diff --git a/component.json b/component.json index c15f488..cfe6844 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "model", "repo": "component/model", "description": "Elegant data models", - "version": "0.0.1", + "version": "0.0.2", "keywords": [ "collection", "model", @@ -12,10 +12,10 @@ ], "dependencies": { "component/each": "*", - "component/json": "*", "component/emitter": "*", "component/collection": "*", - "visionmedia/superagent": "*" + "visionmedia/superagent": "*", + "ForbesLindesay/is-browser": "*" }, "development": { "component/reactive": "*", @@ -25,8 +25,10 @@ "scripts": [ "lib/index.js", "lib/static.js", - "lib/proto.js" + "lib/proto.js", + "lib/common.js", + "lib/sync.js" ], "main": "lib/index.js", "license": "MIT" -} \ No newline at end of file +} diff --git a/lib/common.js b/lib/common.js new file mode 100644 index 0000000..5e31bc9 --- /dev/null +++ b/lib/common.js @@ -0,0 +1,11 @@ +if(require('is-browser')) { + exports.emitter = require('emitter'); + exports.collection = require('collection'); + exports.each = require('each'); +} else { + exports.emitter = require('emitter-component'); + exports.collection = Array; + exports.each = function(arr, fn) { + return arr.forEach(fn); + }; +} diff --git a/lib/index.js b/lib/index.js index ca3eb13..310ec62 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,9 +3,10 @@ * Module dependencies. */ -var proto = require('./proto') - , statics = require('./static') - , Emitter = require('emitter'); +var proto = require('./proto'), + statics = require('./static'), + common = require('./common'), + Emitter = common.emitter; /** * Expose `createModel`. @@ -48,6 +49,7 @@ function createModel(name) { model.modelName = name; model.base = '/' + name.toLowerCase(); model.attrs = {}; + model.sync = {}; model.validators = []; for (var key in statics) model[key] = statics[key]; diff --git a/lib/proto.js b/lib/proto.js index e351684..ff9dc8c 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -3,11 +3,11 @@ * Module dependencies. */ -var Emitter = require('emitter') - , request = require('superagent') - , JSON = require('json') - , each = require('each') - , noop = function(){}; +var sync = require('./sync'), + common = require('./common'), + Emitter = common.emitter, + each = common.each, + noop = function(){}; /** * Mixin emitter. @@ -124,20 +124,25 @@ exports.validate = function(){ * @api public */ -exports.remove = function(fn){ - fn = fn || noop; +exports.remove = function(){ + var args = [].slice.call(arguments), + fn = args.pop() || noop, + self = this, + remove = this.model.sync.remove || sync.remove; + if (this.isNew()) return fn(new Error('not saved')); - var self = this; - var url = this.url(); - this.model.emit('removing', this); - this.emit('removing'); - request.del(url, function(res){ - if (res.error) return fn(error(res)); + + this.run('removing', function() { + remove.apply(self, args.concat(res)); + }); + + function res(err, body) { + if(err) return fn(error(err)); self.removed = true; self.model.emit('remove', self); self.emit('remove'); fn(); - }); + } }; /** @@ -152,22 +157,28 @@ exports.remove = function(fn){ * @api public */ -exports.save = function(fn){ - if (!this.isNew()) return this.update(fn); - var self = this; - var url = this.model.url(); - fn = fn || noop; +exports.save = function(){ + if (!this.isNew()) return this.update.apply(this, arguments); + + var args = [].slice.call(arguments), + fn = args.pop() || noop, + self = this, + save = this.model.sync.save || sync.save; + if (!this.isValid()) return fn(new Error('validation failed')); - this.model.emit('saving', this); - this.emit('saving'); - request.post(url, self, function(res){ - if (res.error) return fn(error(res)); - if (res.body) self.primary(res.body.id); + + this.run('saving', function() { + save.apply(self, args.concat(res)); + }); + + function res(err, body) { + if (err) return fn(error(err)); + if (body) self.primary(body.id || body._id); self.dirty = {}; self.model.emit('save', self); self.emit('save'); fn(); - }); + } }; /** @@ -177,20 +188,27 @@ exports.save = function(fn){ * @api private */ -exports.update = function(fn){ - var self = this; - var url = this.url(); - fn = fn || noop; +exports.update = function() { + var args = [].slice.call(arguments), + fn = args.pop() || noop, + self = this, + changed = this.changed(), + update = this.model.sync.update || sync.update; + + if(!changed) return fn(null, this); if (!this.isValid()) return fn(new Error('validation failed')); - this.model.emit('saving', this); - this.emit('saving'); - request.put(url, self, function(res){ - if (res.error) return fn(error(res)); + + this.run('saving', function() { + update.apply(self, args.concat(res)); + }); + + function res(err) { + if(err) return fn(error(err)); self.dirty = {}; self.model.emit('save', self); self.emit('save'); - fn(); - }); + return fn(); + } }; /** @@ -275,4 +293,34 @@ exports.toJSON = function(){ function error(res) { return new Error('got ' + res.status + ' response'); -} \ No newline at end of file +} + +/** + * Run functions beforehand + * + * @param {String} event + * @param {Function} fn + * @api private + */ + +exports.run = function(event, done) { + var fns = this.model.listeners(event).concat(this.listeners(event)), + pending = 0; + + function next() { + if(!--pending) return done(); + } + + for (var i = 0, len = fns.length; i < len; i++) { + var fn = fns[i]; + if (fn.length > 1) { + pending++; + fn(this, next); + } else { + fn(this); + } + } + + if(!pending) return done(); +}; + diff --git a/lib/static.js b/lib/static.js index 29f64d2..7baca41 100644 --- a/lib/static.js +++ b/lib/static.js @@ -3,9 +3,10 @@ * Module dependencies. */ -var request = require('superagent') - , Collection = require('collection') - , noop = function(){}; +var sync = require('./sync'), + common = require('./common'), + Collection = common.collection, + noop = function(){}; /** * Construct a url to the given `path`. @@ -93,14 +94,18 @@ exports.attr = function(name, options){ * @api public */ -exports.removeAll = function(fn){ - fn = fn || noop; - var self = this; - var url = this.url('all'); - request.del(url, function(res){ - if (res.error) return fn(error(res)); +exports.removeAll = function() { + var args = [].slice.call(arguments), + fn = args.pop() || noop, + self = this, + removeAll = this.sync.removeAll || sync.removeAll; + + removeAll.apply(this, args.concat(res)); + + function res(err, body) { + if(err) return fn(error(err)); fn(); - }); + } }; /** @@ -110,17 +115,22 @@ exports.removeAll = function(fn){ * @api public */ -exports.all = function(fn){ - var self = this; - var url = this.url('all'); - request.get(url, function(res){ - if (res.error) return fn(error(res)); +exports.all = function() { + var args = [].slice.call(arguments), + fn = args.pop(), + self = this, + all = this.sync.all || sync.all; + + all.apply(this, args.concat(res)); + + function res(err, body) { + if (err) return fn(err); var col = new Collection; - for (var i = 0, len = res.body.length; i < len; ++i) { - col.push(new self(res.body[i])); + for (var i = 0, len = body.length; i < len; ++i) { + col.push(new self(body[i])); } fn(null, col); - }); + } }; /** @@ -131,14 +141,19 @@ exports.all = function(fn){ * @api public */ -exports.get = function(id, fn){ - var self = this; - var url = this.url(id); - request.get(url, function(res){ - if (res.error) return fn(error(res)); - var model = new self(res.body); +exports.get = function() { + var args = [].slice.call(arguments), + fn = args.pop(), + self = this, + get = this.sync.get || sync.get; + + get.apply(this, args.concat(res)); + + function res(err, body) { + if(err) return fn(err); + var model = new self(body); fn(null, model); - }); + } }; /** diff --git a/lib/sync.js b/lib/sync.js new file mode 100644 index 0000000..ba901d7 --- /dev/null +++ b/lib/sync.js @@ -0,0 +1,75 @@ +/** + * AJAX Transport + */ + +/** + * Module dependencies + */ + +var request = require('superagent'); + +/** + * All + */ + +exports.all = function(fn) { + var url = this.url('all'); + request.get(url, function(res) { + fn(res.error, res.body); + }); +}; + +/** + * Get + */ + +exports.get = function(id, fn) { + var url = this.url(id); + request.get(url, function(res) { + fn(res.error, res.body); + }); +}; + +/** + * removeAll + */ + +exports.removeAll = function(fn) { + var url = this.url('all'); + request.del(url, function(res) { + fn(res.error, res.body); + }); +}; + +/** + * save + */ + +exports.save = function(fn) { + var url = this.model.url(); + request.post(url, this, function(res) { + fn(res.error, res.body); + }); +}; + +/** + * update + */ + +exports.update = function(fn) { + var url = this.url(); + request.put(url, this, function(res) { + fn(res.error, res.body); + }); +}; + +/** + * remove + */ + +exports.remove = function(fn) { + var url = this.url(); + request.del(url, function(res) { + fn(res.error, res.body); + }); +}; diff --git a/package.json b/package.json index 5cf46aa..c47078b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,13 @@ { - "name": "model", - "version": "0.0.0", + "name": "node-model", + "version": "0.0.2", + "main": "lib/index.js", "devDependencies": { "express": "3.0.0" + }, + "dependencies": { + "is-browser": "~1.0.0", + "emitter-component": "0.0.6", + "superagent": "~0.10.0" } -} \ No newline at end of file +} diff --git a/test/model.js b/test/model.js index 0b576a3..adb5493 100644 --- a/test/model.js +++ b/test/model.js @@ -167,7 +167,9 @@ describe('Model#remove()', function(){ var pet = new Pet({ name: 'Tobi' }); pet.save(function(err){ assert(!err); - pet.on('removing', done); + pet.on('removing', function() { + done(); + }); pet.remove(); }); }) @@ -231,6 +233,20 @@ describe('Model#save(fn)', function(){ }); pet.save(); }) + + it('should handle async plugins', function(done){ + var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); + pet.on('saving', function(pet, next) { + setTimeout(function() { + next(); + }, 100); + }); + Pet.once('save', function(obj){ + assert(pet == obj); + done(); + }); + pet.save(); + }) }) describe('and invalid', function(){ @@ -270,7 +286,9 @@ describe('Model#save(fn)', function(){ var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); pet.save(function(err){ assert(!err); - pet.on('saving', done); + pet.on('saving', function() { + done(); + }); pet.save(); }); }) @@ -382,4 +400,4 @@ describe('Model#isValid()', function(){ assert(true === user.isValid()); assert(0 == user.errors.length); }) -}) \ No newline at end of file +})