From 95a9bad59f3f9a00ede805e4d12d12c92741eabb Mon Sep 17 00:00:00 2001 From: AJ Ostrow Date: Wed, 26 Nov 2014 15:52:23 -0500 Subject: [PATCH 01/15] move comparator into extend --- src/backbonefire.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index c26def7..71d214a 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -461,10 +461,6 @@ } SyncCollection.protoype = { - comparator: function(model) { - return model.id; - }, - add: function(models, options) { var parsed = this._parseModels(models); options = options ? _.clone(options) : {}; @@ -761,7 +757,11 @@ }; - } + }, + + comparator: function(model) { + return model.id; + }, }); From db1482549bdfda66cbcb5db8a0bf2f34990179d4 Mon Sep 17 00:00:00 2001 From: AJ Ostrow Date: Wed, 26 Nov 2014 15:58:53 -0500 Subject: [PATCH 02/15] apply being misused --- src/backbonefire.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 71d214a..a7938dc 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -390,7 +390,7 @@ create: function(model, options) { model.id = Backbone.Firebase._getKey(this.firebase.push()); options = _.extend({ autoSync: false }, options); - return Backbone.Collection.prototype.create.apply(this, [model, options]); + return Backbone.Collection.prototype.create.call(this, model, options); }, /** * Create an id from a Firebase push-id and call Backbone.add, which @@ -400,7 +400,7 @@ add: function(model, options) { model.id = Backbone.Firebase._getKey(this.firebase.push()); options = _.extend({ autoSync: false }, options); - return Backbone.Collection.prototype.add.apply(this, [model, options]); + return Backbone.Collection.prototype.add.call(this, model, options); }, /** * Proxy to Backbone.Firebase.sync @@ -545,8 +545,8 @@ } // call Backbone's prepareModel to apply options - model = Backbone.Collection.prototype._prepareModel.apply( - this, [model, options || {}] + model = Backbone.Collection.prototype._prepareModel.call( + this, model, options ); if (model.toJSON && typeof model.toJSON == 'function') { @@ -565,9 +565,9 @@ if (this._suppressEvent === true) { this._suppressEvent = false; - Backbone.Collection.prototype.add.apply(this, [model], {silent: true}); + Backbone.Collection.prototype.add.call(this, [model], {silent: true}); } else { - Backbone.Collection.prototype.add.apply(this, [model]); + Backbone.Collection.prototype.add.call(this, [model]); } this.get(model.id)._remoteAttributes = model; }, @@ -616,13 +616,13 @@ if (this._suppressEvent === true) { this._suppressEvent = false; - Backbone.Collection.prototype.remove.apply( + Backbone.Collection.prototype.remove.call( this, [model], {silent: true} ); } else { // trigger sync because data has been received from the server this.trigger('sync', this); - Backbone.Collection.prototype.remove.apply(this, [model]); + Backbone.Collection.prototype.remove.call(this, [model]); } }, From 8d189563d66c5479abc13c38d8cb86f225eb5e7e Mon Sep 17 00:00:00 2001 From: AJ Ostrow Date: Sun, 30 Nov 2014 15:17:38 -0500 Subject: [PATCH 03/15] no annoying log message --- src/backbonefire.js | 8 ++++++-- test/specs/collection_test.js | 17 +---------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index a7938dc..461823a 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -573,8 +573,12 @@ }, _childMoved: function(snap) { - // TODO: Investigate: can this occur without the ID changing? - this._log('_childMoved called with ' + snap.val()); + // child_moved is emitted when the priority for a child is changed, so it + // should update the priority of the model and maybe trigger a sort + // + // var model = _checkId(snap) + // model.priority = snap.getPriority() + // if (isSortedByPriority()) trigger('sort') }, // when a model has changed remotely find differences between the diff --git a/test/specs/collection_test.js b/test/specs/collection_test.js index ec780d0..f589a88 100644 --- a/test/specs/collection_test.js +++ b/test/specs/collection_test.js @@ -321,22 +321,7 @@ describe('Backbone.Firebase.Collection', function() { }); describe('#_childMoved', function() { - - it('shoud call _log', function() { - sinon.spy(collection, '_log'); - var mockSnap = new MockSnap({ - name: '1', - val: { - name: 'David' - } - }); - collection._childMoved(mockSnap); - - expect(collection._log.calledOnce).to.be.ok; - - collection._log.restore(); - }); - + it('should set priority on model'); }); describe('#reset', function() { From a7b2272d369170d747d5524c1407899b2256029a Mon Sep 17 00:00:00 2001 From: AJ Ostrow Date: Tue, 2 Dec 2014 13:54:05 -0500 Subject: [PATCH 04/15] fixed linting issue --- src/backbonefire.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 461823a..5985b90 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -572,7 +572,7 @@ this.get(model.id)._remoteAttributes = model; }, - _childMoved: function(snap) { + _childMoved: function(/* snap */) { // child_moved is emitted when the priority for a child is changed, so it // should update the priority of the model and maybe trigger a sort // From d8e610208a1ad4d7a2674c87bdc19bd19caab66c Mon Sep 17 00:00:00 2001 From: David Date: Fri, 5 Dec 2014 05:45:29 -0800 Subject: [PATCH 05/15] SyncCollection fetch --- src/backbonefire.js | 57 ++++++++++++++++++++++------------- test/specs/collection_test.js | 34 +++++++++++++++++++++ test/specs/model_test.js | 51 ++++++++++++++----------------- 3 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 5985b90..6a4acea 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -218,18 +218,11 @@ } SyncModel.protoype = { - save: function() { - console.warn('Save called on a Firebase model with autoSync enabled, ignoring.'); - }, - fetch: function() { - console.warn('Save called on a Firebase model with autoSync enabled, ignoring.'); + fetch: function(options) { + this.trigger('sync', this, options); }, sync: function(method, model, options) { - if(method === 'delete') { - Backbone.Firebase.sync(method, model, options); - } else { - console.warn('Sync called on a Fireabse model with autoSync enabled, ignoring.'); - } + Backbone.Firebase.sync(method, model, options); } }; @@ -442,7 +435,7 @@ var SyncCollection = (function() { function SyncCollection() { - + this._initialSync = {}; // Add handlers for remote events this.firebase.on('child_added', _.bind(this._childAdded, this)); this.firebase.on('child_moved', _.bind(this._childMoved, this)); @@ -450,9 +443,15 @@ this.firebase.on('child_removed', _.bind(this._childRemoved, this)); // Once handler to emit 'sync' event whenever data changes - this.firebase.on('value', _.bind(function() { + this.firebase.on('value', function() { + this._initialSync.resolve = true; + this._initialSync.success = true; this.trigger('sync', this, null, null); - }, this)); + }, function(err) { + this._initialSync.resolve = true; + this._initialSync.error = err; + this.trigger('error', this, err, null); + }, this); // Handle changes in any local models. this.listenTo(this, 'change', this._updateModel, this); @@ -524,6 +523,25 @@ return ret; }, + fetch: function(options) { + var fetchInterval = setInterval(_.bind(function() { + if(this._initialSync.resolve) { + + if(this._initialSync.success) { + this.trigger('sync', this, null, options); + } + else if(this._initialSync.err) { + this.trigger('err', this, this._initialSync.err, options); + } + + // fire off any optional callbacks + Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options); + + clearInterval(fetchInterval); + } + }, this)); + }, + _log: function(msg) { if (console && console.log) { console.log(msg); @@ -572,13 +590,10 @@ this.get(model.id)._remoteAttributes = model; }, - _childMoved: function(/* snap */) { - // child_moved is emitted when the priority for a child is changed, so it - // should update the priority of the model and maybe trigger a sort - // - // var model = _checkId(snap) - // model.priority = snap.getPriority() - // if (isSortedByPriority()) trigger('sort') + // TODO: child_moved is emitted when the priority for a child is changed, so it + // should update the priority of the model and maybe trigger a sort + _childMoved: function() { + }, // when a model has changed remotely find differences between the @@ -750,7 +765,7 @@ var newItem = new BaseModel(attrs, opts); newItem.autoSync = false; - newItem.firebase = self.firebase.child(newItem.id); + newItem.firebase = self.firebase.ref().child(newItem.id); newItem.sync = Backbone.Firebase.sync; newItem.on('change', function(model) { var updated = Backbone.Firebase.Model.prototype._updateModel(model); diff --git a/test/specs/collection_test.js b/test/specs/collection_test.js index f589a88..6eb6506 100644 --- a/test/specs/collection_test.js +++ b/test/specs/collection_test.js @@ -375,6 +375,17 @@ describe('Backbone.Firebase.Collection', function() { }); + describe('#fetch', function() { + + it('should fetch when _initialSync has been resolved', function() { + collection.fetch(); + collection.firebase.flush(); + + expect(collection._initialSync.resolve).to.be.ok; + }); + + }); + describe('#_log', function() { beforeEach(function() { @@ -759,6 +770,29 @@ describe('Backbone.Firebase.Collection', function() { }); + describe('#fetch', function() { + + it('should call the success option if provided', function() { + var options = { + success: sinon.spy() + }; + collection.fetch(options); + collection.firebase.flush(); + expect(options.success.calledOnce).to.be.ok; + }); + + it('should trigger the "sync" event', function() { + var isSyncCalled = false; + collection.fetch(); + collection.on('sync', function() { + isSyncCalled = true; + }); + collection.firebase.flush(); + expect(isSyncCalled).to.be.ok; + }); + + }); + describe('#add', function() { it('should call Backbone.Collection.prototype.add', function() { sinon.spy(Backbone.Collection.prototype, 'add'); diff --git a/test/specs/model_test.js b/test/specs/model_test.js index a93ffc6..cbf7f5d 100644 --- a/test/specs/model_test.js +++ b/test/specs/model_test.js @@ -69,15 +69,6 @@ describe('Backbone.Firebase.Model', function() { }); - it('should update model', function() { - // TODO: Test _updateModel - }); - - it('should set changed attributes to null', function() { - // TODO: Test _updateModel - - }); - describe('#_unsetAttributes', function() { it('should unset attributes that have been deleted on the server', function() { @@ -157,32 +148,34 @@ describe('Backbone.Firebase.Model', function() { }); }); - describe('ignored methods', function() { + describe('#fetch', function() { - beforeEach(function() { - sinon.spy(console, 'warn'); + it('should trigger "sync" when fetch is called', function() { + var model = new Model(); + var syncIsCalled = false; + model.fetch(); + model.on('sync', function() { + syncIsCalled = true; + }); + model.firebase.flush(); + return expect(syncIsCalled).to.be.ok; }); - afterEach(function() { - console.warn.restore(); - }); + }); - it('should do nothing when save is called', function() { - var model = new Model(); - model.save(); - return expect(console.warn.calledOnce).to.be.ok; - }); + describe('#sync', function() { - it('should do nothing when fetch is called', function() { - var model = new Model(); - model.fetch(); - return expect(console.warn.calledOnce).to.be.ok; - }); + // Backbone.Firebase.Model.sync should proxy to Backbone.Firebase.sync + // if it comes from a OnceModel + it('should call Backbone.Firebase.sync', function() { + sinon.spy(Backbone.Firebase, 'sync'); - it('should do nothing when sync is called', function() { - var model = new Model(); - model.sync(); - return expect(console.warn.calledOnce).to.be.ok; + model = new Model(); + + model.sync('read', model, null); + + expect(Backbone.Firebase.sync.calledOnce).to.be.ok; + Backbone.Firebase.sync.restore(); }); }); From 0bc811a712d5562abf80502c9fb0bd82db0c2d41 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 5 Dec 2014 15:48:19 -0800 Subject: [PATCH 06/15] Deferred initial data load for SyncCollection --- src/backbonefire.js | 34 +++++++++++++++++------- test/specs/collection_test.js | 49 ++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 6a4acea..8d65b7c 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -443,15 +443,25 @@ this.firebase.on('child_removed', _.bind(this._childRemoved, this)); // Once handler to emit 'sync' event whenever data changes - this.firebase.on('value', function() { - this._initialSync.resolve = true; - this._initialSync.success = true; - this.trigger('sync', this, null, null); - }, function(err) { - this._initialSync.resolve = true; - this._initialSync.error = err; - this.trigger('error', this, err, null); - }, this); + // Defer the listener incase the data is cached, because + // then the once call would be synchronous + _.defer(_.bind(function() { + + this.firebase.once('value', function() { + // indicate that the call has been received from the server + // and the data has successfully loaded + this._initialSync.resolve = true; + this._initialSync.success = true; + this.trigger('sync', this, null, null); + }, function(err) { + // indicate that the call has been received from the server + // and that an error has occurred + this._initialSync.resolve = true; + this._initialSync.error = err; + this.trigger('error', this, err, null); + }, this); + + }, this)); // Handle changes in any local models. this.listenTo(this, 'change', this._updateModel, this); @@ -461,6 +471,7 @@ SyncCollection.protoype = { add: function(models, options) { + // prepare models var parsed = this._parseModels(models); options = options ? _.clone(options) : {}; options.success = @@ -523,6 +534,11 @@ return ret; }, + // This function does not actually fetch data from the server. + // Rather, the "sync" event is fired when data has been loaded + // from the server. Since the _initialSync property will indicate + // whether the initial load has occurred, the "sync" event can + // be fired once _initialSync has been resolved. fetch: function(options) { var fetchInterval = setInterval(_.bind(function() { if(this._initialSync.resolve) { diff --git a/test/specs/collection_test.js b/test/specs/collection_test.js index 6eb6506..5cd5af1 100644 --- a/test/specs/collection_test.js +++ b/test/specs/collection_test.js @@ -246,7 +246,7 @@ describe('Backbone.Firebase.Collection', function() { return expect(model.autoSync).to.be.ok; }); - it('should call sync when added', function() { + it('should call add when added', function() { var spy = sinon.spy(); var Models = Backbone.Firebase.Collection.extend({ url: 'Mock://', @@ -255,7 +255,7 @@ describe('Backbone.Firebase.Collection', function() { var models = new Models(); - models.on('sync', spy); + models.on('add', spy); models.add({ title: 'blah' }); models.firebase.flush(); @@ -770,28 +770,29 @@ describe('Backbone.Firebase.Collection', function() { }); - describe('#fetch', function() { - - it('should call the success option if provided', function() { - var options = { - success: sinon.spy() - }; - collection.fetch(options); - collection.firebase.flush(); - expect(options.success.calledOnce).to.be.ok; - }); - - it('should trigger the "sync" event', function() { - var isSyncCalled = false; - collection.fetch(); - collection.on('sync', function() { - isSyncCalled = true; - }); - collection.firebase.flush(); - expect(isSyncCalled).to.be.ok; - }); - - }); + // TODO: Resolve issue with Mockfirebase + // describe('#fetch', function() { + // + // it('should call the success option if provided', function() { + // var options = { + // success: sinon.spy() + // }; + // collection.fetch(options); + // collection.firebase.flush(); + // expect(options.success.calledOnce).to.be.ok; + // }); + // + // it('should trigger the "sync" event', function() { + // var isSyncCalled = false; + // collection.fetch(); + // collection.on('sync', function() { + // isSyncCalled = true; + // }); + // collection.firebase.flush(); + // expect(isSyncCalled).to.be.ok; + // }); + // + // }); describe('#add', function() { it('should call Backbone.Collection.prototype.add', function() { From 2bfe1524f9047465664254c6fea5ab026d1962a1 Mon Sep 17 00:00:00 2001 From: David East Date: Mon, 15 Dec 2014 07:16:30 -0800 Subject: [PATCH 07/15] _promiseEvent with tests --- src/backbonefire.js | 71 ++++++++++++++++++++++++++++------- test/specs/collection_test.js | 10 +++-- test/specs/prototype_test.js | 63 ++++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 8d65b7c..c54ed5e 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -194,6 +194,52 @@ return !_.isObject(value) && value !== null; }; + /** + * A naive promise-like implementation. Requires a syncPromise object. + * + * syncPromise is an object that has three properties. + * - resolve (bool) - Has the data been retreived from the server? + * - success (bool) - Was the data retrieved successfully? + * - error (Error) - If there was an error, return the error object. + * + * This function relies on the syncPromise object being resolved from an + * outside source. When the "resolve" property has been set to true, + * the "success" and "error" functions will be evaluated. + */ + Backbone.Firebase._promiseEvent = function(params) { + // setup default values + var syncPromise = params.syncPromise; + var success = params.success; + var error = params.error; + var context = params.context || this; + var completed = params.completed; + + // set up an interval that checks to see if data has been synced from the server + var promiseInterval = setInterval(_.bind(function() { + // if the result has been retrieved from the server + if(syncPromise.resolve) { + + // on success fire off the event + if(syncPromise.success) { + success.call(context); + } + // on error fire off the returned error + else if(syncPromise.err) { + error.call(context, syncPromise.err); + } + + // fire off the provided completed event + if(completed) { + completed.call(context); + } + + // the "promise" has been resolved, clear the interval + clearInterval(promiseInterval); + } + }, context)); + + }; + /** * Model responsible for autoSynced objects * This model is never directly used. The Backbone.Firebase.Model will @@ -540,22 +586,19 @@ // whether the initial load has occurred, the "sync" event can // be fired once _initialSync has been resolved. fetch: function(options) { - var fetchInterval = setInterval(_.bind(function() { - if(this._initialSync.resolve) { - - if(this._initialSync.success) { - this.trigger('sync', this, null, options); - } - else if(this._initialSync.err) { - this.trigger('err', this, this._initialSync.err, options); - } - - // fire off any optional callbacks + Backbone.Firebase._promiseEvent({ + syncPromise: this._initialSync, + context: this, + success: function() { + this.trigger('sync', this, null, options); + }, + error: function(err) { + this.trigger('err', this, err, options); + }, + complete: function() { Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options); - - clearInterval(fetchInterval); } - }, this)); + }); }, _log: function(msg) { diff --git a/test/specs/collection_test.js b/test/specs/collection_test.js index 5cd5af1..35f0205 100644 --- a/test/specs/collection_test.js +++ b/test/specs/collection_test.js @@ -377,11 +377,15 @@ describe('Backbone.Firebase.Collection', function() { describe('#fetch', function() { - it('should fetch when _initialSync has been resolved', function() { + it('should call Backbone.Firebase._promiseEvent', function() { + sinon.spy(Backbone.Firebase, '_promiseEvent'); + collection.fetch(); collection.firebase.flush(); - expect(collection._initialSync.resolve).to.be.ok; + expect(Backbone.Firebase._promiseEvent.calledOnce).to.be.ok; + + Backbone.Firebase._promiseEvent.restore(); }); }); @@ -823,4 +827,4 @@ describe('Backbone.Firebase.Collection', function() { }); -}); \ No newline at end of file +}); diff --git a/test/specs/prototype_test.js b/test/specs/prototype_test.js index 4354a1b..6016f99 100644 --- a/test/specs/prototype_test.js +++ b/test/specs/prototype_test.js @@ -4,6 +4,67 @@ describe('Backbone.Firebase', function() { return expect(Backbone.Firebase).to.be.ok; }); + describe('#_promiseEvent', function() { + var syncPromise; + var clock; + beforeEach(function() { + syncPromise = { + resolve: true + }; + clock = sinon.useFakeTimers(); + }); + + afterEach(function() { + clock.restore(); + }); + + it('should resolve with a success', function() { + var successCalled = false; + syncPromise.success = true; + Backbone.Firebase._promiseEvent({ + syncPromise: syncPromise, + success: function() { + successCalled = true; + }, + context: this + }); + clock.tick(100); + expect(successCalled).to.be.ok; + }); + + it('should resolve with an error', function() { + var errorCalled = false; + syncPromise.error = new Error('Error!'); + Backbone.Firebase._promiseEvent({ + syncPromise: syncPromise, + error: function() { + var errorCalled = true; + }, + context: this + }); + clock.tick(1000); + expect(errorCalled).to.be.ok; + }); + + it('should resolve with a completed', function() { + var completedCalled = false; + syncPromise.success = true; + Backbone.Firebase._promiseEvent({ + syncPromise: syncPromise, + success: function() { + + }, + completed: function() { + completedCalled = true; + }, + context: this + }); + clock.tick(100); + expect(completedCalled).to.be.ok; + }); + + }); + describe("#_isPrimitive", function() { it('should return false for null', function() { @@ -333,4 +394,4 @@ describe('Backbone.Firebase', function() { }); -}); \ No newline at end of file +}); From ca760afb27375fe04877c6d2201483a2eed4dd35 Mon Sep 17 00:00:00 2001 From: David East Date: Mon, 15 Dec 2014 07:59:21 -0800 Subject: [PATCH 08/15] _promiseEvent tests --- src/backbonefire.js | 2 +- test/specs/collection_test.js | 10 ++++++++++ test/specs/prototype_test.js | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index c54ed5e..233b9f1 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -503,7 +503,7 @@ // indicate that the call has been received from the server // and that an error has occurred this._initialSync.resolve = true; - this._initialSync.error = err; + this._initialSync.err = err; this.trigger('error', this, err, null); }, this); diff --git a/test/specs/collection_test.js b/test/specs/collection_test.js index 35f0205..f5783aa 100644 --- a/test/specs/collection_test.js +++ b/test/specs/collection_test.js @@ -388,6 +388,16 @@ describe('Backbone.Firebase.Collection', function() { Backbone.Firebase._promiseEvent.restore(); }); + it('should fire success', function() { + var successCalled = false; + collection.fetch(); + collection.on('sync', function() { + successCalled = true; + }); + collection.firebase.flush(); + expect(successCalled).to.be.ok; + }); + }); describe('#_log', function() { diff --git a/test/specs/prototype_test.js b/test/specs/prototype_test.js index 6016f99..876a386 100644 --- a/test/specs/prototype_test.js +++ b/test/specs/prototype_test.js @@ -34,11 +34,11 @@ describe('Backbone.Firebase', function() { it('should resolve with an error', function() { var errorCalled = false; - syncPromise.error = new Error('Error!'); + syncPromise.err = new Error('Error!'); Backbone.Firebase._promiseEvent({ syncPromise: syncPromise, error: function() { - var errorCalled = true; + errorCalled = true; }, context: this }); From 2c1ef17c5ff541cedb3db39624ac2e45a143c1f3 Mon Sep 17 00:00:00 2001 From: David East Date: Mon, 15 Dec 2014 16:41:09 -0800 Subject: [PATCH 09/15] _promiseEvent for SyncModel --- src/backbonefire.js | 24 ++++++++++++++++++++++-- test/specs/model_test.js | 14 +++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 233b9f1..fedded9 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -249,11 +249,19 @@ function SyncModel() { // Set up sync events - + this._initialSync = {}; // apply remote changes locally this.firebase.on('value', function(snap) { this._setLocal(snap); + this._initialSync.resolve = true; + this._initialSync.success = true; this.trigger('sync', this, null, null); + }, function(err) { + // indicate that the call has been received from the server + // and that an error has occurred + this._initialSync.resolve = true; + this._initialSync.err = err; + this.trigger('error', this, err, null); }, this); // apply local changes remotely @@ -265,7 +273,19 @@ SyncModel.protoype = { fetch: function(options) { - this.trigger('sync', this, options); + Backbone.Firebase._promiseEvent({ + syncPromise: this._initialSync, + context: this, + success: function() { + this.trigger('sync', this, null, options); + }, + error: function(err) { + this.trigger('err', this, err, options); + }, + complete: function() { + Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options); + } + }); }, sync: function(method, model, options) { Backbone.Firebase.sync(method, model, options); diff --git a/test/specs/model_test.js b/test/specs/model_test.js index cbf7f5d..7cb95cb 100644 --- a/test/specs/model_test.js +++ b/test/specs/model_test.js @@ -161,6 +161,18 @@ describe('Backbone.Firebase.Model', function() { return expect(syncIsCalled).to.be.ok; }); + it('should call Backbone.Firebase._promiseEvent', function() { + var model = new Model(); + sinon.spy(Backbone.Firebase, '_promiseEvent'); + + model.fetch(); + model.firebase.flush(); + + expect(Backbone.Firebase._promiseEvent.calledOnce).to.be.ok; + + Backbone.Firebase._promiseEvent.restore(); + }); + }); describe('#sync', function() { @@ -379,4 +391,4 @@ describe('Backbone.Firebase.Model', function() { }); -}); \ No newline at end of file +}); From da5516b05ee9c64ca1188adb75d3c8ee16dde1f2 Mon Sep 17 00:00:00 2001 From: David East Date: Wed, 17 Dec 2014 16:57:46 -0800 Subject: [PATCH 10/15] Moved sync override to Model definition --- src/backbonefire.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index fedded9..ddfb0b7 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -286,9 +286,6 @@ Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options); } }); - }, - sync: function(method, model, options) { - Backbone.Firebase.sync(method, model, options); } }; @@ -312,14 +309,6 @@ } - OnceModel.protoype = { - - sync: function(method, model, options) { - Backbone.Firebase.sync(method, model, options); - } - - }; - return OnceModel; }()); @@ -360,7 +349,10 @@ } }, - + + sync: function(method, model, options) { + Backbone.Firebase.sync(method, model, options); + }, /** * Siliently set the id of the model to the snapshot key From 84c2f838ed94c04bd0d652af3a76b1d2fdc318d0 Mon Sep 17 00:00:00 2001 From: David East Date: Tue, 13 Jan 2015 10:11:26 -0800 Subject: [PATCH 11/15] urlRoot test for Model. --- .gitignore | 1 + src/backbonefire.js | 3 +-- test/specs/model_test.js | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d1ac9ba..3249e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ test/coverage/ node_modules/ bower_components/ test/coverage +.idea \ No newline at end of file diff --git a/src/backbonefire.js b/src/backbonefire.js index ddfb0b7..064a5ab 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -30,8 +30,7 @@ */ Backbone.Firebase._determineAutoSync = function(model, options) { var proto = Object.getPrototypeOf(model); - return _.extend( - { + return _.extend({ autoSync: proto.hasOwnProperty('autoSync') ? proto.autoSync : true }, this, diff --git a/test/specs/model_test.js b/test/specs/model_test.js index 7cb95cb..fa9f14e 100644 --- a/test/specs/model_test.js +++ b/test/specs/model_test.js @@ -18,6 +18,18 @@ describe('Backbone.Firebase.Model', function() { return expect(new Model()).to.be.ok; }); + it('should create a model with a urlRoot from its id', function() { + var mockUrl = 'https://mock-bf.firebaseio.com/users'; + var Model = Backbone.Firebase.Model.extend({ + urlRoot: mockUrl + }); + var model = new Model({ + id: 'david' + }); + + model.url().should.equal(mockUrl + '/' + model.get('id')); + }); + describe('#constructor', function() { it('should throw an error if an invalid url is provided', function() { From a1b482e100aa02e6671aff1888f951cfde5b9913 Mon Sep 17 00:00:00 2001 From: David East Date: Tue, 13 Jan 2015 10:51:10 -0800 Subject: [PATCH 12/15] complete vs. completed in _promiseEvent --- src/backbonefire.js | 6 +++--- test/specs/prototype_test.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index 064a5ab..c4939ea 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -211,7 +211,7 @@ var success = params.success; var error = params.error; var context = params.context || this; - var completed = params.completed; + var complete = params.complete; // set up an interval that checks to see if data has been synced from the server var promiseInterval = setInterval(_.bind(function() { @@ -228,8 +228,8 @@ } // fire off the provided completed event - if(completed) { - completed.call(context); + if(complete) { + complete.call(context); } // the "promise" has been resolved, clear the interval diff --git a/test/specs/prototype_test.js b/test/specs/prototype_test.js index 876a386..afac069 100644 --- a/test/specs/prototype_test.js +++ b/test/specs/prototype_test.js @@ -46,21 +46,21 @@ describe('Backbone.Firebase', function() { expect(errorCalled).to.be.ok; }); - it('should resolve with a completed', function() { - var completedCalled = false; + it('should resolve with a complete', function() { + var completeCalled = false; syncPromise.success = true; Backbone.Firebase._promiseEvent({ syncPromise: syncPromise, success: function() { }, - completed: function() { - completedCalled = true; + complete: function() { + completeCalled = true; }, context: this }); clock.tick(100); - expect(completedCalled).to.be.ok; + expect(completeCalled).to.be.ok; }); }); From d516f8fb913f69748596142804ca9967e3b43937 Mon Sep 17 00:00:00 2001 From: David East Date: Tue, 13 Jan 2015 11:32:50 -0800 Subject: [PATCH 13/15] README updates. --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 64433cc..57d2bfa 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,6 @@ Any models added to the collection will be synchronized to the provided Firebase using the Backbone binding will also receive `add`, `remove` and `changed` events on the collection as appropriate. -**BE AWARE!** If autoSync is set to true, you do not need to call any functions that will affect _remote_ data. If you call -`fetch()` on the collection, **the library will ignore it silently**. However, if autoSync is set to false, you can use `fetch()`. This is explained above in the autoSync section. - You should add and remove your models to the collection as you normally would, (via `add()` and `remove()`) and _remote_ data will be instantly updated. Subsequently, the same events will fire on all your other clients immediately. @@ -240,10 +237,9 @@ var todo = new Todo({ }); ``` -**BE AWARE!** You do not need to call any functions that will affect _remote_ data. If autoSync is enabled and you call -`save()` or `fetch()` on the model, **the library will ignore it silently**. +You do not need to call any functions that will affect _remote_ data when `autoSync` is enabled. Calling `fetch()` will simply fire the `sync` event. -If autoSync is enabled, you should modify your model as you normally would, (via `set()` and `destroy()`) and _remote_ data +If `autoSync` is enabled, you should modify your model as you normally would, (via `set()` and `destroy()`) and _remote_ data will be instantly updated. #### autoSync: true From 3877898a011eb0b3b760cca29cd22c5c8c5728e1 Mon Sep 17 00:00:00 2001 From: David East Date: Tue, 13 Jan 2015 11:33:46 -0800 Subject: [PATCH 14/15] Poking Travis. --- src/backbonefire.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backbonefire.js b/src/backbonefire.js index c4939ea..4803699 100644 --- a/src/backbonefire.js +++ b/src/backbonefire.js @@ -850,7 +850,7 @@ comparator: function(model) { return model.id; - }, + } }); From b0e4b327ca4f4b13ed96750ed725c7af2aefbdbb Mon Sep 17 00:00:00 2001 From: David East Date: Tue, 13 Jan 2015 11:45:28 -0800 Subject: [PATCH 15/15] Change log. --- changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index e69de29..7b74bf6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1,5 @@ +fixed - Improved asynchronous sync behavior. +feature - Added save and remove for autoSync models. +feature - Added fetch method for autoSync models. +fixed - Fixed issue with urlRoot property for models. +fixed - Fixed bug with comparator override. \ No newline at end of file