Validating computed property in model - ember.js

So far, I have an 'address' model set as the following:
import Ember from 'ember';
import DS from 'ember-data';
import { validator, buildValidations } from 'ember-cp-validations';
const { attr } = DS;
const { computed } = Ember;
const Validations = buildValidations({
streetName: validator('presence', true),
});
export default DS.Model.extend(Validations, {
/* Properties */
streetName: attr(),
streetNumberNum: attr('number'),
streetNumberAlpha: attr(),
addressDetails: attr(),
municipalityName: attr(),
zip: attr(),
/* computed */
streetNum: computed('streetNumberNum', 'streetNumberAlpha', function() {
return this.get('streetNumberNum') + '' + this.get('streetNumberAlpha');
})
});
I created the computed property streetNum so that I can use it in the template. The idea is to allow the user to input, in a field, his address street number (including an alpha) within a unique field (i.e. 1000a).
I would like to use the ember-cp-validations addon for validations purposes; and I was wondering how can I make ember understand this usecase.

Related

RangeError: Maximum call stack size exceeded when using ember-cli-mirage

I am having issue using ember-cli-mirage. My instance of mirage is configured to use the RESTSerializer and the EmbeddedRecordsMixin for relationships. I am able to get records with relationship without a problem, however when I save the parent record I get the following error coming from the ember-data code:
RangeError: Maximum call stack size exceeded
Weirdly enough, if I just removed the EmbeddedRecordsMixin everything works fine. Is there some restriction or special thing you need to do use the EmbeddedRecordsMixin with mirage
// models/artist.js
import DS from 'ember-data';
const { attr, hasMany, Model } = DS;
export default Model.extend({
name: attr('string'),
genre: attr('string'),
country: attr('string'),
bio: attr(),
albums: hasMany('album', {
async: false
}),
songs: hasMany('song', {
async: false
})
});
// serializers/artist.js
import DS from 'ember-data';
const { RESTSerializer } = DS;
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
albums: {
embedded: 'always'
},
songs: {
embedded: 'always'
}
}
});
// mirage/models/artist.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
albums: hasMany(),
songs: hasMany()
});
// mirage/factories/artist.js
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
name() {
return faker.name.findName();
},
genre() {
return faker.hacker.noun();
},
country() {
return faker.address.country();
},
bio() {
return faker.lorem.sentence();
},
afterCreate(artist, server) {
server.createList('album', 3, { artist });
server.createList('song', 3, { artist });
}
});
// mirage/serializers/artist.js
import { RestSerializer } from 'ember-cli-mirage';;
export default RestSerializer.extend({
embed: true,
include: ['songs', 'albums']
});

Trouble generating a model instance and applying it to a specific key in Ember Mirage

While generating an instance of a staffMember within ember-cli-mirage I'm attempting to create a 'task' and assign it to the key 'tasksCreated' within the staffMember model. My current code is as follows;
It's creating a task, and creating a staffMember but the two have no relationships built between them.
app/models/staff-member.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
atWork: DS.attr('boolean'),
avatar: DS.attr(),
tasksCreated: DS.hasMany('task', {
inverse: 'creator'
}),
tasksAssigned: DS.hasMany('task', {
inverse: 'assignee'
})
});
app/models/tasks.js
import DS from 'ember-data';
export default DS.Model.extend({
creator: DS.belongsTo('staff-member', { inverse: null }),
assignee: DS.belongsTo('staff-member', { inverse: null }),
creationDate: DS.attr(),
description: DS.attr('string'),
urgency: DS.attr()
});
mirage/models/staff-member.js
import {Model, hasMany} from 'ember-cli-mirage';
export default Model.extend({
tasksCreated: hasMany('task'),
tasksAssigned: hasMany('task', { inverse: 'assignee'})
});
mirage/models/task.js
import {Model, belongsTo} from 'ember-cli-mirage';
export default Model.extend({
creator: belongsTo('staff-member'),
assignee: belongsTo('staff-member')
});
mirage/factories/staff-member.js
import { Factory, faker, trait } from 'ember-cli-mirage';
export default Factory.extend({
name: faker.name.firstName,
atWork: faker.random.boolean,
avatar: faker.image.avatar,
withTasks: trait({
afterCreate(staffMember, server){
server.createList('task',2,{tasksCreated: [staffMember]});
}
})
});
mirage/factories/task.js
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
creationDate: faker.date.recent,
description: faker.lorem.sentence,
urgency: faker.random.number({
'min': 0,
'max': 4
})
});
mirage/scenarios/default.js
export default function(server ) {
server.createList('staff-member', 4, 'withTasks');
}
I tried to remove most of the code that wasn't directly related to setting the relationships up. Before fiddling with the factories, try to get the relationships working in just the default scenario.
I've made a working twiddle here: https://ember-twiddle.com/5bcdcdee50faa0c0a679c3c4d35fe0ea?openFiles=mirage.scenarios.default.js%2C
Here's what the default scenario looks like:
export default function(server ) {
let member1 = server.create('staff-member');
server.createList('task', 2, { creator: member1 });
server.create('task', { assignee: member1 });
let member2 = server.create('staff-member');
server.create('task', { creator: member2 });
server.createList('task', 3, { assignee: member2 });
}
As far as your factory afterCreate code goes, one error is in this line
server.createList('task',2,{tasksCreated: [staffMember]});
You're creating a task, but passing in tasksCreated as a property of that task. But tasks don't have a tasksCreated property, staffMembers do. Maybe something like this is what you're looking for:
server.createList('task', 2, { creator: staffMember });

