Background
Recently Tom Dale announced that “Embedded loading is now back!”. However it only appears to be fully supported for the loading scenario. It is unclear if serializing embedded associations is still under development, or if users are expected to implement it themselves by building a custom serializer.
Requirements
The JSON for my embedded association looks like this:
{
inputs: [
{
id: 1,
name: "Favorite Color",
type: "SelectInput",
options: {
choices: [ 'red', 'green', 'yellow' ]
}
},
{
id: 2,
name: "email",
type: "TextInput",
options: {}
}
]
}
I believe the “ember way” to represent this is to build a custom serializer that will convert my JSON to models like:
App.Choice = DS.Model.extend({
name: DS.attr( 'string' ),
input: DS.belongsTo( 'App.Input' )
});
App.Input = DS.Model.extend({
name: DS.attr( 'string' ),
type: DS.attr( 'string' ),
choices: DS.hasMany( 'App.Choice' )
});
Attempted Solution
The following solution mostly works, however it feels like I must be “doing it wrong” because I’m having to reverse engineer and subclass so much code.
Customizer.MyRESTAdapter = DS.RESTAdapter.extend({
dirtyRecordsForAttributeChange: function(dirtySet, record, attributeName, newValue, oldValue) {
if(record.constructor === Customizer.Choice) {
if(newValue === oldValue) { return; }
var input = null;
if (attributeName == 'name') {
input = record.get('input');
}
else if(attributeName == 'input') {
input = newValue;
}
if( input ) {
dirtySet.add( input );
}
}
else {
this._super(dirtySet, record, attributeName, newValue, oldValue);
}
},
dirtyRecordsForBelongsToChange: function(dirtySet, child, relationship) {
if(child.constructor === Customizer.Choice) {
var input = child.get( 'input' );
if( input ) {
dirtySet.add( input );
}
}
else {
this._super(dirtySet, child, relationship);
}
},
dirtyRecordsForHasManyChange: function(dirtySet, parent, relationship) {
this._super(dirtySet, parent, relationship);
}
});
Customizer.MyRESTSerializer = DS.RESTSerializer.extend({
init: function() {
this._super();
this.mappings.set( 'Customizer.Input', { choices: { embedded: 'load' } } );
},
extractEmbeddedHasMany: function(type, hash, key) {
if(type == Customizer.Input) {
if(!(hash['options'] && hash['options']['choices'])) { return null; }
var choices = [];
hash['options']['choices'].forEach(function(choice, i){
var choiceId = hash['id'] + '_' + i;
var inputId = hash['id'];
choices[i] = { id: choiceId, input_id: inputId, name: choice };
});
return choices;
}
return this._super(type, hash, key);
},
addHasMany: function(data, record, key, relationship) {
this._super(data, record, key, relationship);
if( key === 'choices' ) {
var choices = record.get('choices').map(function( choice ){
return choice.get( 'name' );
});
data['options'] = data['options'] || {};
data['options']['choices'] = choices;
}
}
});
Customizer.store = DS.Store.create({
revision: 10,
adapter: Customizer.MyRESTAdapter.create({
namespace: 'api/v1',
bulkCommit: false,
serializer: Customizer.MyRESTSerializer
})
})
Request For Feedback
- Is this the right path?
- Is the ember team actively working on a better way to do this?
Check out the embedded-records branch of ember-data:
https://github.com/emberjs/data/commits/embedded-records
In particular, see this commit for saving embedded data:
https://github.com/emberjs/data/commit/0abd3b965c50dfeb23bd8ff50751825482050e68
Full support for embedded records is right around the corner 🙂
Update
Embedded records branch was merged into master on 12/28/2012:
https://github.com/emberjs/data/commit/b5d7c478e79aa9706e0196b8769b7ef67bb26fc4