Skip to content

Commit

Permalink
Merge pull request #57 from kiva/update-not-replace
Browse files Browse the repository at this point in the history
Update not replace
  • Loading branch information
Martin Butt committed May 7, 2014
2 parents 9f576c4 + b11f9a1 commit 8261793
Show file tree
Hide file tree
Showing 7 changed files with 611 additions and 200 deletions.
153 changes: 136 additions & 17 deletions src/backbone.siren.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function toCamelCase(name) {


/**
* Gets a link url by name from a raw siren enity
* Gets a link url by name from a raw siren entity
*
* @param {Object} rawEntity
* @param {String} name
Expand Down Expand Up @@ -468,6 +468,107 @@ _.extend(BbSiren, {
}


/**
*
* @param {Backbone.Siren.Store} store
* @param {Backbone.Siren.Model} model
*/
, addModelToStore: function (store, model) {
store.addModel(model);
}


/**
* Adds a Collection to the store.
* If it's a "current" collection, it also adds the "self" collection to the store.
*
* @param {Backbone.Siren.Store} store
* @param {Backbone.Siren.Collection} collection
*/
, addCollectionToStore: function (store, collection, storeCurrentOnly) {
var currentUrl = collection.link('current');
if (currentUrl) {
store.addCollection(collection, 'current');
}

// @todo, by having objects automatically added to the store on instantiation we end up having to pass
// around store option. It feels a bit messy doing things this way. Consider re-visiting so that
// objects no longer add themselves to the store on instantiation.
if (! storeCurrentOnly) {
store.addCollection(collection, 'self');
}
}


/**
* Parses a raw siren model into a Backbone.Siren.Model and maintains its representation in the store.
*
* @param {Object} rawModel
* @param {Object} options
* @returns {Backbone.Model}
*/
, parseModel: function (rawModel, options) {
options = options || {};

var model
, store = options.store;

if (store) {
model = store.get(rawModel);
}

if (model) {
model.update(rawModel);
} else {
model = new Backbone.Siren.Model(rawModel, options);
}

return model;
}


/**
* Parses a raw siren collection into a Backbone.Siren.Collection and maintains its representation in the store.
*
* @param rawCollection
* @param options
* @returns {*}
*/
, parseCollection: function (rawCollection, options) {
options = options || {};

var collection, currentUrl
, createNewCollectionFlag = true
, store = options.store;

if (store) {
collection = store.get(rawCollection);
if (collection) {
collection.update(rawCollection);
createNewCollectionFlag = false;
options.storeCurrentOnly = true;
}

// Is it a "current" collection?
currentUrl = getRawEntityUrl(rawCollection, 'current');
if (currentUrl) {
collection = store.get(currentUrl);
if (collection) {
collection.update(rawCollection);
} else {
createNewCollectionFlag = true;
}
}
}

if (createNewCollectionFlag) {
collection = new Backbone.Siren.Collection(rawCollection, options);
}

return collection;
}


/**
* Creates a Backbone.Siren model, collection, or error from a Siren object
*
Expand All @@ -478,13 +579,14 @@ _.extend(BbSiren, {
, parse: function (rawEntity, options) {
options = options || {};

if (BbSiren.isRawCollection(rawEntity)) {
return new Backbone.Siren.Collection(rawEntity, options);
if (BbSiren.isRawCollection(rawEntity)) {
return this.parseCollection(rawEntity, options);
} else if (BbSiren.isRawError(rawEntity)) {
// @todo how should we represent errors? For now, treat them as regular Models...
// @todo are we storing errors in the store? If so, don't...
return new Backbone.Siren.Model(rawEntity, options);
} else {
return new Backbone.Siren.Model(rawEntity, options);
return this.parseModel(rawEntity, options);
}
}

Expand Down Expand Up @@ -766,10 +868,6 @@ _.extend(BbSiren, {

this.resolveEntities(options);

if (options.store) {
options.store.addModel(this);
}

return rawEntity.properties;
}

Expand Down Expand Up @@ -842,6 +940,16 @@ _.extend(BbSiren, {
}


, update: function (rawModel) {
if (BbSiren.isLoaded(rawModel)) {
this.set(this.parse(rawModel));
this.parseActions();
}

return this;
}


/**
* http://backbonejs.org/#Model-constructor
*
Expand All @@ -856,12 +964,11 @@ _.extend(BbSiren, {

this.siren = {};

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

// entity options
if (options.ajaxOptions) {
this.siren.ajaxOptions = options.ajaxOptions;
}
Expand Down Expand Up @@ -902,15 +1009,11 @@ _.extend(BbSiren, {
this._meta = rawEntity.properties || {};
this.isLoaded = BbSiren.isLoaded(rawEntity);

var models = [];
_.each(rawEntity.entities, function (entity) {
models.push(new Backbone.Siren.Model(entity, options));
// As an optimization step, we can use the preParsedModels, otherwise, parse all the sub-entities
var models = options.preParsedModels || _.map(rawEntity.entities, function (entity) {
return BbSiren.parse(entity, options);
});

if (options.store) {
options.store.addCollection(this);
}

return models;
}

Expand Down Expand Up @@ -980,6 +1083,21 @@ _.extend(BbSiren, {
}


/**
*
* @param {Object} rawCollection
* @param {Array} [models] When parsing, use these models instead of the raw models from the collection
*/
, update: function (rawCollection, models) {
if (BbSiren.isLoaded(rawCollection)) {
this.add(this.parse(rawCollection, {preParsedModels: models}));
this.parseActions();
}

return this;
}


/**
* http://backbonejs.org/#Collection-constructor
*
Expand All @@ -996,6 +1114,7 @@ _.extend(BbSiren, {

if (options.store) {
this.siren.store = options.store;
BbSiren.addCollectionToStore(options.store, this, !!options.storeCurrentOnly);
}

if (options.ajaxOptions) {
Expand Down
61 changes: 24 additions & 37 deletions src/backbone.siren.store.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
'use strict';

function newEmptySirenCollection(sirenClass, url) {
return new Backbone.Siren.Collection({
'class': sirenClass
, links: [
{
rel: ['self'], href: url
}
]
});
}


/**
* Stores Siren objects in memory
Expand Down Expand Up @@ -42,43 +31,41 @@ Store.prototype = {
* Adds a collection to the store.
*
* @param {Backbone.Siren.Collection} collection
* @param {String} [rel="self"] - Can be "self" or "current"
* @returns {Backbone.Siren.Store}
*/
, addCollection: function (collection) {
var selfCollection
, self = this
, currentUrl = collection.link('current')
, selfUrl = collection.url();

if (currentUrl) {
// Make sure there is a "self" collection, models should also be added there.
selfCollection = this.get(selfUrl);
if (! selfCollection) {
selfCollection = newEmptySirenCollection(collection.classes(), selfUrl);
this.data[selfUrl] = selfCollection;
}

selfCollection.add(collection.models);
}

// Add each model to the store
collection.each(function (model) {
self.addModel(model);
});

this.data[currentUrl || selfUrl] = collection;
, addCollection: function (collection, rel) {
this.data[collection.link(rel || 'self')] = collection;
return this;
}


/**
*
* @param {Object|String} rawEntityOrUrl
* @returns {Backbone.Siren.Model}
* @returns {Backbone.Siren.Model|Backbone.Siren.Collection}
*/
, get: function (rawEntityOrUrl) {
/*global getRawEntityUrl*/
return this.data[typeof rawEntityOrUrl == 'object'? getRawEntityUrl(rawEntityOrUrl, 'self'): rawEntityOrUrl];
/*global getRawEntitySelfUrl*/
return this.data[typeof rawEntityOrUrl == 'object'? getRawEntitySelfUrl(rawEntityOrUrl): rawEntityOrUrl];
}


/**
* Get the matching "current" collection for the given rawCollection
*
* @param {Object} rawCollection
* @returns {Backbone.Siren.Collection}
*/
, getCurrentCollection: function (rawCollection) {
/*global BbSiren, getRawEntityUrl, getRawEntitySelfUrl */

if (BbSiren.isLoaded(rawCollection)) {
return this.data[getRawEntityUrl(rawCollection, 'current')];
} else {
// The entity is not loaded, so we don't know if it references a "current" collection or a "self" collection
return this.data[getRawEntitySelfUrl(rawCollection)];
}
}


Expand Down
52 changes: 26 additions & 26 deletions test/buster.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,30 @@ config.development = {


// @todo - enable these
//
//config.dist_amd = {
// env: 'browser'
// , rootPath: '../'
// , deps: [
// 'test/assertions.js'
// , 'bower_components/jquery/jquery.js'
// , 'bower_components/underscore/underscore.js'
// , 'bower_components/backbone/backbone.js'
// ]
// , sources: ['dist/amd/backbone.siren.min.js']
// , tests: ['test/spec/**/*.js']
//};

config.dist_amd = {
env: 'browser'
, rootPath: '../'
, deps: [
'test/assertions.js'
, 'bower_components/jquery/jquery.js'
, 'bower_components/underscore/underscore.js'
, 'bower_components/backbone/backbone.js'
]
, sources: ['dist/amd/backbone.siren.min.js']
, tests: ['test/spec/**/*.js']
};


config.dist_iife = {
env: 'browser'
, rootPath: '../'
, deps: [
'test/assertions.js'
, 'bower_components/jquery/jquery.js'
, 'bower_components/underscore/underscore.js'
, 'bower_components/backbone/backbone.js'
]
, sources: ['dist/iife/backbone.siren.min.js']
, tests: ['test/spec/**/*.js']
};
//
//config.dist_iife = {
// env: 'browser'
// , rootPath: '../'
// , deps: [
// 'test/assertions.js'
// , 'bower_components/jquery/jquery.js'
// , 'bower_components/underscore/underscore.js'
// , 'bower_components/backbone/backbone.js'
// ]
// , sources: ['dist/iife/backbone.siren.min.js']
// , tests: ['test/spec/**/*.js']
//};
Loading

0 comments on commit 8261793

Please sign in to comment.