Ember sort newly added data

I started working on a new ember project. I have never used ember before.
I am working with an api that does not conform with the JSON API spec and does not have a websocket.
So I poll the api to get the latest data.
I can get the latest data but it is rendered in the view on the bottom instead of the top. I have looked at 1, 2, 3 to no avail.
How do I get the new data to render at the top of the list?
//sample output after a new job fetched
2
1
3
//desired output
3
2
1
This is a new project so I don't want to use anything that will be depreciated in 2.0 (controllers, etc.). I am open to changing the model(s) if that works.
My route looks like this:
//routes query.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var interval = 1000; // every second
Ember.run.later(this, function() {
this.model().then(function(json) {
this.controller.set('model', json);
}.bind(this));
}, interval);
return Ember.RSVP.hash({
query: this.store.find('query'),
job: this.store.peekAll('job')
});
},
});
My models are:
//models query.js
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ'), <-- this doesn't seem to work
});
//models job.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
submitter: DS.attr('string'),
submission_time: DS.attr('date'), // '2015-04-27 15:14:55', // date?
completion_time: DS.attr('date'), // '2015-04-27 15:15:08',
took: DS.attr('number'),
statuses: DS.attr('string'), // object
query: DS.belongsTo('query'),
});
So my serializer is:
// serializers query.js
import DS from 'ember-data';
import assign from 'npm:object-assign';
export default DS.RESTSerializer.extend({
isNewSerializerAPI: true,
normalizeResponse: function (store, primaryModelClass, payload) {
// delete things we don't want
delete payload.error;
delete payload.status;
// populate array
var jobs = [],
relationships = [];
// copies the jobs to a new var keep the payload clean
Object.keys(payload.jobs).forEach((key) => {
let attributes = {};
assign(attributes, payload.jobs[key]);
delete attributes.id;
jobs.push({ id: key, type: 'job', attributes: attributes});
relationships.push({ id: key, type: 'job'});
});
var c = payload.count;
//jobs
// [{
// id:
// type:
// attributes: {
// name:
// filter:
// ...
// },
// ...
// }]
return {
data: {
id: 1,
type: 'query',
relationship: {
job: {
data: relationships
}
},
attributes: { count: c }
},
included: jobs
};
}
});
A template that looks like this would work just fine:
{{#each model.query as |query|}}
{{#each query.jobsSorted as |job|}}
{{job.took}}
{{/each}}
{{/each}}
What does your template look like? Although to sort it on a descending order you would need to add :desc to the sort order:
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took:desc'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ')
});
Here's a JSFiddle demonstrating that Ember.computed.sort works like you're trying to use it: http://emberjs.jsbin.com/towerukafa/3/edit?html,css,js,output

Filter through a hasMany connection

I have a route and a destination model and would like to create a filteredRoutes computed property which only contains routes which have a destination.name with the value of selectedDestination. I can't figure out the last puzzle piece to do that. How can I filter that?
controller.js
filteredRoutes: Ember.computed('model.routes', 'selectedDestination', function() {
var selectedDestination = this.get('selectedDestination');
var routes = this.get('model.routes');
if(selectedDestination) {
routes = routes.filter(function(route) {
// ????
});
}
}),
app/destination/model.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string')
});
app/route/model.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
destinations: DS.hasMany('destinations', { async: true })
});
Use filter, as you had suggested, with the filter condition being the presence of at least one destination whose name is equal to the selected one.
filteredRoutes: Ember.computed(
'model.routes.#each.destinations.#each.name',
'selectedDestination',
function() {
var selectedDestination = this.get('selectedDestination');
return this.get('model.routes') . filter(
route =>
route.get('destinations') . find(
destination =>
destination.get('name') === selectedDestination
)
);
}
)
In English:
Find the routes which have at least one destination whose name is the same as the selected destination.

Filter within a date range

I have a cruise model which hasMany trips. A trip stores the start and end date. I'd like to filter cruises which start at or later than earliestStartDate and which end latests at latestEndDate.
app/models/cruise.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
trips: DS.hasMany('trip', { async: true })
});
app/models/trip.js
import DS from 'ember-data';
export default DS.Model.extend({
starts_on: DS.attr('date'),
ends_on: DS.attr('date')
});
app/templates/index.hbs
{{date-picker date=earliestStartDate valueFormat='YYYY-MM-DD'}
{{date-picker date=latestEndDate valueFormat='YYYY-MM-DD'}}
How can I change filteredCruises in this controller to filter the cruises which have trips within the given dates?
app/controllers/index.js
[...]
filteredCruises: function() {
var earliestStartDate = this.get('earliestStartDate');
var latestEndDate = this.get('latestEndDate');
var cruises = this.get('model.cruises');
return cruises;
}.property('model.cruises','earliestStartDate','latestEndDate'),
[...]
I haven't touched Ember in a while, and I haven't tested/executed this code, but I think this might work for you (and I am sure this can be optimized):
[...]
filteredCruises: function() {
var earliestStartDate = this.get('earliestStartDate');
var latestEndDate = this.get('latestEndDate');
var cruises = this.get('model.cruises');
return cruises.filter(function(cruise) {
return cruise.get('trips').filter(function(trip) {
return ((trip.get('starts_on') >= earliestStartDate) && (trip.get('ends_on') <= latestEndDate));
});
});
}.property('model.cruises','earliestStartDate','latestEndDate'),
[...]