Skip to content

Commit

Permalink
Merge 7bfe0d7 into 86fc971
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel committed Apr 29, 2014
2 parents 86fc971 + 7bfe0d7 commit 637d6aa
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 29 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ language: node_js
node_js:
- 0.10.18
before_install:
- npm install -g buster@0.7.8
- npm install -g grunt-cli
- npm install -g bower
after_script:
Expand Down
13 changes: 13 additions & 0 deletions examples/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var sirenApi = new Backbone.Siren('root.url', options);

sirenApi.resolve('team/123');

// How can I subscribe to changes to this model?
// Do you subscribe to changes to a model or changes to a "channel"
// Does each push notification resolve to a url endpoint?

// Are push notifications fundamentally different from http requests (e.g., is there a 1 to 1 relationship between a notification and a url endpoint)

// would this be a "batch" operation?
// If subscribed to a "loans" channel this is easy because you can get a collection of loans
// If listening to changes in general, for all models this would require some concept of Batch (how would we implement the long polling fallback?)
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
"license": "BSD",
"readmeFilename": "readme.md",
"devDependencies": {
"buster": "~0.7.8",
"buster-coverage": "~0.1.0",
"coveralls": "~2.6.1",
"grunt": "~0.4.1",
"grunt-buster": "~0.3.1",
"grunt-contrib-jshint": "~0.7.2",
"grunt-contrib-uglify": "~0.2.7",
"grunt-rigger": "~0.5.0"
"buster": "0.7.8",
"buster-coverage": "0.1.0",
"coveralls": "2.6.1",
"grunt": "0.4.1",
"grunt-buster": "0.3.1",
"grunt-contrib-jshint": "0.7.2",
"grunt-contrib-uglify": "0.2.7",
"grunt-rigger": "0.5.0"
}
}
2 changes: 1 addition & 1 deletion src/backbone.siren.action.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ Action.prototype = {
});
}

options = _.extend(presets, options);
options = _.extend(presets, parent.siren.ajaxOptions || {}, options);
attributes = _.extend(parent.toJSON({actionName: this.name}), attributes);

// Note that .save() can return false in the case of failed validation.
Expand Down
63 changes: 45 additions & 18 deletions src/backbone.siren.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ function match(filters) {
* @todo This only works with rel links that are requests to the API.
* There will be times when a rel points to a resource outside of the API and that needs to be thought through
* @todo This method leaves much to be desired and should be refactored.
* @todo might be more useful if it checks if the link is to a sirenEntity? If so, resolves it? (still need to put some thought into this)
*
* @param {String} rel
* @returns {Promise}
Expand All @@ -237,11 +238,9 @@ function request(rel) {
return _.indexOf(link.rel, rel) > -1;
});

if (! link) {
return;
if (link) {
return BbSiren.resolveOne(link.href);
}

return BbSiren.resolveOne(link.href);
}


