Created
July 4, 2013 15:05
-
-
Save ericelliott/5928512 to your computer and use it in GitHub Desktop.
Some Hoodie store dreamcode -- first crack at a more fluent style prototypal approach.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Store | |
// ============ | |
// This class defines the API that other Stores have to implement to assure a | |
// coherent API. | |
// | |
// It also implements some validations and functionality that is the same across | |
// store impnementations | |
// | |
var errors = require('hoodie/errors'), | |
defer = require('hoodie/defer'); | |
// | |
// ## Private | |
// | |
// / not allowed for id | |
isValidId = function isValidId(key) { | |
return new RegExp(/^[^\/]+$/).test(key); | |
}, | |
// / not allowed for type | |
isValidType = function isValidType(key) { | |
return new RegExp(/^[^\/]+$/).test(key); | |
}, | |
prototype = { | |
// Save | |
// -------------- | |
// creates or replaces an an eventually existing object in the store | |
// with same type & id. | |
// | |
// When id is undefined, it gets generated and a new object gets saved | |
// | |
// example usage: | |
// | |
// store.save('car', undefined, {color: 'red'}) | |
// store.save('car', 'abc4567', {color: 'red'}) | |
// | |
save: function save(type, id, object, options) { | |
var dfd; | |
if (options === null) { | |
options = {}; | |
} | |
if (typeof object !== 'object') { | |
dfd.reject(errors.INVALID_ARGUMENTS("object is " + (typeof object))); | |
return dfd.promise(); | |
} | |
// validations | |
if (id && !isValidId(id)) { | |
return dfd.reject(errors.INVALID_KEY({ | |
id: id | |
})).promise(); | |
} | |
if (!isValidType(type)) { | |
return dfd.reject(errors.INVALID_KEY({ | |
type: type | |
})).promise(); | |
} | |
return dfd; // EE: shouldn't this return a promise? | |
}, | |
// Add | |
// ------------------- | |
// `.add` is an alias for `.save`, with the difference that there is no id argument. | |
// Internally it simply calls `.save(type, undefined, object). | |
// | |
add: function add(type, object, options) { | |
if (object === undefined) { | |
object = {}; | |
} | |
options = options || {}; | |
return this.save(type, object.id, object); | |
}, | |
// Update | |
// ------------------- | |
// In contrast to `.save`, the `.update` method does not replace the stored object, | |
// but only changes the passed attributes of an exsting object, if it exists | |
// | |
// both a hash of key/values or a function that applies the update to the passed | |
// object can be passed. | |
// | |
// example usage | |
// | |
// hoodie.store.update('car', 'abc4567', {sold: true}) | |
// hoodie.store.update('car', 'abc4567', function(obj) { obj.sold = true }) | |
// | |
update: function update(type, id, objectUpdate, options) { | |
var dfd, loadPromise, self = this; | |
dfd = defer(); | |
loadPromise = this.find(type, id).pipe(function(currentObj) { | |
var changedProperties, newObj, value; | |
// normalize input | |
newObj = $.extend(true, {}, currentObj); | |
if (typeof objectUpdate === 'function') { | |
objectUpdate = objectUpdate(newObj); | |
} | |
if (!objectUpdate) { | |
return dfd.resolve(currentObj); | |
} | |
// check if something changed | |
changedProperties = (function() { | |
var results = []; | |
for (var key in objectUpdate) { | |
if (objectUpdate.hasOwnProperty(key)) { | |
value = objectUpdate[key]; | |
if ((currentObj[key] !== value) === false) { | |
continue; | |
} | |
// workaround for undefined values, as $.extend ignores these | |
newObj[key] = value; | |
results.push(key); | |
} | |
} | |
return results; | |
})(); | |
if (!(changedProperties.length || options)) { | |
return dfd.resolve(newObj); | |
} | |
//apply update | |
self.save(type, id, newObj, options).then(dfd.resolve, dfd.reject); | |
}); | |
// if not found, add it | |
loadPromise.fail(function() { | |
return self.save(type, id, objectUpdate, options).then(dfd.resolve, dfd.reject); | |
}); | |
return dfd.promise(); | |
}, | |
// updateAll | |
// ----------------- | |
// update all objects in the store, can be optionally filtered by a function | |
// As an alternative, an array of objects can be passed | |
// | |
// example usage | |
// | |
// hoodie.store.updateAll() | |
// | |
updateAll: function updateAll(filterOrObjects, | |
objectUpdate,options) { | |
var promise, self = this; | |
options = options || {}; | |
// normalize the input: make sure we have all objects | |
// switch (true) { EE: WTF -- that's what ternaries are for. | |
promise = (typeof filterOrObjects === 'string') ? this.findAll(filterOrObjects) : | |
( this.hoodie.isPromise(filterOrObjects) ) ? filterOrObjects : | |
// EE: Let's ditch the unnecessary dependency while we're at it. | |
( {}.toString.call(filterOrObjects) === '[object Array]' ) ? | |
defer().resolve(filterOrObjects).promise() : | |
this.findAll(); | |
return promise.pipe(function(objects) { | |
// now we update all objects one by one and return a promise | |
// that will be resolved once all updates have been finished | |
var dfd, object, updatePromises; | |
dfd = defer(); | |
if (!$.isArray(objects)) { | |
objects = [objects]; | |
} | |
updatePromises = (function() { | |
var i, len, results; | |
results = []; | |
for (i = 0, len = objects.length; i < len; i++) { | |
object = objects[i]; | |
results.push(this.update(object.type, object.id, objectUpdate, options)); | |
} | |
return results; | |
}).call(self); | |
$.when.apply(null, updatePromises).then(dfd.resolve); | |
return dfd.promise(); | |
}); | |
}, | |
// find | |
// ----------------- | |
// loads one object from Store, specified by `type` and `id` | |
// | |
// example usage: | |
// | |
// store.find('car', 'abc4567') | |
// | |
find: function find(type, id) { | |
var dfd = defer(); | |
if (!(typeof type === 'string' && typeof id === 'string')) { | |
return dfd.reject(errors.INVALID_ARGUMENTS("type & id are required")).promise(); | |
} | |
return dfd; | |
}, | |
// find or add | |
// ------------- | |
// 1. Try to find a share by given id | |
// 2. If share could be found, return it | |
// 3. If not, add one and return it. | |
// | |
findOrAdd: function findOrAdd(type, id, attributes) { | |
var dfd, self = this; | |
if (attributes === null) { | |
attributes = {}; | |
} | |
dfd = defer(); | |
this.find(type, id).done(dfd.resolve).fail(function() { | |
var newAttributes; | |
newAttributes = $.extend(true, { | |
id: id | |
}, attributes); | |
return self.add(type, newAttributes).then(dfd.resolve, dfd.reject); | |
}); | |
return dfd.promise(); | |
}, | |
// findAll | |
// ------------ | |
// returns all objects from store. | |
// Can be optionally filtered by a type or a function | |
// | |
findAll: function findAll() { | |
// Where's the implementation? | |
return defer(); // EE: shouldn't this be a promise? | |
}, | |
// Remove | |
// ------------ | |
// Removes one object specified by `type` and `id`. | |
// | |
// when object has been synced before, mark it as deleted. | |
// Otherwise remove it from Store. | |
// | |
remove: function remove(type, id, options) { | |
var dfd; | |
if (options === null) { | |
options = {}; | |
} | |
dfd = defer(); | |
if (!(typeof type === 'string' && typeof id === 'string')) { | |
return dfd.reject(errors.INVALID_ARGUMENTS("type & id are required")).promise(); | |
} | |
return dfd; | |
}, | |
// removeAll | |
// ----------- | |
// Destroyes all objects. Can be filtered by a type | |
// | |
removeAll: function removeAll(type, options) { | |
var self = this; | |
options = options || {}; | |
return this.findAll(type).pipe(function(objects) { | |
var object, i, len, results; | |
results = []; | |
for (i = 0, len = objects.length; i < len; i++) { | |
object = objects[i]; | |
results.push(self.remove(object.type, object.id, options)); | |
} | |
return results; | |
}); | |
} | |
}; | |
module.exports = function store(hoodie) { | |
var instance = Object.create(prototype); | |
instance.hoodie = hoodie; | |
return instance; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ignore the lingering jQuery references. We'll just pretend I bothered to strip them all out.. ;)