35

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 
------------------------------- 
Sarus
  • 3,303
  • 1
  • 23
  • 27

2 Answers2

26

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', { /* ... */ }));
});
Community
  • 1
  • 1
S'pht'Kr
  • 2,809
  • 1
  • 24
  • 43
  • Cool thank you! I actually ripped out Ember Data from my code because I was getting tired of trying to figure out what it wanted but I have it saved in another branch. Will go back and try this solution out to see if it works. Thanks for sharing! (Will let you know here how it goes) – Sarus Mar 06 '14 at 15:10
  • @Sarus Be advised, I'm still figuring out how this works in my apps as well. I've been trying on and off for about a year now. The new 1.0.0-betaX Ember Data builds are MUCH better to work with now (read: might actually work beyond the examples), but it's still rough. The price we pay for riding the bleeding edge. I'll add any changes I find I have to make. – S'pht'Kr Mar 06 '14 at 18:58
  • After you save the basket and then save each item in the basket individually, did this result in a new POST request for each "item" that you added to the "fruits" array or does it somehow put all these together into a single POST request? – Sarus Mar 07 '14 at 14:35
  • @Sarus What I observed was one post request per model object. That is, in your example, one POST for the basket, then one POST for the banana and one POST for the orange. I'm unsure whether it's possible to get Ember Data to make them all POST together, but I imagine there are some people with strong opinions on how that would (or wouldn't) be "RESTful". In any case, as I mentioned when I POST the basket it doesn't even send the _ids_ of the fruits in the `fruits` array (well, they don't have them, so how could it), so I'm not surprised it doesn't post the objects. – S'pht'Kr Mar 07 '14 at 20:02
  • @Sarus Also note that I've opened a [similar question here](http://stackoverflow.com/questions/22245793/belongsto-promise-is-unsettled-when-serializing-a-modified-record) that elaborates on the solution I gave--and addresses a problem I had when trying to get this code to work for creating new objects _and_ updating existing ones. – S'pht'Kr Mar 07 '14 at 20:08
  • I was hoping there might be some way to send all the needed data back at once in a single POST as it seems pretty inefficient to require multiple POSTS to construct an object like a fruit basket. With two fruits it's okay but what if you have 100 fruits? It seems like Ember Data does not support this yet although I think I recall seeing something about plans to support "bulk" saves and do so in a more efficient manner. I'm marking you answer as accepted since its very useful information and I'm reasonably certain that what I want to do can't be done yet without lots of custom code. Thanks! – Sarus Mar 07 '14 at 20:17
  • 1
    @Sarus Indeed, I imagine you should be able to at least POST the basket, and then POST all the fruits in one go--that way you'd still be POSTing all of a single type of resource to a single endpoint, and the response would contain only one type of (primary) object. But yes, this probably isn't ready yet in Ember Data, though I honestly don't know for sure. Given my history with this I'm thrilled to have "working" and I'll take "efficient" when it comes :-). – S'pht'Kr Mar 07 '14 at 20:23
17

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.

  • Where is this current documentation? How to persist (especially if creating multiple in a loop? – leejt489 Apr 01 '15 at 20:36
  • Documentation found [here](http://emberjs.com/api/data/classes/DS.ManyArray.html#method_createRecord). You basically use it like `store.createRecord` but you can call it on a relationship array such as `comments` listed above. To persist you would do `newComment = post.get('comments').createRecord({})` and then `newComment.save()` as usual. – Dalton A. Mitchell Apr 01 '15 at 20:58
  • This method is private at least at 2.7.0. – jax Oct 12 '16 at 01:00