Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Persistable Backbone Collection Implementation
/*!
* Copyright (c) 2012 Eric Feminella, http://code.ericfeminella.com/license/LICENSE.txt
*/
( function( _, Backbone )
{
// convenience reference to the Backbone.Collection constructor
var _initialize = Backbone.Collection.prototype.initialize;
/*
* The Backbone.PersistableCollection provides a simply abstraction which
* extends the Backbone Collection API in order to allow a collection to
* be persisted in a similar manner to that which is implemented by the
* Backbone.Model via the save method.
*
* Note: The Backbone.PersistableCollection is a simple abstraction in that
* the underlying implementation simply saves a complete representation of
* the collection and it's models when sent to the server.
*
*/
Backbone.PersistableCollection = Backbone.Collection.extend(
{
/*
* Provides an override of Backbone.Collection.initialize which, when
* a new Backbone.PersistableCollection is instantiated, will create
* the proxy for this collection, invoking the super constructor with
* any defaults provided.
*
*/
initialize: function( models, options )
{
_initialize.call( this, models, options );
this.proxy = Backbone.PersistableCollection.createProxy( this );
},
/*
* Saves the collection as a representation of all models based on the
* state of the collection.
*/
save: function( options )
{
return this.proxy.save( options );
}
},
{
/*
* Provides a static factory method which, given a collection, creates
* a proxy which wraps the collection in a Backbone.Model, thus allowing
* the collection to be saved in the same manner as one would typically
* persist a Backbone.Model.
*
* The createProxy method will only create a single proxy per collection.
* Therefore, if createProxy is called with the same collection more than
* once, the existing proxy will be returned. If createProxy is invoked
* with a collection being provided, an exception is thrown.
*
* <pre>
*
* var users = new Backbone.Collection([
* { name: "Joe", id: 5},
* { name: "Bob", id: 26}
* ]);
* users.url = '/users';
*
* var proxy = Backbone.PersistableCollection.createProxy( users );
* proxy.save(); // saves the collection to /users
*
* </pre>
*
*/
createProxy: function( collection )
{
if ( collection )
{
if ( !collection.proxy )
{
collection.proxy = _.extend( new Backbone.Model(),
{
url: collection.url,
toJSON: function()
{
return collection.toJSON();
}
});
}
return collection.proxy;
}
throw new Error( 'A collection must be provided to implement the proxy' );
}
});
}( _, Backbone ));
/*!
* Copyright (c) 2012 Eric Feminella, http://code.ericfeminella.com/license/LICENSE.txt
*/
(function(a,b){var c=b.Collection.prototype.initialize;b.PersistableCollection=b.Collection.extend({initialize:function(a,d){c.call(this,a,d),this.proxy=b.PersistableCollection.createProxy(this)},save:function(a){return this.proxy.save(a)}},{createProxy:function(c){if(c)return c.proxy||(c.proxy=a.extend(new b.Model,{url:c.url,toJSON:function(){return c.toJSON()}})),c.proxy;throw new Error("A collection must be provided to implement the proxy")}})})(_,Backbone)
/*!
* Copyright (c) 2012 Eric Feminella, http://code.ericfeminella.com/license/LICENSE.txt
*/
describe( "The Backbone.PersistableCollection", function()
{
it( "should extend Backbone.Collection", function()
{
expect( new Backbone.PersistableCollection() instanceof Backbone.Collection ).toBeTruthy();
});
describe( "The createProxy method", function()
{
beforeEach( function()
{
this.collection = new Backbone.Collection([
{ name: "Joe", id: 5},
{ name: "Bob", id: 26}
]);
this.collection.url = "/users";
});
it( "should only create one unique proxy per collection ", function()
{
var proxy = Backbone.PersistableCollection.createProxy( this.collection );
expect( Backbone.PersistableCollection.createProxy( this.collection ) ).toEqual( proxy );
});
it( "should throw an exception if a collection is not specified", function()
{
expect( function(){
Backbone.PersistableCollection.createProxy()
}).toThrow();
});
it( "should be assigned the collection's url value", function()
{
expect( Backbone.PersistableCollection.createProxy( this.collection ).url ).toEqual( this.collection.url );
});
it( "should reference the collection's url function", function()
{
var expected, actual;
this.collection = new Backbone.Collection();
this.collection.url = function(){
return '/some/path/users';
};
expected = Backbone.PersistableCollection.createProxy( this.collection ).url;
actual = this.collection.url;
expect( expected ).toEqual( actual );
expect( expected() ).toEqual( actual() );
});
it( "should return the collection's toJSON value when toJSON is called", function()
{
expect( Backbone.PersistableCollection.createProxy( this.collection ).toJSON() ).toEqual( this.collection.toJSON() );
});
});
describe( "The save method", function()
{
describe( "When saving a PersistableCollection instance", function()
{
beforeEach( function()
{
this.collection = new Backbone.PersistableCollection([
{ name: "Joe", id: 5},
{ name: "Bob", id: 26}
]);
this.collection.url = "/users";
});
it( "should invoke save on the underlying proxy", function()
{
spyOn( this.collection.proxy, 'save' );
spyOn( $, 'ajax' ).andCallFake( function( options ){});
this.collection.save();
expect( this.collection.proxy.save).toHaveBeenCalled();
});
it( "should invoke save on the underlying proxy with the specified options", function()
{
var options = {};
spyOn( this.collection.proxy, 'save' );
spyOn( $, 'ajax' ).andCallFake( function( options ){});
this.collection.save( options );
expect( this.collection.proxy.save).toHaveBeenCalledWith( options );
});
});
describe( "When saving a Backbone.Collection instance", function()
{
beforeEach( function()
{
this.collection = new Backbone.Collection([
{ name: "Joe", id: 5},
{ name: "Bob", id: 26}
]);
this.collection.url = "/users";
this.proxy = Backbone.PersistableCollection.createProxy( this.collection );
});
it( "should invoke save on the underlying proxy", function()
{
spyOn( this.proxy, 'save' );
spyOn( $, 'ajax' ).andCallFake( function( options ){});
this.proxy.save();
expect( this.proxy.save).toHaveBeenCalled();
});
it( "should invoke save on the underlying proxy with the specified options", function()
{
var options = {};
spyOn( this.proxy, 'save' );
spyOn( $, 'ajax' ).andCallFake( function( options ){});
this.proxy.save( options );
expect( this.proxy.save).toHaveBeenCalledWith( options );
});
});
describe( "When the Collection is null", function()
{
beforeEach( function()
{
this.collection = new Backbone.Collection([
{ name: "Joe", id: 5},
{ name: "Bob", id: 26}
]);
this.collection.url = "/users";
this.proxy = Backbone.PersistableCollection.createProxy( this.collection );
});
it( "should throw an exception", function()
{
this.collection = null;
expect( function(){
this.proxy.save();
}).toThrow();
});
});
});
describe( "toJSON", function()
{
beforeEach( function()
{
var UsersCollection = Backbone.PersistableCollection.extend({
url: "/users"
});
this.collection = new UsersCollection([
{ name: "Joe", id: 5},
{ name: "Bob", id: 26}
]);
});
it( "should invoke toJSON on the underlying collection via the proxy", function()
{
spyOn( this.collection, 'toJSON' );
spyOn( $, 'ajax' ).andCallFake( function( options ){});
this.collection.save();
expect( this.collection.toJSON ).toHaveBeenCalled();
});
});
});
@hansede

This comment has been minimized.

Copy link

hansede commented Oct 29, 2012

Bug fix for success and error callbacks: https://gist.github.com/3976291

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.