Expand All @@ -256,7 +255,7 @@ function request(rel) {
* @returns {Promise}
*/
function resolve(options) {
options = options || {};
options = $.extend(this.siren.ajaxOptions || {}, options);

var deferred = new $.Deferred();

Expand All @@ -270,6 +269,7 @@ function resolve(options) {
// Its already been hydrated
deferred.resolve(this);
} else if (options.url) {

// This option allows us to defer hydration of our model or collection with the url provided
// Very much like .fetch() only it adds support for chaining nested entities

Expand Down Expand Up @@ -342,6 +342,7 @@ function resolveNextInChain(chain, options) {
/**
* Is called in the Model or Collection's constructor.
* It creates a Backbone.Siren.Action instance from a raw action and attaches it to the Model or Collection (aka "parent").
* @todo, This should probably be a public, static method on BbSiren.
*
* @returns {Array|undefined}
*/
Expand Down Expand Up @@ -438,21 +439,20 @@ _.extend(BbSiren, {
* Creates a Backbone.Siren model, collection, or error from a Siren object
*
* @param {Object} rawEntity
* @param {Object} options
* @returns {Backbone.Siren.Model|Backbone.Siren.Collection|Backbone.Siren.Error}
*/
, parse: function (rawEntity, store) {
var bbSiren;
, parse: function (rawEntity, options) {
options = options || {};

if (BbSiren.isRawCollection(rawEntity)) {
bbSiren = new Backbone.Siren.Collection(rawEntity, {store: store});
return new Backbone.Siren.Collection(rawEntity, options);
} else if (BbSiren.isRawError(rawEntity)) {
// @todo how should we represent errors? For now, treat them as regular Models...
bbSiren = new Backbone.Siren.Model(rawEntity, {store: store});
return new Backbone.Siren.Model(rawEntity, options);
} else {
bbSiren = new Backbone.Siren.Model(rawEntity, {store: store});
return new Backbone.Siren.Model(rawEntity, options);
}

return bbSiren;
}


Expand Down Expand Up @@ -542,6 +542,7 @@ _.extend(BbSiren, {
* @param {String} url
* @param {Object} options
* @param {Object} options.store - store instance @todo remove the need to have this parameter
* @todo - add an options.ajaxOptions parameter.
*/
, resolveOne: function (url, options) {
options = options || {};
Expand Down Expand Up @@ -605,7 +606,7 @@ _.extend(BbSiren, {

BbSiren.ajax(rootUrl, options)
.done(function (rawEntity) {
var bbSiren = BbSiren.parse(rawEntity, store);
var bbSiren = BbSiren.parse(rawEntity, options);
deferred.resolve(bbSiren);

options.deferred = chainedDeferred;
Expand All @@ -620,7 +621,7 @@ _.extend(BbSiren, {
entity = {};
}

bbSiren = BbSiren.parse(entity, store);
bbSiren = BbSiren.parse(entity, options);
deferred.reject(bbSiren, jqXhr);
chainedDeferred.reject(bbSiren, jqXhr);
});
Expand Down Expand Up @@ -686,7 +687,7 @@ _.extend(BbSiren, {
deferred.resolve(self.setEntity(bbSiren, rawEntity.rel, getRawEntityName(rawEntity)));
});
} else {
bbSiren = BbSiren.parse(rawEntity, options.store);
bbSiren = BbSiren.parse(rawEntity, options);
bbSirenPromise = deferred.resolve(this.setEntity(bbSiren, rawEntity.rel, getRawEntityName(rawEntity)));
}

Expand Down Expand Up @@ -718,9 +719,6 @@ _.extend(BbSiren, {
}





/**
* http://backbonejs.org/#Model-parse
*
Expand Down Expand Up @@ -820,6 +818,23 @@ _.extend(BbSiren, {
options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

Backbone.Model.call(this, sirenObj, options);

this.siren = {};

// the store
if (options.store) {
this.siren.store = options.store;
}

// entity options
if (options.ajaxOptions) {
this.siren.ajaxOptions = options.ajaxOptions;
}

if (options.apiRoot) {
this.siren.apiRoot = options.apiRoot;
}

this.parseActions();
}

Expand Down Expand Up @@ -944,6 +959,17 @@ _.extend(BbSiren, {
options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

Backbone.Collection.call(this, sirenObj, options);

this.siren = {};

if (options.store) {
this.siren.store = options.store;
}

if (options.ajaxOptions) {
this.siren.ajaxOptions = options.ajaxOptions;
}

this.parseActions();
}
})
Expand Down Expand Up @@ -983,6 +1009,7 @@ BbSiren.prototype = {
, resolve: function (entityPaths, options) {
options = $.extend({}, this.options, options);
options.store = this.store;
options.apiRoot = this.apiRoot;

var self = this
, urls = [];
Expand Down
19 changes: 17 additions & 2 deletions test/spec/backbone.siren.action.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ describe('Siren Action: ', function () {
// @todo quick fix for upgrade to buster 0.7
var expect = buster.expect;

var sirenAction = {name: 'add-item', 'class': ['fuzzy', 'fluffy'], title: 'Add Item', method: 'FANCY', href: 'http://api.x.io/orders/42/items', type: 'application/x-fancy-stuff', fields: [{name: 'orderNumber', type: 'hidden', value: '42'}, {name: 'productCode', type: 'text'}, {name: 'quantity', type: 'number' }]}
, bbSirenAction;
var sirenAction, bbSirenAction;


beforeEach(function () {
sirenAction = {name: 'add-item', 'class': ['fuzzy', 'fluffy'], title: 'Add Item', method: 'FANCY', href: 'http://api.x.io/orders/42/items', type: 'application/x-fancy-stuff', fields: [{name: 'orderNumber', type: 'hidden', value: '42'}, {name: 'productCode', type: 'text'}, {name: 'quantity', type: 'number' }]};
bbSirenAction = new Backbone.Siren.Action(sirenAction);
});

Expand Down Expand Up @@ -96,6 +96,21 @@ describe('Siren Action: ', function () {
});


it('merges the siren.ajaxOptions onto each call', function () {
var mySirenModel = {href: 'test', actions: [sirenAction]}
, myBbSirenModel = new Backbone.Siren.Model(mySirenModel, {ajaxOptions: {type: 'FANCIER', contentType: 'application/x-aaah-shite'}});

myBbSirenModel.getActionByName('add-item').execute();
expect($.ajax).toHaveBeenCalledWith(sinon.match({url: 'http://api.x.io/orders/42/items', type: 'FANCIER', contentType: 'application/x-aaah-shite'}));

$.ajax.reset();

// Override
myBbSirenModel.getActionByName('add-item').execute({type: 'FANCIEST'});
expect($.ajax).toHaveBeenCalledWith(sinon.match({url: 'http://api.x.io/orders/42/items', type: 'FANCIEST', contentType: 'application/x-aaah-shite'}));
});


it('returns undefined if there is no parent to the action', function () {
expect(bbSirenAction.execute()).not.toBeDefined();
});
Expand Down
38 changes: 38 additions & 0 deletions test/spec/backbone.siren.collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ describe('Siren Collection: ', function () {
});


describe('.resolve()', function () {
it('merges siren.ajaxOptions onto each each call', function () {
var options = {forceFetch: true, type: 'blah'};

this.stub(sirenCollection, 'fetch');
sirenCollection.siren.ajaxOptions = {dataType: 'json'};
sirenCollection.resolve(options);

expect(sirenCollection.fetch).toHaveBeenCalledWith(sinon.match({forceFetch: true, type: 'blah', dataType: 'json'}));
});
});


describe('.hasClass()', function () {
it('returns whether a collection has a given class', function () {
expect(sirenCollection.hasClass('wtf')).toBe(false);
Expand Down Expand Up @@ -443,6 +456,31 @@ describe('Siren Collection: ', function () {
});




describe('.siren', function () {
it('is an object that is set on each BbSiren Collection upon instantiation', function () {
var myCollection = new Backbone.Siren.Model();
expect(myCollection.siren).toBeObject();
});


it('has a store if provided via the options', function () {
var myCollection = new Backbone.Siren.Collection({href: 'blah'}, {store: new Backbone.Siren.Store()});
expect(myCollection.siren.store).toBeObject();
});


it('has a ajaxOptions if provided via the options', function () {
var ajaxOptions = {data: {blah: true}, type: 'json'}
, myCollection = new Backbone.Siren.Collection({href: 'blah'}, {ajaxOptions: ajaxOptions});

expect(myCollection.siren.ajaxOptions).toBeObject();
expect(myCollection.siren.ajaxOptions).toEqual(ajaxOptions);
});
});


it('Adds siren sub-entities as models to a Backbone Collection\'s models property', function () {
expect(sirenCollection.models).toBeDefined();
expect(sirenCollection.models.length).toBe(3);
Expand Down
36 changes: 36 additions & 0 deletions test/spec/backbone.siren.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ describe('Siren Model: ', function () {
});


describe('.resolve()', function () {
it('merges siren.ajaxOptions onto each each call', function () {
var options = {forceFetch: true, type: 'blah'};

this.stub(sirenModel, 'fetch');
sirenModel.siren.ajaxOptions = {dataType: 'json'};
sirenModel.resolve(options);

expect(sirenModel.fetch).toHaveBeenCalledWith(sinon.match({forceFetch: true, type: 'blah', dataType: 'json'}));
});
});


describe('.hasClass()', function () {
it('returns whether a model has a given class', function () {
expect(sirenModel.hasClass('wtf')).toBeFalse();
Expand Down Expand Up @@ -530,6 +543,29 @@ describe('Siren Model: ', function () {
});


describe('.siren', function () {
it('is an object that is set each BbSiren Model upon instantiation', function () {
var myModel = new Backbone.Siren.Model();
expect(myModel.siren).toBeObject();
});


it('has a store if provided via the options', function () {
var myModel = new Backbone.Siren.Model({href: 'blah'}, {store: new Backbone.Siren.Store()});
expect(myModel.siren.store).toBeObject();
});


it('has a ajaxOptions if provided via the options', function () {
var ajaxOptions = {data: {blah: true}, type: 'json'}
, myModel = new Backbone.Siren.Model({href: 'blah'}, {ajaxOptions: ajaxOptions});

expect(myModel.siren.ajaxOptions).toBeObject();
expect(myModel.siren.ajaxOptions).toEqual(ajaxOptions);
});
});


it('sets a Backbone Model\'s "attributes" hash to the siren "properties"', function () {
expect(sirenModel.attributes).toMatch(settingsModelSiren.properties);
});
Expand Down

0 comments on commit 637d6aa

Please sign in to comment.