diff --git a/CHANGELOG.md b/CHANGELOG.md index a44a2bb4..5b46519d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # EmberFire Changelog +### EmberFire 1.0.5 (April 17, 2014) + +* Adding/removing objects to a `hasMany` array now persists to Firebase after `save()` +* Better error handling +* Registed EmberFire with Ember.libraries +* Removed `_enqueue` in `extractSingle` to prevent race condition with embedded records +* `find()` calls `store.dematerializeRecord()` if the record can't be found + ### EmberFire 1.0.4 (April 4, 2014) * _saveHasManyRelationshipRecord bug fix diff --git a/bower.json b/bower.json index 1ae5e996..f35013e3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "emberfire", - "version": "1.0.4", + "version": "1.0.5", "description": "Firebase bindings for Ember Data", "main": ["./dist/emberfire.js"], "dependencies": { diff --git a/dist/emberfire.js b/dist/emberfire.js index 9b8052fe..2cc6caa5 100644 --- a/dist/emberfire.js +++ b/dist/emberfire.js @@ -6,8 +6,16 @@ return; } - var Promise = Ember.RSVP.Promise; + var EmberFire = Ember.Namespace.create({ + VERSION: '1.0.5' + }); + if (Ember.libraries) { + Ember.libraries.registerCoreLibrary('EmberFire', EmberFire.VERSION); + } + + // Shortcuts + var Promise = Ember.RSVP.Promise; var map = Ember.EnumerableUtils.map; var forEach = Ember.EnumerableUtils.forEach; var fmt = Ember.String.fmt; @@ -51,7 +59,7 @@ var normalizedPayload = this.normalize(type, payload); // Check for embedded records type.eachRelationship(function(key, relationship) { - if (!Ember.isNone(payload[key]) && relationship.options.embedded === true) { + if (!Ember.isNone(payload) && !Ember.isNone(payload[key]) && relationship.options.embedded === true) { var embeddedKey; var embeddedRecordPayload = normalizedPayload[key]; var records = []; @@ -64,14 +72,8 @@ records.push(record); } normalizedPayload[key] = Ember.keys(normalizedPayload[key]); - if (adapter._enqueue) { - adapter._enqueue(function() { - store.pushMany(relationship.type, records); - }); - } - else { - store.pushMany(relationship.type, records); - } + // Push the embedded records into the store + store.pushMany(relationship.type, records); } }); return normalizedPayload; @@ -129,6 +131,8 @@ this._ref = this.firebase.ref(); // Keep track of what types `.findAll()` has been called for this._findAllMapForType = {}; + // Keep a cache to check modified relationships against + this._recordCacheForType = {}; // Used to batch records into the store this._queue = []; }, @@ -174,11 +178,18 @@ var payload = adapter._assignIdToPayload(snapshot); var record = store.getById(type, snapshot.name()); + adapter._updateRecordCacheForType(type, payload); + if (!resolved) { resolved = true; // If this is the first event, resolve the promise. if (payload === null) { - adapter._enqueue(reject, [{ message: fmt('no record was found at %@', [ref.toString()]), recordId: id }]); + if (store.hasRecordForId(type, id)) { + store.dematerializeRecord(record); + } + var error = new Error(fmt('no record was found at %@', [ref.toString()])); + error.recordId = id; + adapter._enqueue(reject, [error]); } else { adapter._enqueue(resolve, [payload]); @@ -192,7 +203,7 @@ }); } // Otherwise push it into the store - else { + else if (payload !== null) { adapter._enqueue(function() { store.push(type, serializer.extractSingle(store, type, payload)); }); @@ -220,10 +231,9 @@ promises = Ember.A(promises); forEach(promises.filterBy('state', 'rejected'), function(promise) { var recordId = promise.reason.recordId; - if(store.hasRecordForId(type, recordId)) { + if (store.hasRecordForId(type, recordId)) { var record = store.getById(type, recordId); - record.transitionTo('loaded.created.uncommitted'); - store.deleteRecord(record); + store.dematerializeRecord(record); } }); return Ember.A(promises.filterBy('state', 'fulfilled')).mapBy('value'); @@ -250,20 +260,23 @@ valueEventTriggered = adapter._findAllAddEventListeners(store, type, ref); } ref.once('value', function(snapshot) { + var results = []; if (valueEventTriggered) { Ember.run(null, valueEventTriggered.resolve); } if (snapshot.val() === null) { - adapter._enqueue(reject); + adapter._enqueue(resolve, [results]); } else { - var results = []; snapshot.forEach(function(childSnapshot) { var payload = adapter._assignIdToPayload(childSnapshot); + //adapter._updateRecordCacheForType(type, payload); results.push(payload); }); adapter._enqueue(resolve, [results]); } + }, function(error) { + adapter._enqueue(reject, [error]); }); }, fmt('DS: FirebaseAdapter#findAll %@ to %@', [type, ref.toString()])); }, @@ -349,10 +362,9 @@ */ updateRecord: function(store, type, record) { var adapter = this; - var serializedRecord = record.serialize({ - includeId: false - }); + var serializedRecord = this._getSerializedRecord(record); var recordRef = this._getRef(type, record.id); + var recordCache = Ember.get(adapter._recordCacheForType, fmt('%@.%@', [type.typeKey, record.get('id')])) || {}; return new Promise(function(resolve, reject) { var savedRelationships = Ember.A(); @@ -360,7 +372,7 @@ switch (relationship.kind) { case 'hasMany': if (Ember.isArray(serializedRecord[key])) { - var save = adapter._saveHasManyRelationship(store, relationship, serializedRecord[key], recordRef); + var save = adapter._saveHasManyRelationship(store, type, relationship, serializedRecord[key], recordRef, recordCache); savedRelationships.push(save); // Remove the relationship from the serializedRecord delete serializedRecord[key]; @@ -391,22 +403,52 @@ }, fmt('DS: FirebaseAdapter#updateRecord %@ to %@', [type, recordRef.toString()])); }, + /** + Return a serialized version of the record + */ + _getSerializedRecord: function(record) { + return record.serialize({ + includeId: false + }); + }, + /** Call _saveHasManyRelationshipRecord on each record in the relationship and then resolve once they have all settled */ - _saveHasManyRelationship: function(store, relationship, ids, parentRef) { + _saveHasManyRelationship: function(store, type, relationship, ids, recordRef, recordCache) { if (!Ember.isArray(ids)) { throw new Error('hasMany relationships must must be an array'); } - // Save each record in the relationship - var savedRecords = map(ids, function(id) { - return this._saveHasManyRelationshipRecord(store, relationship, parentRef, id); - }, this); + var adapter = this; + var idsCache = Ember.A(recordCache[relationship.key]); + ids = Ember.A(ids); + // Added + var addedRecords = ids.filter(function(id) { + return !idsCache.contains(id); + }); + // Dirty + var dirtyRecords = ids.filter(function(id) { + var type = relationship.type; + return store.hasRecordForId(type, id) && store.getById(type, id).get('isDirty') === true; + }); + dirtyRecords = Ember.A(dirtyRecords.concat(addedRecords)).uniq().map(function(id) { + return adapter._saveHasManyRecord(store, relationship, recordRef, id); + }); + // Removed + var removedRecords = idsCache.filter(function(id) { + return !ids.contains(id); + }).map(function(id) { + return adapter._removeHasManyRecord(store, relationship, recordRef, id); + }); + // Combine all the saved records + var savedRecords = dirtyRecords.concat(removedRecords); // Wait for all the updates to finish return Ember.RSVP.allSettled(savedRecords).then(function(savedRecords) { var rejected = Ember.A(Ember.A(savedRecords).filterBy('state', 'rejected')); if (rejected.get('length') === 0) { + // Update the cache + recordCache[relationship.key] = ids; return savedRecords; } else { @@ -419,57 +461,65 @@ /** If the relationship is `async: true`, create a child ref - named with the record id and set the value to false + named with the record id and set the value to true If the relationship is `embedded: true`, create a child ref named with the record id and update the value to the serialized version of the record */ - _saveHasManyRelationshipRecord: function(store, relationship, parentRef, id) { + _saveHasManyRecord: function(store, relationship, parentRef, id) { var adapter = this; - // Create a reference to the related record var ref = this._getRelationshipRef(parentRef, relationship.key, id); - // Get the local version of the related record - var relatedRecord = store.hasRecordForId(relationship.type, id) ? store.getById(relationship.type, id) : false; + var record = store.getById(relationship.type, id); var isEmbedded = relationship.options.embedded === true; - var isDirty = relatedRecord ? relatedRecord.get('isDirty') : false; - var valueToSave = isEmbedded ? relatedRecord.serialize({ includeId: false }) : true; + var valueToSave = isEmbedded ? record.serialize({ includeId: false }) : true; return new Promise(function(resolve, reject) { - // If the relationship is embedded and a record was found and the and there are changes - // If the relationship is embedded and a related record was found and its dirty or there is no related record - // TODO: use a state machine to manager these conditionals - if ((isEmbedded && relatedRecord && isDirty) || (!isEmbedded && ((relatedRecord && isDirty) || !relatedRecord))) { - var _saveHandler = function(error) { - if (error) { - if (typeof error === 'object') { - error.location = ref.toString(); - } - adapter._enqueue(reject, [error]); - } else { - adapter._enqueue(resolve); + var _saveHandler = function(error) { + if (error) { + if (typeof error === 'object') { + error.location = ref.toString(); } - }; - if (isEmbedded) { - ref.update(valueToSave, _saveHandler); - } - else { - ref.set(valueToSave, _saveHandler); + adapter._enqueue(reject, [error]); + } else { + adapter._enqueue(resolve); } + }; + if (isEmbedded) { + ref.update(valueToSave, _saveHandler); } else { - // The related record didn't need to be save - adapter._enqueue(resolve); + ref.set(valueToSave, _saveHandler); } }); }, + /** + Remove a relationship + */ + _removeHasManyRecord: function(store, relationship, parentRef, id) { + var adapter = this; + var ref = this._getRelationshipRef(parentRef, relationship.key, id); + return new Promise(function(resolve, reject) { + var _removeHandler = function(error) { + if (error) { + if (typeof error === 'object') { + error.location = ref.toString(); + } + adapter._enqueue(reject, [error]); + } else { + adapter._enqueue(resolve); + } + }; + ref.remove(_removeHandler); + }); + }, + /** Called by the store when a record is deleted. */ deleteRecord: function(store, type, record) { var adapter = this; - var ref = this._getRef(type, record.id); - + var ref = this._getRef(type, record.get('id')); return new Promise(function(resolve, reject) { ref.remove(function(err) { if (err) { @@ -543,6 +593,32 @@ if (length === 1) { this._queueScheduleFlush(); } + }, + + /** + A cache of hasMany relationships that can be used to + diff against new relationships when a model is saved + */ + _recordCacheForType: undefined, + + /** + _updateHasManyCacheForType + */ + _updateRecordCacheForType: function(type, payload) { + if (!payload) { return; } + var adapter = this; + var id = payload.id; + var cache = adapter._recordCacheForType; + var typeKey = type.typeKey; + // Only cache relationships for now + type.eachRelationship(function(key, relationship) { + if (relationship.kind === 'hasMany') { + var ids = payload[key]; + cache[typeKey] = cache[typeKey] || {}; + cache[typeKey][id] = cache[typeKey][id] || {}; + cache[typeKey][id][key] = !Ember.isNone(ids) ? Ember.A(Ember.keys(ids)) : Ember.A(); + } + }); } }); diff --git a/dist/emberfire.min.js b/dist/emberfire.min.js index eb89346f..f74d21cb 100644 --- a/dist/emberfire.min.js +++ b/dist/emberfire.min.js @@ -1 +1 @@ -!function(){"use strict";if(void 0!==window.DS){var a=Ember.RSVP.Promise,b=Ember.EnumerableUtils.map,c=Ember.EnumerableUtils.forEach,d=Ember.String.fmt;DS.FirebaseSerializer=DS.JSONSerializer.extend(Ember.Evented,{normalize:function(a,b){return a.eachRelationship(function(c,e){if("hasMany"===e.kind)if("object"!=typeof b[c]||Ember.isArray(b[c])||e.options.embedded===!0){if(Ember.isArray(b[c]))throw new Error(d('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }',[a.toString(),e.kind,e.type.typeKey,e.key,e.type.typeKey]))}else b[c]=Ember.keys(b[c])}),this._super.apply(this,arguments)},extractSingle:function(a,b,c){var d=a.adapterFor(b),e=this.normalize(b,c);return b.eachRelationship(function(b,f){if(!Ember.isNone(c[b])&&f.options.embedded===!0){var g,h,i=e[b],j=[];for(g in i)h=i[g],null!==h&&"object"==typeof h&&(h.id=g),j.push(h);e[b]=Ember.keys(e[b]),d._enqueue?d._enqueue(function(){a.pushMany(f.type,j)}):a.pushMany(f.type,j)}}),e},extractArray:function(a,c,d){return b(d,function(b){return this.extractSingle(a,c,b)},this)}}),DS.FirebaseAdapter=DS.Adapter.extend(Ember.Evented,{defaultSerializer:"-firebase",init:function(){if(!this.firebase||"object"!=typeof this.firebase)throw new Error("Please set the `firebase` property on the adapter.");this._ref=this.firebase.ref(),this._findAllMapForType={},this._queue=[]},generateIdForRecord:function(){return this._ref.push().name()},_assignIdToPayload:function(a){var b=a.val();return null!==b&&"object"==typeof b&&"undefined"==typeof b.id&&(b.id=a.name()),b},find:function(b,c,e){var f=this,g=!1,h=this._getRef(c,e),i=b.serializerFor(c);return new a(function(a,j){h.on("value",function(k){var l=f._assignIdToPayload(k),m=b.getById(c,k.name());g?f._enqueue(null===l&&m&&!m.get("isDeleted")?function(){b.getById(c,k.name()).deleteRecord()}:function(){b.push(c,i.extractSingle(b,c,l))}):(g=!0,null===l?f._enqueue(j,[{message:d("no record was found at %@",[h.toString()]),recordId:e}]):f._enqueue(a,[l]))},function(a){g||f._enqueue(j,[a])})},d("DS: FirebaseAdapter#find %@ to %@",[c,h.toString()]))},findMany:function(a,d,e){var f=b(e,function(b){return this.find(a,d,b)},this);return Ember.RSVP.allSettled(f).then(function(b){return b=Ember.A(b),c(b.filterBy("state","rejected"),function(b){var c=b.reason.recordId;if(a.hasRecordForId(d,c)){var e=a.getById(d,c);e.transitionTo("loaded.created.uncommitted"),a.deleteRecord(e)}}),Ember.A(b.filterBy("state","fulfilled")).mapBy("value")})},findAll:function(b,c){var e=this,f=this._getRef(c);return new a(function(a,d){var g;e._findAllHasEventsForType(c)||(g=e._findAllAddEventListeners(b,c,f)),f.once("value",function(b){if(g&&Ember.run(null,g.resolve),null===b.val())e._enqueue(d);else{var c=[];b.forEach(function(a){var b=e._assignIdToPayload(a);c.push(b)}),e._enqueue(a,[c])}})},d("DS: FirebaseAdapter#findAll %@ to %@",[c,f.toString()]))},_findAllMapForType:void 0,_findAllHasEventsForType:function(a){return!Ember.isNone(this._findAllMapForType[a])},_findAllAddEventListeners:function(a,b,c){this._findAllMapForType[b]=!0;var d=Ember.RSVP.defer(),e=this,f=a.serializerFor(b),g=!1;return d.promise.then(function(){g=!0}),c.on("child_added",function(c){g&&e._handleChildValue(a,b,f,c)}),c.on("child_changed",function(c){g&&e._handleChildValue(a,b,f,c)}),c.on("child_removed",function(c){if(g){var d=a.getById(b,c.name());d&&!d.get("isDeleted")&&e._enqueue(function(){a.deleteRecord(d)})}}),d},_handleChildValue:function(a,b,c,d){var e=this._assignIdToPayload(d);this._enqueue(function(){a.push(b,c.extractSingle(a,b,e))})},createRecord:function(a,b,c){return this.updateRecord(a,b,c)},updateRecord:function(b,c,e){var f=this,g=e.serialize({includeId:!1}),h=this._getRef(c,e.id);return new a(function(a,i){var j=Ember.A();e.eachRelationship(function(a,c){switch(c.kind){case"hasMany":if(Ember.isArray(g[a])){var d=f._saveHasManyRelationship(b,c,g[a],h);j.push(d),delete g[a]}}}),Ember.RSVP.allSettled(j).then(function(b){b=Ember.A(b);var j=Ember.A(b.filterBy("state","rejected"));if(0!==j.get("length")){var k=new Error(d("Some errors were encountered while saving %@ %@",[c,e.id]));k.errors=j.mapBy("reason"),f._enqueue(i,[k])}h.update(g,function(b){b?f._enqueue(i,[b]):f._enqueue(a)})})},d("DS: FirebaseAdapter#updateRecord %@ to %@",[c,h.toString()]))},_saveHasManyRelationship:function(a,c,e,f){if(!Ember.isArray(e))throw new Error("hasMany relationships must must be an array");var g=b(e,function(b){return this._saveHasManyRelationshipRecord(a,c,f,b)},this);return Ember.RSVP.allSettled(g).then(function(a){var b=Ember.A(Ember.A(a).filterBy("state","rejected"));if(0===b.get("length"))return a;var e=new Error(d("Some errors were encountered while saving a hasMany relationship %@ -> %@",[c.parentType,c.type]));throw e.errors=Ember.A(b).mapBy("reason"),e})},_saveHasManyRelationshipRecord:function(b,c,d,e){var f=this,g=this._getRelationshipRef(d,c.key,e),h=b.hasRecordForId(c.type,e)?b.getById(c.type,e):!1,i=c.options.embedded===!0,j=h?h.get("isDirty"):!1,k=i?h.serialize({includeId:!1}):!0;return new a(function(a,b){if(i&&h&&j||!i&&(h&&j||!h)){var c=function(c){c?("object"==typeof c&&(c.location=g.toString()),f._enqueue(b,[c])):f._enqueue(a)};i?g.update(k,c):g.set(k,c)}else f._enqueue(a)})},deleteRecord:function(b,c,e){var f=this,g=this._getRef(c,e.id);return new a(function(a,b){g.remove(function(c){c?f._enqueue(b,[c]):f._enqueue(a)})},d("DS: FirebaseAdapter#deleteRecord %@ to %@",[c,g.toString()]))},pathForType:function(a){var b=Ember.String.camelize(a);return Ember.String.pluralize(b)},_getRef:function(a,b){var c=this._ref;return a&&(c=c.child(this.pathForType(a.typeKey))),b&&(c=c.child(b)),c},_getRelationshipRef:function(a,b,c){return a.child(b).child(c)},_queueFlushDelay:1e3/60,_queueScheduleFlush:function(){Ember.run.later(this,this._queueFlush,this._queueFlushDelay)},_queueFlush:function(){c(this._queue,function(a){var b=a[0],c=a[1];b.apply(null,c)}),this._queue.length=0},_enqueue:function(a,b){var c=this._queue.push([a,b]);1===c&&this._queueScheduleFlush()}}),Ember.onLoad("Ember.Application",function(a){a.initializer({name:"firebase",after:"store",initialize:function(a,b){b.register("adapter:-firebase",DS.FirebaseAdapter),b.register("serializer:-firebase",DS.FirebaseSerializer)}})})}}(); \ No newline at end of file +!function(){"use strict";if(void 0!==window.DS){var a=Ember.Namespace.create({VERSION:"1.0.5"});Ember.libraries&&Ember.libraries.registerCoreLibrary("EmberFire",a.VERSION);var b=Ember.RSVP.Promise,c=Ember.EnumerableUtils.map,d=Ember.EnumerableUtils.forEach,e=Ember.String.fmt;DS.FirebaseSerializer=DS.JSONSerializer.extend(Ember.Evented,{normalize:function(a,b){return a.eachRelationship(function(c,d){if("hasMany"===d.kind)if("object"!=typeof b[c]||Ember.isArray(b[c])||d.options.embedded===!0){if(Ember.isArray(b[c]))throw new Error(e('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }',[a.toString(),d.kind,d.type.typeKey,d.key,d.type.typeKey]))}else b[c]=Ember.keys(b[c])}),this._super.apply(this,arguments)},extractSingle:function(a,b,c){var d=(a.adapterFor(b),this.normalize(b,c));return b.eachRelationship(function(b,e){if(!Ember.isNone(c)&&!Ember.isNone(c[b])&&e.options.embedded===!0){var f,g,h=d[b],i=[];for(f in h)g=h[f],null!==g&&"object"==typeof g&&(g.id=f),i.push(g);d[b]=Ember.keys(d[b]),a.pushMany(e.type,i)}}),d},extractArray:function(a,b,d){return c(d,function(c){return this.extractSingle(a,b,c)},this)}}),DS.FirebaseAdapter=DS.Adapter.extend(Ember.Evented,{defaultSerializer:"-firebase",init:function(){if(!this.firebase||"object"!=typeof this.firebase)throw new Error("Please set the `firebase` property on the adapter.");this._ref=this.firebase.ref(),this._findAllMapForType={},this._recordCacheForType={},this._queue=[]},generateIdForRecord:function(){return this._ref.push().name()},_assignIdToPayload:function(a){var b=a.val();return null!==b&&"object"==typeof b&&"undefined"==typeof b.id&&(b.id=a.name()),b},find:function(a,c,d){var f=this,g=!1,h=this._getRef(c,d),i=a.serializerFor(c);return new b(function(b,j){h.on("value",function(k){var l=f._assignIdToPayload(k),m=a.getById(c,k.name());if(f._updateRecordCacheForType(c,l),g)null===l&&m&&!m.get("isDeleted")?f._enqueue(function(){a.getById(c,k.name()).deleteRecord()}):null!==l&&f._enqueue(function(){a.push(c,i.extractSingle(a,c,l))});else if(g=!0,null===l){a.hasRecordForId(c,d)&&a.dematerializeRecord(m);var n=new Error(e("no record was found at %@",[h.toString()]));n.recordId=d,f._enqueue(j,[n])}else f._enqueue(b,[l])},function(a){g||f._enqueue(j,[a])})},e("DS: FirebaseAdapter#find %@ to %@",[c,h.toString()]))},findMany:function(a,b,e){var f=c(e,function(c){return this.find(a,b,c)},this);return Ember.RSVP.allSettled(f).then(function(c){return c=Ember.A(c),d(c.filterBy("state","rejected"),function(c){var d=c.reason.recordId;if(a.hasRecordForId(b,d)){var e=a.getById(b,d);a.dematerializeRecord(e)}}),Ember.A(c.filterBy("state","fulfilled")).mapBy("value")})},findAll:function(a,c){var d=this,f=this._getRef(c);return new b(function(b,e){var g;d._findAllHasEventsForType(c)||(g=d._findAllAddEventListeners(a,c,f)),f.once("value",function(a){var c=[];g&&Ember.run(null,g.resolve),null===a.val()?d._enqueue(b,[c]):(a.forEach(function(a){var b=d._assignIdToPayload(a);c.push(b)}),d._enqueue(b,[c]))},function(a){d._enqueue(e,[a])})},e("DS: FirebaseAdapter#findAll %@ to %@",[c,f.toString()]))},_findAllMapForType:void 0,_findAllHasEventsForType:function(a){return!Ember.isNone(this._findAllMapForType[a])},_findAllAddEventListeners:function(a,b,c){this._findAllMapForType[b]=!0;var d=Ember.RSVP.defer(),e=this,f=a.serializerFor(b),g=!1;return d.promise.then(function(){g=!0}),c.on("child_added",function(c){g&&e._handleChildValue(a,b,f,c)}),c.on("child_changed",function(c){g&&e._handleChildValue(a,b,f,c)}),c.on("child_removed",function(c){if(g){var d=a.getById(b,c.name());d&&!d.get("isDeleted")&&e._enqueue(function(){a.deleteRecord(d)})}}),d},_handleChildValue:function(a,b,c,d){var e=this._assignIdToPayload(d);this._enqueue(function(){a.push(b,c.extractSingle(a,b,e))})},createRecord:function(a,b,c){return this.updateRecord(a,b,c)},updateRecord:function(a,c,d){var f=this,g=this._getSerializedRecord(d),h=this._getRef(c,d.id),i=Ember.get(f._recordCacheForType,e("%@.%@",[c.typeKey,d.get("id")]))||{};return new b(function(b,j){var k=Ember.A();d.eachRelationship(function(b,d){switch(d.kind){case"hasMany":if(Ember.isArray(g[b])){var e=f._saveHasManyRelationship(a,c,d,g[b],h,i);k.push(e),delete g[b]}}}),Ember.RSVP.allSettled(k).then(function(a){a=Ember.A(a);var i=Ember.A(a.filterBy("state","rejected"));if(0!==i.get("length")){var k=new Error(e("Some errors were encountered while saving %@ %@",[c,d.id]));k.errors=i.mapBy("reason"),f._enqueue(j,[k])}h.update(g,function(a){a?f._enqueue(j,[a]):f._enqueue(b)})})},e("DS: FirebaseAdapter#updateRecord %@ to %@",[c,h.toString()]))},_getSerializedRecord:function(a){return a.serialize({includeId:!1})},_saveHasManyRelationship:function(a,b,c,d,f,g){if(!Ember.isArray(d))throw new Error("hasMany relationships must must be an array");var h=this,i=Ember.A(g[c.key]);d=Ember.A(d);var j=d.filter(function(a){return!i.contains(a)}),k=d.filter(function(b){var d=c.type;return a.hasRecordForId(d,b)&&a.getById(d,b).get("isDirty")===!0});k=Ember.A(k.concat(j)).uniq().map(function(b){return h._saveHasManyRecord(a,c,f,b)});var l=i.filter(function(a){return!d.contains(a)}).map(function(b){return h._removeHasManyRecord(a,c,f,b)}),m=k.concat(l);return Ember.RSVP.allSettled(m).then(function(a){var b=Ember.A(Ember.A(a).filterBy("state","rejected"));if(0===b.get("length"))return g[c.key]=d,a;var f=new Error(e("Some errors were encountered while saving a hasMany relationship %@ -> %@",[c.parentType,c.type]));throw f.errors=Ember.A(b).mapBy("reason"),f})},_saveHasManyRecord:function(a,c,d,e){var f=this,g=this._getRelationshipRef(d,c.key,e),h=a.getById(c.type,e),i=c.options.embedded===!0,j=i?h.serialize({includeId:!1}):!0;return new b(function(a,b){var c=function(c){c?("object"==typeof c&&(c.location=g.toString()),f._enqueue(b,[c])):f._enqueue(a)};i?g.update(j,c):g.set(j,c)})},_removeHasManyRecord:function(a,c,d,e){var f=this,g=this._getRelationshipRef(d,c.key,e);return new b(function(a,b){var c=function(c){c?("object"==typeof c&&(c.location=g.toString()),f._enqueue(b,[c])):f._enqueue(a)};g.remove(c)})},deleteRecord:function(a,c,d){var f=this,g=this._getRef(c,d.get("id"));return new b(function(a,b){g.remove(function(c){c?f._enqueue(b,[c]):f._enqueue(a)})},e("DS: FirebaseAdapter#deleteRecord %@ to %@",[c,g.toString()]))},pathForType:function(a){var b=Ember.String.camelize(a);return Ember.String.pluralize(b)},_getRef:function(a,b){var c=this._ref;return a&&(c=c.child(this.pathForType(a.typeKey))),b&&(c=c.child(b)),c},_getRelationshipRef:function(a,b,c){return a.child(b).child(c)},_queueFlushDelay:1e3/60,_queueScheduleFlush:function(){Ember.run.later(this,this._queueFlush,this._queueFlushDelay)},_queueFlush:function(){d(this._queue,function(a){var b=a[0],c=a[1];b.apply(null,c)}),this._queue.length=0},_enqueue:function(a,b){var c=this._queue.push([a,b]);1===c&&this._queueScheduleFlush()},_recordCacheForType:void 0,_updateRecordCacheForType:function(a,b){if(b){var c=this,d=b.id,e=c._recordCacheForType,f=a.typeKey;a.eachRelationship(function(a,c){if("hasMany"===c.kind){var g=b[a];e[f]=e[f]||{},e[f][d]=e[f][d]||{},e[f][d][a]=Ember.isNone(g)?Ember.A():Ember.A(Ember.keys(g))}})}}}),Ember.onLoad("Ember.Application",function(a){a.initializer({name:"firebase",after:"store",initialize:function(a,b){b.register("adapter:-firebase",DS.FirebaseAdapter),b.register("serializer:-firebase",DS.FirebaseSerializer)}})})}}(); \ No newline at end of file diff --git a/package.json b/package.json index 6220a2e7..f1aebd89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "EmberFire", - "version": "1.0.4", + "version": "1.0.5", "description": "An officially supported Ember binding for Firebase.", "main": "dist/emberfire.js", "repository": { diff --git a/src/data.js b/src/data.js index 044060d0..2cc6caa5 100644 --- a/src/data.js +++ b/src/data.js @@ -7,7 +7,7 @@ } var EmberFire = Ember.Namespace.create({ - VERSION: '1.0.4' + VERSION: '1.0.5' }); if (Ember.libraries) { diff --git a/test/index.html b/test/index.html index e2055214..cf9cd7fe 100644 --- a/test/index.html +++ b/test/index.html @@ -22,7 +22,7 @@ - +