I am having troubles with ember data and I was seeking some help. I am using findRecord to return a single record('location') and then getting a single attribute out of that record('product'). There are other attributes that return just fine(phone, name, etc), however "product" does not return until I have fired the model call at least twice. I have checked my network tab and it is coming in fine from the api, but it does not seem to be loading into ember data(until it fires twice). Has anyone else come across this? I am completely stumped. Thanks!
It looks to me that you have a model that is defined as follows:
/* location model */
export default Model.extend({
phone: attr(),
name: attr(),
/*...*/
product: belongsTo('product')
});
Then the code you are trying to execute is most probably something like:
let name = location.get('name'); // This returns immediately.
let product = location.get('product'); // This does not work as expected
If that is the case then your problem is that you are trying to acquire the product from the location synchronously while it is an asynchronous relationship. This means that you have two options:
Option #1: Make the relationship synchronous (as mentioned by Paul Oliver)
/* location model */
export default Model.extend({
phone: attr(),
name: attr(),
/*...*/
product: belongsTo('product', {async: false})
});
Option #2: Wait for the promise to complete
location.get('product').then(function(product) {
// Do something with product here
});
Related
I have two models. Lets call them mult and post. They have a many to many relationship.
Their definitions looks like:
Post:
export default Model.extend({
name: attr(),
mult: hasMany('mult')
});
Mult:
export default Model.extend({
name: attr(),
perspectives: hasMany('post')
});
I want to start off by creating a Post and adding Mults to it. So, I do
createPost(name) {
let post = this.get('store').createRecord('post', {
name: name, mults: []
});
post.save();
},
in my controller.js. However, this then throws an exception of
mult.js:3 Uncaught TypeError: (0 , _emberData.default) is not a function
and it is referring to this line of mult:
name: (0, _emberData['default'])(),
Can I create a post first without providing any mults? If so, what might be a suggested workaround?
You can't set hasMany relations on createRecord. Confusing, I know, but first create the record then set the muts.
EDIT: I've set up an actual repro of the issue on JSBIN
Been trying to resolve this for a while now and I'm clearly not understanding how the relationship between model and setupController works. I have a model which is returning a hash; the result of two find calls:
model(params) {
return Ember.RSVP.hash({
course: this.store.find('course', params.course_id),
topics: this.store.find('topic', { course_id: params.course_id })
});
},
The first time setupController gets called, the value of model if as expected, a hash like { course: <Class>, topics: <Class> }. Awesome, that's what I want.
However, the next time setupController gets called (for example, transition to another route and then press the back button in the browser), the model is now just the course <Class>:
setupController(controller, model) {
// when first called model will be { course: <Class>, topics: <Class> }
// next time entered, model will just be <Class> (just the value of "course" )
// why is the model object not preserved?
controller.set('model', model.course);
controller.set('topics', model.topics);
}}
If I just make model() return a single resource, it's the same every time:
model(params) { return this.store.find('course', params.course_id); }
// now `model` will always be "course" in setupController
Why is the original model not preserved when using a hash result? Am I doing something wrong?
You're sending the model color when you're linking here:
{{#link-to 'color' color}}{{color.name}}{{/link-to}}
Because of that, the model hooks aren't run. If you change that to color.id, it'll work.
It's mentioned here.
In the above example, the model hook for PhotoRoute will run with
params.photo_id = 5. The model hook for CommentRoute won't run since
you supplied a model object for the comment segment. The comment's id
will populate the url according to CommentRoute's serialize hook.
Looking at it, the original model will not be preserved because on setupController, you are calling controller.set('model', model.course). When it first loads, its called the model(params {} function appropriately, but on back button transitions and certain {{link-to}} calls, that isn't always the case.
In your setupController, try changing it to controller.set('course', model.course);, that way you aren't overwriting your model on execution as well and it will always be able to find it.
I am trying to search through several thousand records for a particular record. I have loaded a User model and it has several thousand Shipments that are async. In other words I have the serializer send down shipment_ids with the user model.
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true })
});
I want to search through all those ids to see if a particular record's id is among them. However I don't want to go to the api and load each and every single record. I just want to be able to search through the shipment_ids.
I have a jsbin that showcases it loading all the records. How do I change this so it doesn't make any api calls for a shipment and still finds the specific record?
Instead of doing
this.get('shipments').any(function(shipment){
return shipment.get('id') === "10124";
there has to be a way to go through only the ids, no?
Thanks!
The solution described in "Get belongsTo ID without fetching record"
seems to work as of Ember Data 1.0.0-beta.10. Briefly, you can do the following to access the underlying
modelInstance.get('data.belongsToRelation.id');
Presumably you can also do this (though I have not tested it):
modelInstance.get('data.hasManyRelation');
Original Answer (DO NOT USE):
I have accomplished this on a belongsTo by adding a second model field for the id. My example:
App.User = DS.Model.extend({
group: DS.belongsTo('group', { async: true }),
groupId: DS.attr('number')
});
So maybe you can do this?
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true }),
shipmentIds: DS.attr('array')
});
You will need to add an array transform in app/transforms/array.js (assuming you use ember-cli)
import DS from 'ember-data';
var ArrayTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
export default ArrayTransform;
Let me know if it works as I probably will need to something similar in my app soon.
This hack is very smelly but it's the only way I have found. And it is to search with _data
this.get('content._data.shipments').any(function(shipment){
return shipment.get('id') === "10124";
});
It won't make any api calls. But there has to be a more acceptable method that won't be prone to breakage when updating Ember.
Here is an updated jsbin to show this. Does anyone have a better solution?
Thanks!
Since Ember Data 2.3 you can use this code:
// get all ids without triggering a request
var commentIds = post.hasMany('comments').ids();
See http://emberjs.com/blog/2016/01/12/ember-data-2-3-released.html for details.
What is the best practice for immediately persisting a model, when one of it's attributes is bound to a template input? Does it belong to the model or the controller, in your opinion?
I came up with this solution, based on an observer:
# Models
App.Foo = DS.Model.extend
bars: DS.hasMany('bars')
App.Bar = DS.Model.extend
quantity: DS.attr('number')
# Template
{{#each bar in bars}}
{{input value=bar.quantity}}
{{/each}}
# Controller
persistQuantity: ( ->
#get('bars').forEach (bar) -> bar.save() if bar.get('isDirty')
).observes('bars.#each.quantity')
But this fires multiple (3 for me) save requests for the same model for some reason.
I also tried to put the observer on the model, but this went to an endless loop:
# App.Bar Model
persistQuantity: ( ->
#save()
).observes('quantity')
I tried to fix that through Ember.run.once, but my understanding of the Ember run loop wasn't deep enough, apparently.
Where it belongs depends on whether or not you want the model to save whenever it changes, or only save when it changes from a particular view. If you want the model to always save, regardless of where it's saved, do it on the model. If you want to control saving it from a particular view do it in the controller.
Debouncing would be my favorite option for solving the multiple call issue. Watching a particular item, then automatically saving when it changes. You could also watch isDirty and fire when it changes, but I'm more a fan of the other pattern (though isDirty scales better, it's less informative). Here's both patterns, feel free to mix and match as appropriate.
Have the model auto save when dirty:
App.Bar = DS.Model.extend
quantity: DS.attr('number'),
watchDirty: function(){
if(this.get('isDirty')){
this.save();
}
}.observes('isDirty')
Example: http://emberjs.jsbin.com/OxIDiVU/898/edit
Have the model queue saving when an item gets dirty (or multiple items)
App.Bar = DS.Model.extend({
quantity: DS.attr(),
watchQuantity: function(){
if(this.get('isDirty')){
Em.run.debounce(this, this.save, 500);
}
}.observes('quantity')
});
Example: http://emberjs.jsbin.com/OxIDiVU/897/edit
I have an application that saves a user's search criteria in localStorage, where each saved search is represented as an instance of an Ember.js model:
Checklist.SavedSearch = DS.Model.extend({
id: DS.attr('string'),
filters: DS.attr('string')
});
When the "save" button is pressed, the controller creates a model instanced and creates a record for it:
Checklist.savedSearchController = Ember.ArrayController.create({
[..]
save: function(view) {
var saved_seach = Checklist.SavedSearch.createRecord({
id: 'abcd',
filters: '<json>'
});
Checklist.local_store.commit();
}
});
Checklist.local_store is an adapter I created (this is unsurprisingly where the problem probably begins) that has a basic interface that maps createRecord, updateRecord, etc. to a bunch of get/set methods that work with localStorage (loosely based on a github fork of ember-data). The adapter appears to work fine for some basic tests, particularly as findAll has no issues and returns values added manually to localStorage.
Here is the relevant method within Checklist.local_store:
createRecord: function(store, type, model) {
model.set('id', this.storage.generateId);
var item = model.toJSON({associations: true});
this.storage.setById(this.storage_method, type, id, item);
store.didCreateRecord(model, item);
}
The problem is that when createRecord is called by the controller, absolutely nothing occurs. Running it through the debugger, and logging to console, seems to show that the method isn't called at all. I imagine this is a misunderstanding on my part as to how Ember.js is supposed to work. I'd appreciate help on why this is happening.
I come from a ruby and php background, and have perhaps foolishly dived straight in to a JS framework, so any other comments on code style, structure and anything in general are welcome.
Ember Data doesn't change createRecord on the controller so it shouldn't behave any differently. It's possible that there was something related to this in the past, but it's certainly not the case anymore.