Skip to content

Instantly share code, notes, and snippets.

@runspired
Last active January 1, 2016 08:09
Show Gist options
  • Save runspired/8116167 to your computer and use it in GitHub Desktop.
Save runspired/8116167 to your computer and use it in GitHub Desktop.
A method of saving / updating embedded records for ember.js and ember-data. This is probably NOT the best method, it's simply the best method I've come up with currently "as an adapter". It relies on calls to several store methods that should be private, and essentially turns a single save request into a save queue. This code could be altered to…
var DSA = {};
DSA.RESTAdapter = DS.RESTAdapter.extend({
/*
@private
Saves a newly created record, traverses and saves dirty related records
Currently Only belongsTo relationships are traversed
*/
saveRecord : function(_store,type,record,initiator) {
var _this = this ,
//save a single record, bind to parent following
save = function(store, recordMap ) {
//add parent ID if necessary
if( !!recordMap.parent ) {
var parentKey = getParentKey( recordMap );
//by setting this end of the belongsTo we set both ends of the belongsTo
recordMap.record.set( parentKey , recordMap.parent );
}
var data = {} ,
serializer = store.serializerFor( recordMap.type.typeKey )
;
serializer.serializeIntoHash(
data,
recordMap.type,
recordMap.record,
{ includeId: true }
);
return _this.ajax(
_this.buildURL(recordMap.type.typeKey) ,
recordMap.method ,
{ data: data }
);
} ,
//given a recordMap returns the record's key for the parent
getParentKey = function( recordMap ) {
var matches = [];
recordMap.record.eachRelationship(function(r,n){
if( n.kind != 'belongsTo' || !n.type || n.type.typeKey != recordMap.parentType.typeKey )
return;
matches.push( n.key );
return;
});
Ember.assert( "belongsTo relationship does not exist on both models "
+ recordMap.parentType.typeKey
+ ' and '
+ recordMap.type.typeKey
, matches.length );
//for now only return one match
// in the future multiple matches need to be handled and the correct inverse determined
return matches[0];
} ,
//store references to records that have been traversed to avoid
// multiple traversals
traversedRecords = [] ,
//iterate 'belongsTo' relationships to find dirty records
getRelatedRecords = function(record , parent ) {
traversedRecords.push(record);
var relatedRecords = [];
//fill the relatedRecords cache with dirty belongsTo records
record.eachRelationship(function(r,n){
//ignore non-belongsTo relationships
if( n.kind != 'belongsTo' || !n.type )
return;
//usually this will be an existing record
var relatedRecord = record.get( n.key );
//but sometimes it will be an ID, if the ID isn't in the store
// we can be certain this record hasn't been altered
if( relatedRecord == parseInt( relatedRecord) && _store.hasRecordForId(n.type.typeKey , relatedRecord) )
relatedRecord = _store.recordForId(n.type.typeKey , relatedRecord);
//we have a record and it isn't the parent
if( !!relatedRecord && (!parent || relatedRecord !== parent) ) {
var isDirty = relatedRecord.get('isDirty') ,
isNew = relatedRecord.get('isNew') ,
isSaving = relatedRecord.get('isSaving')
;
if( (isDirty || isNew) && !isSaving )
relatedRecords.push({
attribute : n.key ,
type : n.type ,
kind : n.kind ,
method : isNew? 'POST': 'PUT' ,
parent : record ,
parentType : n.parentType ,
record : relatedRecord
});
//get related records for this record
if( (initiator == 'update' || (initiator == 'create' && ((isDirty || isNew) && !isSaving))) && traversedRecords.indexOf(relatedRecord) === -1 ) {
relatedRecords.concat( getRelatedRecords( relatedRecord, record ) );
}
}
return;
});
return relatedRecords;
} ,
originalRecordMap = {
attribute : null ,
type : type ,
kind : null ,
method : (initiator == 'create')? 'POST' : 'PUT' ,
parent : null ,
record : record
} ,
promiseChain = getRelatedRecords( record ) ,
errorSavingChain = function(e) {
Ember.Logger.debug('error saving all promises, currently this means only the top most record may have saved and callbacks will still be triggered');
return finalCleanup();
} ,
chainLink = function(previous, recordMap) {
recordMap.record.adapterWillCommit();
recordMap.record._inFlightAttributes = recordMap.record._attributes;
recordMap.record._attributes = {};
return previous
.then( function(){ return save( _store , recordMap ); } )
.then(
function(saveResult){
var serializer = _store.serializerFor( recordMap.type.typeKey ) ,
serialized = serializer.extract(_store,recordMap.type, saveResult , Ember.get( recordMap.record , 'id' ) , (recordMap.method == 'POST' ? 'createRecord' : 'updateRecord') );
_store.didSaveRecord( recordMap.record , serialized );
_store.updateId( recordMap.record , serialized );
recordMap.record.adapterDidCommit( serialized );
} ,
errorSavingChain
);
} ,
//returned data
initialSaveData ,
//we may want to hook in here and do some stuff later on
finalCleanup = function(){
return new Ember.RSVP.Promise(
function(resolve, reject) {
Ember.run(null, resolve, initialSaveData);
});
} ,
initialLink = save( _store , originalRecordMap )
.then(
function(saveResult){
//set initial data
initialSaveData = saveResult
var serializer = _store.serializerFor( originalRecordMap.type.typeKey )
, serialized = serializer.extract(
_store,
originalRecordMap.type,
saveResult ,
Ember.get( originalRecordMap.record , 'id' ) ,
(originalRecordMap.method == 'POST' ? 'createRecord' : 'updateRecord')
);
_store.didSaveRecord( originalRecordMap.record , serialized );
_store.updateId( originalRecordMap.record , serialized );
originalRecordMap.record.adapterDidCommit( serialized );
} ,
function(e){
Ember.Logger.debug('an error occurred during the initial save',e);
return e;
}
) ,
chain = promiseChain.reduce( chainLink , initialLink )
;
return chain.then( finalCleanup );
}
, createRecord : function(_store,type,record){ return this.saveRecord(_store,type,record,'create'); }
, updateRecord : function(_store,type,record){ return this.saveRecord(_store,type,record,'update'); }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment