Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store and options #41

Merged
merged 7 commits into from
Apr 29, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uglymunky - why did you decide to move to stricter versioning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests are run on Travis and each run installs all dependencies and I think there were times when these dependencies were introducing breaking changes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uglymunky - Thanks - not questioning your choice - just wanted to understand why

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for being thorough :)

"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
52 changes: 38 additions & 14 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 Down Expand Up @@ -256,7 +257,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 +271,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 +344,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 +441,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 +544,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 +608,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 +623,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 +689,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 +721,6 @@ _.extend(BbSiren, {
}





/**
* http://backbonejs.org/#Model-parse
*
Expand Down Expand Up @@ -820,6 +820,19 @@ _.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;
}

this.parseActions();
}

Expand Down Expand Up @@ -944,6 +957,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
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