Trouble accessing data in relationships in Ember - ember.js

I'm having trouble saving data in this model relationship. My models are as follows:
App.Flow = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
isCustom: DS.attr('boolean'),
params: DS.hasMany('parameter', {async: true})
});
App.Parameter = DS.Model.extend({
flow: DS.belongsTo('flow'),
param: DS.attr('string'),
param_tooltip: DS.attr('string'),
param_value: DS.attr('string')
});
As you can see, I want Flows to have multiple Parameters. I have a rudimentary setup using Flow and Parameter fixtures, which behave as expected in the templates. However, when I try to create new ones in the controller, I have trouble setting the flow and parameter values correctly.
var p = this.store.createRecord('parameter', {
param: "foo",
param_tooltip: "world",
param_value: "hello"
});
var f = this.store.createRecord('flow', {
title: 'job',
content: title,
isCustom: true,
params: [p] // doesn't seem to work
});
f.set('params', [p]); // doesn't seem to work
p.set('flow', f); // also doesn't seem to work
// Save the new model
p.save();
f.save();
I've tried a lot of solutions after staring at this and StackOverflow for a while (not just the ones listed). I'm not really sure what to try next. One thing that I noticed in the Ember inspector was that the ids of these created elements were not integers (they were something like the string 'fixture_0'), but I'm not really sure why that would be, whether its related, or how to fix it.
Thanks!
Edit
After dealing with promises ala kingpin's solution, the parameter seems like it is being set in the createFlow function I have when I check with print statements; however, I'm still having trouble accessing the parameters in both my template and in the removeFlow function.
createJob: function() {
// ...
f.get('params').then(function(params) {
params.pushObject(p);
});
f.get('params').then(function(params) {
console.log(params.toArray()[0].get('param')); // prints 'foo'
});
// ...
},
removeJob: function(flow) {
console.log(flow.get('title')); // prints 'job'
flow.get('params').then(function(params) {
var arr = params.toArray();
console.log(arr); // prints '[]'
for (var i=0; i < arr.length; i++) {
console.log('hi'); // never gets printed
arr[i].deleteRecord();
arr[i].save();
}
flow.deleteRecord();
flow.save();
});
},

params being async needs to be waited on before you can set a property or model on it (this is something that is being hardened out before 1.0 finally hits, but it's finicky right now in 1.0 beta). The fixture_ids are applied if you're using the fixture adapter.
var store = this.store;
var p = store.createRecord('parameter', {
param: "foo",
param_tooltip: "world",
param_value: "hello"
});
var f = this.store.createRecord('flow', {
title: 'job',
content: 'title',
isCustom: true,
});
f.get('params').then(function(params){
params.pushObject(p);
});
p.set('flow', f);
http://emberjs.jsbin.com/OxIDiVU/588/edit
Additionally saving using the fixture adapter seems to remove hasMany relationships when their is a manyToOne connection (aka both records have a relationship to each other, one of them being a hasMany). Getting rid of one of the relationships fixes the issue (or using one of the real adapters)
http://emberjs.jsbin.com/OxIDiVU/590/edit

Related

Ember return length of a model created today

I am trying to do this: I have a model called 'trip', and inside trip, an attribute called 'createdToday', which returns the date when a trip is created. What I want is to return a list of trips that were made today.
Here is my trip model:
import DS from 'ember-data';
export default DS.Model.extend({
driver: DS.belongsTo('driver', {
async: true,
inverse: 'trip'
}),
..... etc .......
createdAt: DS.attr('string', {
defaultValue() {
return new Date();
}
}),
isBookedToday: function(trip) {
var today = new Date().toDateString();
return (today === trip.get('createdAt').toDateString);
},
getTripsToday: Ember.computed('trip.#each.createdAt', function() {
var tripsToday = this.get('trip');
return tripsToday.filterBy('isBookedToday', true).get('length');
})
});
In my isBookedToday, I'm trying to see if an individual trip's created time is the same as todays time, and in getTripsToday, I am trying to loop through all the trips and filtering by isBookedToday.
And in my .hbs file, I'm saying: {{trips.getTripsToday}}, which won't render anything, so something's wrong.
I guess I am most confused at Ember's #each and exactly how it works.
Thanks for any feedback.
First you have to understand that your Trip Model instances represents a single Trip! Its absolutely not the right place to put a function that gives you a filtered list of trips!
Next isBookedToday is a normal function not a Computed Property. So you can't filterBy on it.
You may want to implement a isBookedToday on your trip, but you definitely have to filter your trips on the same place where you fetch them! Probably in a model() hook or a Computed Property on a component or a controller.
So you could do but don't need to do in your models/trip.js:
...
isBookedToday: Ember.computed('createdAt', {
get() {
let now = new Date();
let created = get(this, 'createdAt');
return now.getFullYear() === created.getFullYear() &&
now.getMonth() === created.getMonth() &&
now.getDate() === created.getDate();
}
})
...
And then in your model hook:
model() {
return this.store.findAll('trip').then(trips => trips.filterBy('isBookedToday'));
}
Or in a Computed Property in a controller or a component:
tripsToday: Ember.computed('trips.#each.isBookedToday', {
return get(this, 'trips').filterBy('isBookedToday');
})
Be careful. This will result in confusing things if you leave the page open overnight! when your date changes the Computed Properties will not recompute automatically!

How to filter records using hasMany association

Trying to figure out what should be a simple solution: filterBy records using an array of hasMany ids.
I have a model Language, which hasMany Providers, and vice versa. From a collection of Providers, I need to filterBy('language', [1,2]) or even filterBy('language', 2).
(I can't do a language.get('providers') since I'd getting the collection of Providers from another association.)
// models.language.js
var Language = DS.Model.extend({
title: DS.attr('string'),
providers: DS.hasMany('provider')
});
// models/provider.js
var Provider = DS.Model.extend({
title: DS.attr('string'),
region: DS.belongsTo('region', { async: true }),
languages: DS.hasMany('language', {async: true})
});
// In this region-component.js I have the model title,
// and then an each block listing all providers
// When the language filter changes, I need it to filter down
// just to those providers who have the language association.
providers: function() {
return this.get('model').get('providers').filterBy('languages', this.get('language'));
}.property('model')
filterBy filters based on a single property, but you can just use filter itself, passing it a selection function:
filteredProviders: function() {
let providers = this.get('providers'); // some providers to select from
let languages = this.get('languages'); // array of languages to select
// Does a provider have one of the languages we're looking for?
function providerHasLanguage(provider) {
return provider.get('languages') .
some(language => languages.indexOf(language) !== -1;
}
return providers . filter(providerHasLanguage);
}.property('providers', 'languages')
So some redditors helped me out and I worked it from there. Seems there is no native solution but to iterate through each array and render a new array from matching objects.
// app/utils/where.js
let where = function(sourceArray, property, language_id) {
let destArray = [];
sourceArray.forEach(function(obj) {
obj.get(property).forEach(function(property_object) {
let id = parseInt(property_object.get('id'));
if (language_id === id) {
destArray.push(obj);
}
});
});
return destArray;
}
export default where;
And its usage:
// in a component
import where from 'path';
let filteredProviders = where(providers,
'languages', language_id);
Minor modifications would be required to accommodate filtering from an array of multiple language_ids.

How do I set a dynamic model attribute in the fixture?

As the title describes, I am running into trouble making a dynamic attribute on the Fixture layer.
Here is an example model:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes')
});
And my Fixture:
App.Pokeball.reopenClass({
FIXTURES : [
{
id: 1,
name: 'PokeBall',
ballRate: 1
},
{
id: 23,
name: 'Dusk Ball',
ballRate: function() {
// Some logic that applies only model #23
return 2;
}.property('battleAttributes')
}
]
});
I scoured online trying to find out the right way to do this, but have instead ran into a dead end. :(
This is an invalid use of fixtures. They’re meant to represent JSON (or whatever) on the server that is passed to your application and turned into Ember Data models. JSON cannot represent the concept of a computed property, it’s for pure data.
I don’t understand your use case so I could be way off, it seems like you should use a computed property on the model instead:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes'),
adjustedBallRate: function() {
if (this.get('battleAttributes.whateverPropertyCausesThisToChange') == 'special value') {
return 2;
}
else {
return this.get('ballRate');
}
}.property('battleAttributes.whateverPropertyCausesThisToChange')
});

EmberJS - Computed property referencing firstObject do not update

I have a model like this:
App.Conversation = DS.Model.extend({
body : DS.attr('string'),
created_at : DS.attr('date'),
entry : DS.hasMany('Entry', {async: true}),
user : DS.belongsTo('User'),
allEntriesLoaded : DS.attr('boolean'),
entryProxyBody : function() {
return this.get('entry.firstObject.body');
}.property('entry.firstObject.body')
});
As you can see it references its Entry hasMany relationsship in the function entryProxyBody. This reference works great, as calling entryProxyBody do indeed return the body-attribute from the first object in Entry.
However my problem is, that the computed property is not updated, when a new value is added to the Entry-store.
I add a new record like this:
App.NewController = Em.ObjectController.extend({
actions: {
save: function() {
var entry = this.store.createRecord('entry', {body: 'Test', created_at: new Date() });
this.store.find('conversation', this.parentController.get('id')).then(function(conversation) {
conversation.get('entry').pushObject(entry);
entry.save();
});
}
},
});
However.. If I update the first object in Entry directly using Ember Inspector in Chrome, then the computed property changes as it should.
What am I missing? Thank you for your help!
I think you might want to observe for changes for each entry in the entry array.
entryProxyBody:function() {
return this.get('entry.firstObject.body');
}.property('entry.#each.body')

