Skip to content

Instantly share code, notes, and snippets.

@ericelliott
Created July 4, 2013 15:05
Show Gist options
  • Save ericelliott/5928512 to your computer and use it in GitHub Desktop.
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.
// 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;
};
@ericelliott
Copy link
Author

Ignore the lingering jQuery references. We'll just pretend I bothered to strip them all out.. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment