Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember.js & Ember Data: How to create record with hasMany relationship when the parent and child don't exist yet

As seems to be the case with most everything I do with ember-data the simple test case is very simple but anything "real" falls apart quickly due to my lack of understanding (and I think documentation). In the simplest case I have no problem creating a new record and saving it to my REST API. I'll use the prevalent fruit basket example and start with just saving a new piece of fruit.

// Here is our fruit model module.exports = App.Fruit= DS.Model.extend({     name: attr('string'),        color: attr('string') });  // This is easy to create and save var fruit = this.store.createRecord('fruit', {     name: 'apple',     color: 'red' });  fruit.save().then(successCallback, errorCallback); 

Okay, I have no problem here. Conceptually makes sense, the code is clean, and things look peachy! Now suppose that we have a basket model and that basket model can hold lots of fruit. Furthermore, the user can create new fruit and add it to a new basket that has not been created yet in one step from the UI.

So here are the new models:

module.exports = App.Fruit= DS.Model.extend({     name: attr('string'),        color: attr('string')     basket: DS.belongsTo('basket') });  module.exports = App.Basket = DS.Model.extend({     fruits: DS.hasMany('fruit', {async:true}) }); 

Given the above, lets say the user inputs in two new fruits that don't currently exist in the store or in the backend REST API, adds them to his new basket (which also doesn't exist yet) and then hits 'save'.

How do you create this record using Ember Data? I was hoping you could do something like this:

var newFruits = Ember.A();  newFruits.pushObject(this.store.createRecord('fruit', {     name: 'orange',     color: 'orange' }));  newFruits.pushObject(this.store.createRecord('fruit', {     name: 'banana',     color: 'yellow' }));  var basket = this.store.createRecord('basket', {     fruits: newFruits });  basket.save().then(successCallback, errorCallback); 

Unfortunately, this doesn't seem to work. When I look at the POST request data in Chrome inspector the fruits property of basket is always empty. Basically, the request ends up looking this:

{     basket: {        fruits: []     } } 

Am I using the correct pattern to create the 'basket' record? I'm hoping I don't have to end up saving each piece of fruit first before saving the basket as it seems wasteful to send 3 POST requests when 1 would suffice. Additionally, what happens when the user wants to create 10 pieces of fruit? That would be 11 POST Requests.

Is there a standard practice for accomplishing the above? Essentially, how do you create a new record with a hasMany relationship (basket) where the records in the hasMany relationship are also new (the fruit).

Thank you!

Here are the versions I am using:

-------------------------------  Ember      : 1.3.2+pre.25108e91  Ember Data : 1.0.0-beta.7+canary.238bb5ce  Handlebars : 1.3.0  jQuery     : 2.0.3  -------------------------------  
like image 811
Sarus Avatar asked Feb 24 '14 09:02

Sarus


People also ask

Is Ember JS still used?

Ember has been an enabler of great productivity for many teams for almost a decade and I'm sure it's going to continue to be that. It's changed and improved a lot since its first release and is now in better shape than ever with its Octane edition.

What is Ember JS good for?

Ember. js is an open source, free JavaScript client-side framework used for developing web applications. It allows building client side JavaScript applications by providing a complete solution which contains data management and an application flow.

Is Ember JS frontend or backend?

It should also be mentioned that Ember is purely a frontend framework. It has a number of ways of interacting with the backend of your choice, but this backend is not in any way handled by Ember itself.

Is Ember JS popular?

6.3% of javascript developers are currently using Ember. Its popularity has stagnated over the last few years. Ranked 4rd most popular front-end JavaScript framework in State Of JS survey.


2 Answers

I'm just figuring this out myself, but I just did it this way and it works…

For starters, when you this.store.createRecord('basket', {}) it should come with an empty array for its fruits property. So, do this:

var basket = this.store.createRecord('basket', {});  basket.get('fruits').addObject(this.store.createRecord('fruit', {     name: 'orange',     color: 'orange' }));  basket.get('fruits').addObject(this.store.createRecord('fruit', {     name: 'banana',     color: 'yellow' })); 

Incidentally, the empty fruits array is actually a DS.PromiseArray (which may instantly resolve to a DS.ManyArray?), a more specific subclass of Ember.Array, so that could make a difference.

However, what I found was that the fruits property wouldn't get serialized at all--I'm using the JsonApiAdapter though, so that could be the reason. Or it might be that this is the expected behavior when the hasMany child objects haven't been persisted yet. Anyway, I just did something like this in my controller's actions.save method:

basket.save().then(function(){     var promises = Ember.A();     basket.get('fruits').forEach(function(item){         promises.push(item.save());     });     Ember.RSVP.Promise.all(promises).then(function(resolvedPromises){         alert('All saved!');     }); }); 

Somehow, magically, when all that was done, I ended up with a saved "basket", with a generated id, and saved "fruits" with generated ids. And, all the fruits' belongsTo properties pointed back at the basket, and the basket's hasMany contained all the fruits…despite the fact that, when I save()d the basket, the response from my backend returned an empty array for my fruits property, which I was sure would mess things up but somehow didn't.

Of course, these weren't my actual models, but the parallel was very close. I'm on Ember Data 1.0.0-beta5 and Ember 1.3.1. I'm not using the stock REST adapter, but the JsonApiAdapter is a pretty thin shell over it--so try this approach and see if it works!

Edit

I've opened a similar question here that addresses a problem I encountered using the above code to also update existing records (as opposed to creating new ones). That solution is more general and detailed but more complex.

Also, another potential gotcha I ran into is that when (in this example) the basket is created, the fruits property (which is a DS.PromiseArray) may not be fully ready yet. So you may need to do it like this instead:

var basket = this.store.createRecord('basket', {});  basket.get('fruits').then(function(){     // Now the fruits array is available     basket.get('fruits').addObject(this.store.createRecord('fruit', { /* ... */ })); }); 
like image 146
S'pht'Kr Avatar answered Oct 02 '22 08:10

S'pht'Kr


In case anyone else is getting to this late, the current documentation points out that you can simply call createRecord() like this:

post.get('comments').createRecord({})

which works even if post is an empty object.

like image 45
Dalton A. Mitchell Avatar answered Oct 02 '22 07:10

Dalton A. Mitchell