Ember.js: Observing all object properties

I would like to observe all changes on an object properties.
In the following example, i would like to be notified by the personChanged observer if firstname or lastname is changed.
BUT I would like to have something generic applied on all object properties (using Ember.keys() ??)
How to replace 'firstname', 'lastname' with something more generic ?
In my example:
personChanged is called when firstname or lastname is changed:
App.MyObject: Ember.Object.create({
firstname: 'first',
lastname: 'last',
personChanged: Ember.observer(function() {
console.log("person changed");
}, 'firstname', 'lastname')
})
This is possible using an ObjectProxy in two flavours, depending on your requirements. Both approaches differ only in when and how many times is the observer called and both of them rely on Ember.keys.
The HTML for both the solutions is the same.
HTML
<script type="text/x-handlebars" data-template-name="app">
Name: {{App.MyObject.firstname}} {{App.MyObject.lastname}}
<ul>
{{#each App.List}}
<li>{{this}}</li>
{{/each}}
</ul>
</script>
Solution 1
JsFiddle: http://jsfiddle.net/2zxSq/
Javascript
App = Em.Application.create();
App.List = [];
App.MyObject = Em.ObjectProxy.create({
// Your Original object, must be defined before 'init' is called, however.
content: Em.Object.create({
firstname: 'first',
lastname: 'last'
}),
// These following two functions can be abstracted out to a Mixin
init: function () {
var self = this;
Em.keys(this.get('content')).forEach(function (k) {
Em.addObserver(self.get('content'), k, self, 'personChanged')
});
},
// Manually removing the observers is necessary.
willDestroy: function () {
var self = this;
Em.keys(this.get('content')).forEach(function (k) {
Em.removeObserver(self.get('content'), k, self, 'personChanged');
});
},
// The counter is for illustrative purpose only
counter: 0,
// This is the function which is called.
personChanged: function () {
// This function MUST be idempotent.
this.incrementProperty('counter');
App.List.pushObject(this.get('counter'));
console.log('person changed');
}
});
App.ApplicationView = Em.View.extend({
templateName: 'app'
});
// Test driving the implementation.
App.MyObject.set('firstname', 'second');
App.MyObject.set('lastname', 'last-but-one');
App.MyObject.setProperties({
'firstname': 'third',
'lastname' : 'last-but-two'
});
While initialising MyObject, all properties which already exist on the content object are observed, and the function personChanged is called each time any of the property changes. However, since the observers are fired eagerly [1], the function personChanged should be idempotent, which the function in example is not. The next solution fixes this by making the observer lazy.
Solution 2
JsFiddle: http://jsfiddle.net/2zxSq/1/
Javascript
App.MyObject = Em.ObjectProxy.create({
content: Em.Object.create({
firstname: 'first',
lastname: 'last'
}),
init: function () {
var self = this;
Em.keys(this.get('content')).forEach(function (k) {
Em.addObserver(self, k, self, 'personChanged')
});
},
willDestroy: function () {
var self = this;
Em.keys(this.get('content')).forEach(function (k) {
Em.removeObserver(self, k, self, 'personChanged');
});
},
// Changes from here
counter: 0,
_personChanged: function () {
this.incrementProperty('counter');
App.List.pushObject(this.get('counter'));
console.log('person changed');
},
// The Actual function is called via Em.run.once
personChanged: function () {
Em.run.once(this, '_personChanged');
}
});
The only change here is that the actual observer function is now called only at the end of the Ember Run loop, which might be the behaviour you are looking for.
Other notes
These solutions use ObjectProxy instead of defining the observers on the object itself to avoid setting spurious observers (on properties such as init, willDestroy, etc.) or an explicit list of properties to observe.
This solution can be extended to start observing dynamic properties by overriding the setUnknownProperty on the proxy to add an observer every time a key is added to content. The willDestroy will remain the same.
Reference
[1] This might get changed soon thanks to Asyn Observers
What you have there is right using inline observers, but you just have some syntax errors. However it is even simpler to do with the key word observes. So I have tweaked your example a little below.
App.MyObject: Ember.Object.create({
firstname: 'first',
lastname: 'last',
personChanged: function() {
//firstname or lastname changed
}.observes('firstname','lastname')
});
Note: I put quotes around the properties
Source: http://emberjs.com/guides/object-model/observers/