resourceful
Advanced tools
Comparing version 0.1.10 to 0.2.0
@@ -30,3 +30,3 @@ var resourceful = require('../resourceful'); | ||
}); | ||
} | ||
} | ||
else { | ||
@@ -58,3 +58,3 @@ return this.store[id.toString()]; | ||
delete(this.store[id]); | ||
} | ||
} | ||
else { | ||
@@ -61,0 +61,0 @@ this.size = 0; |
@@ -8,2 +8,4 @@ /* | ||
*/ | ||
var util = require('util'), | ||
inflect = require('i')(); | ||
@@ -62,3 +64,7 @@ var common = exports; | ||
else if (derived === 'object') { | ||
return derived ? 'object' : 'null'; | ||
if (util.isRegExp(value)) { | ||
return 'regexp'; | ||
} else { | ||
return derived ? 'object' : 'null'; | ||
} | ||
} | ||
@@ -72,8 +78,4 @@ else if (derived === 'function') { | ||
common.capitalize = function (str) { | ||
return str && str[0].toUpperCase() + str.slice(1); | ||
}; | ||
common.capitalize = inflect.camelize; | ||
common.pluralize = function (s) { | ||
return /s$/.test(s) ? s : s + 's'; | ||
}; | ||
common.pluralize = inflect.pluralize; |
@@ -28,3 +28,3 @@ var events = require('events'), | ||
} | ||
else if (typeof engine === 'object') { | ||
else if (typeof engine === 'function') { | ||
this.engine = engine; | ||
@@ -31,0 +31,0 @@ } |
@@ -166,5 +166,5 @@ var common = require('./common'); | ||
var matcher = new RegExp('^' + prefix + '(.*)'); | ||
return this.sanitize(function (val) { | ||
return !matcher.test(val) | ||
return !matcher.test(val) | ||
? prefix + val | ||
@@ -171,0 +171,0 @@ : val; |
@@ -28,5 +28,6 @@ /* | ||
cache: false, | ||
secure: config.secure, | ||
auth: config && config.auth || null | ||
}).database(config.database || resourceful.env); | ||
this.cache = new resourceful.Cache(); | ||
@@ -53,7 +54,9 @@ }; | ||
if (e) { | ||
e.status = e.headers.status; | ||
if (e.headers) { | ||
e.status = e.headers.status; | ||
} | ||
return callback(e); | ||
} | ||
return Array.isArray(id) | ||
return Array.isArray(id) | ||
? callback(null, res.rows.map(function (r) { return r.doc; })) | ||
@@ -67,3 +70,3 @@ : callback(null, res); | ||
if (e) return callback(e); | ||
res.status = 201; | ||
@@ -87,3 +90,3 @@ callback(null, resourceful.mixin({}, doc, res)); | ||
doc = args.pop(); | ||
// if there's an ID left in args after popping off the callback and | ||
@@ -93,3 +96,3 @@ // the doc, then we need to PUT, otherwise create a new record thru POST | ||
return this.put.apply(this, arguments); | ||
} | ||
} | ||
@@ -99,3 +102,3 @@ // checks for presence of _id in doc, just in case the caller forgot | ||
if (doc._id) { | ||
return this.put.apply(doc._id, doc, callback); | ||
return this.put.apply(this, [doc._id, doc, callback]); | ||
} else { | ||
@@ -106,3 +109,3 @@ return this.post.call(this, doc, callback); | ||
Couchdb.prototype.update = function (id, doc, callback) { | ||
return this.cache.has(id) | ||
return this.cache.has(id) | ||
? this.put(id, resourceful.mixin({}, this.cache.get(id).toJSON(), doc), callback) | ||
@@ -125,8 +128,8 @@ : this.request('merge', id, doc, callback); | ||
return this.request.apply(this, ['remove', id, rev, callback]); | ||
} | ||
} | ||
if (this.cache.has(id)) { | ||
args.splice(1, -1, this.cache.get(id)._rev); | ||
return this.request.apply(this, ['remove'].concat(args)); | ||
} | ||
} | ||
this.head(id, function (e, headers, res) { | ||
@@ -136,3 +139,3 @@ if (res === 404 || !headers['etag']) { | ||
} | ||
if (headers.etag) { | ||
@@ -187,3 +190,3 @@ args.splice(1, -1, headers.etag.slice(1, -1)); | ||
} | ||
that.connection.put(id, factory._design, function (e, res) { | ||
@@ -193,5 +196,5 @@ if (e) { | ||
that.connection.create(function () { | ||
that.sync(callback); | ||
that.sync(factory, callback); | ||
}); | ||
} | ||
} | ||
else { | ||
@@ -198,0 +201,0 @@ |
@@ -12,6 +12,6 @@ var resourceful = require('../../resourceful'), | ||
this.uri = options.uri; | ||
this.increment = function () { | ||
return ++counter; | ||
} | ||
}; | ||
@@ -73,2 +73,6 @@ if (typeof(this.uri) === 'string') { | ||
// Forces key to be a string | ||
key += ''; | ||
val._id += ''; | ||
this.request(function () { | ||
@@ -104,3 +108,3 @@ var update = key in this.store; | ||
}); | ||
} | ||
}; | ||
@@ -107,0 +111,0 @@ Memory.prototype.find = function (conditions, callback) { |
@@ -6,3 +6,3 @@ | ||
} | ||
return !!init.type[schema.type] | ||
@@ -9,0 +9,0 @@ ? init.type[schema.type](schema) |
@@ -11,6 +11,6 @@ var util = require('util'), | ||
Object.defineProperty(this, 'isNewRecord', { | ||
value: true, | ||
value: true, | ||
writable: true | ||
}); | ||
Object.defineProperty(this, 'schema', { | ||
@@ -44,3 +44,3 @@ value: this.constructor.schema, | ||
if (hook) { | ||
if (hook && hook.length === 2) { | ||
hook(obj, function (e, obj) { | ||
@@ -56,3 +56,12 @@ if (e || obj) { | ||
}); | ||
} else { | ||
} | ||
else if (hook && hook.length === 1) { | ||
var res = hook(obj); | ||
if(res === true) { | ||
loop(hooks); | ||
} else { | ||
if (callback) { callback(res, obj); } | ||
} | ||
} | ||
else { | ||
finish(); | ||
@@ -69,3 +78,3 @@ } | ||
if (hook) { | ||
if (hook && hook.length === 3) { | ||
hook(e, obj, function (e, obj) { | ||
@@ -75,3 +84,12 @@ if (e) { finish(e, obj); } | ||
}); | ||
} else { | ||
} | ||
else if (hook && hook.length === 2) { | ||
var res = hook(e, obj); | ||
if (res === true) { | ||
loop(hooks); | ||
} else { | ||
finish(res, obj); | ||
} | ||
} | ||
else { | ||
finish(); | ||
@@ -114,2 +132,3 @@ } | ||
if (obj) args.push(obj.properties ? obj.properties : obj); | ||
else obj = this.connection.cache.get(key) || {_id: key}; | ||
@@ -134,3 +153,3 @@ this.runBeforeHooks(method, obj, callback, function () { | ||
result = result.map(function (r) { | ||
return resourceful.instantiate.call(that, r); | ||
return r ? resourceful.instantiate.call(that, r) : r; | ||
}); | ||
@@ -169,3 +188,4 @@ } else { | ||
if (e) { that.emit('error', e); } | ||
else { that.emit(method, res || result); } | ||
else { that.emit(method, | ||
method === 'destroy' ? obj : res || result); } | ||
if (callback) { | ||
@@ -186,3 +206,3 @@ callback(e || null, result); | ||
return id | ||
return id | ||
? this._request('get', id, callback) | ||
@@ -202,6 +222,26 @@ : callback && callback(new Error('key is undefined')); | ||
var that = this; | ||
this.runBeforeHooks("create", attrs, callback, function () { | ||
var instance = new(that)(attrs); | ||
// could happen after validate, but would unnecessarily remove the validation safety net | ||
var instance = new(that)(attrs); | ||
var validate = that.prototype.validate(instance, that.schema); | ||
if (!validate.valid) { | ||
var e = { validate: validate, value: attrs, schema: that.schema }; | ||
that.emit('error', e); | ||
if (callback) { | ||
callback(e); | ||
} | ||
return; | ||
} | ||
this.runBeforeHooks("create", instance, callback, function (err, result) { | ||
if (!validate.valid) { | ||
var e = { validate: validate, value: attrs, schema: that.schema }; | ||
that.emit('error', e); | ||
if (callback) { | ||
callback(e); | ||
} | ||
return; | ||
} | ||
that.runAfterHooks("create", null, instance, function (e, res) { | ||
@@ -211,14 +251,2 @@ if (e) { | ||
} | ||
var validate = that.prototype.validate(instance, that.schema); | ||
// Why is this here? We are validating in Resource.save | ||
if (!validate.valid) { | ||
that.emit('error', validate.errors); | ||
if (callback) { | ||
callback(validate.errors); | ||
} | ||
return; | ||
} | ||
instance.save(function (e, res) { | ||
@@ -229,3 +257,2 @@ if (res) { | ||
} | ||
if (callback) { | ||
@@ -243,3 +270,4 @@ callback(e, instance); | ||
if (!validate.valid) { | ||
return callback && callback(validate.errors); | ||
var e = { validate: validate, value: instance, schema: that.schema }; | ||
return callback && callback(e); | ||
} | ||
@@ -261,4 +289,4 @@ | ||
} | ||
return id | ||
return id | ||
? this._request('destroy', id, callback) | ||
@@ -272,8 +300,29 @@ : callback && callback(new Error('key is undefined')); | ||
} | ||
if (this._timestamps) { | ||
obj.mtime = Date.now(); | ||
} | ||
return id | ||
var self = this, | ||
partialSchema = { properties: {} }, | ||
validate; | ||
Object.keys(obj).forEach(function (key) { | ||
if (self.schema.properties[key]) { | ||
partialSchema.properties[key] = self.schema.properties[key]; | ||
} | ||
}); | ||
validate = this.prototype.validate({ _properties: obj }, partialSchema); | ||
if (!validate.valid) { | ||
var e = { validate: validate, value: obj, schema: this.schema }; | ||
this.emit('error', e); | ||
if (callback) { | ||
callback(e); | ||
} | ||
return; | ||
} | ||
return id | ||
? this._request('update', id, obj, callback) | ||
@@ -435,5 +484,23 @@ : callback && callback(new Error('key is undefined')); | ||
function notifyParent(c, callback) { | ||
factory.get(id, function(err, p) { | ||
if(err) { | ||
if(callback) return callback(err); | ||
} | ||
p[rstring + '_ids'].push(c._id || c.id); | ||
p.save(callback); | ||
}); | ||
} | ||
if (child instanceof rfactory) { | ||
child[key] = id; | ||
child.save(callback); | ||
child._id = id + '/' + child._id; | ||
child.save(function(err) { | ||
if(err) { | ||
if(callback) return callback(err); | ||
} | ||
notifyParent(child, callback); | ||
}); | ||
} else { | ||
@@ -443,5 +510,18 @@ var inheritance = {}; | ||
child = resourceful.mixin({}, child, inheritance); | ||
rfactory.create(child, callback); | ||
child._id = id + '/' + child._id; | ||
rfactory.create(child, function(err, c) { | ||
if(err) { | ||
if(callback) return callback(err); | ||
} | ||
notifyParent(c, function(e) { | ||
if(e) { | ||
if(callback) return callback(e); | ||
} else { | ||
if(callback) callback(err, c); | ||
} | ||
}); | ||
}); | ||
} | ||
}; | ||
} | ||
@@ -485,2 +565,24 @@ // | ||
factory.before('destroy', function(obj, next) { | ||
factory.get(obj, function(e, i) { | ||
if(e) { return next(e); } | ||
rfactory.get(i[rstring + '_id'], function(err, p) { | ||
if(err) { return next(err); } | ||
var key = factory.resource.toLowerCase() + '_ids'; | ||
if(~p[key].indexOf(obj)) { | ||
p[key].splice(p[key].indexOf(obj), 1); | ||
p.save(function(err) { | ||
if(err) { return next(err); } | ||
next(); | ||
}); | ||
} | ||
else { | ||
next(); | ||
} | ||
}); | ||
}); | ||
}); | ||
// Notify parent about new child | ||
@@ -559,4 +661,18 @@ rfactory.child(factory); | ||
this._timestamps = true; | ||
this.property('ctime', 'number'); | ||
this.property('mtime', 'number'); | ||
// | ||
// Remark: All timestamps should be considered Unix time format, | ||
// see: http://en.wikipedia.org/wiki/Unix_time | ||
// | ||
// | ||
// The time the resource was created | ||
// | ||
this.property('ctime', 'number', { format: "unix-time", private: true }); | ||
// | ||
// The last time the resource was modified | ||
// | ||
this.property('mtime', 'number', { format: "unix-time", private: true }); | ||
// | ||
// The last time the resource was accessed | ||
// | ||
// TODO: this.property('atime', 'number', { format: "unix-time", private: true }); | ||
}; | ||
@@ -632,3 +748,3 @@ | ||
return this.constructor.saveAttachment({ | ||
id: this._id, | ||
id: this._id, | ||
rev: this._rev | ||
@@ -682,3 +798,3 @@ }, attachment, callback); | ||
Resource.prototype.__defineGetter__('id', function () { | ||
return this.constructor.key === '_id' | ||
return this.constructor.key === '_id' | ||
? this._id | ||
@@ -685,0 +801,0 @@ : undefined; |
{ | ||
"name": "resourceful", | ||
"description": "A storage agnostic resource-oriented ODM for building prototypical models with validation and sanitization.", | ||
"version": "0.1.10", | ||
"version": "0.2.0", | ||
"url": "http://github.com/flatiron/resourceful", | ||
"keywords": ["ODM", "database", "couchdb", "model", "resource"], | ||
"author": "Charlie Robbins <charlie@nodejitsu.com>", | ||
"contributors": [ | ||
{ "name": "Alexis Sellier", "email": "self@cloudhead.io" }, | ||
{ "name": "Fedor Indutny", "email": "fedor@indutny.com" }, | ||
{ "name": "Robert Sköld", "email": "robert@publicclass.se" } | ||
"keywords": [ | ||
"ODM", | ||
"database", | ||
"couchdb", | ||
"model", | ||
"resource" | ||
], | ||
"author": "Nodejitsu Inc. <info@nodejitsu.com>", | ||
"maintainers": [ | ||
"indexzero <charlie@nodejitsu.com>", | ||
"indutny <fedor.indutny@gmail.com>" | ||
], | ||
"repository": { | ||
@@ -19,3 +24,4 @@ "type": "git", | ||
"cradle": "0.6.x", | ||
"revalidator": "0.1.x" | ||
"i": "0.3.x", | ||
"revalidator": "0.1.x" | ||
}, | ||
@@ -26,3 +32,5 @@ "devDependencies": { | ||
"main": "./lib/resourceful", | ||
"engines": { "node": ">= 0.6.0" }, | ||
"engines": { | ||
"node": ">= 0.6.0" | ||
}, | ||
"scripts": { | ||
@@ -33,1 +41,2 @@ "browserify": "browserify lib/browser.js -o build/resourceful.js", | ||
} | ||
@@ -145,3 +145,3 @@ | ||
assert: function (val) { | ||
conform: function (val) { | ||
return val % 2 === 0; | ||
@@ -161,3 +161,3 @@ } | ||
.maximum(8) | ||
.assert(function (val) { return val % 2 === 0 }); | ||
.conform(function (val) { return val % 2 === 0 }); | ||
``` | ||
@@ -164,0 +164,0 @@ |
@@ -541,3 +541,21 @@ var path = require('path') | ||
} | ||
}).export(module) | ||
}).addBatch(multipleGet(e)).export(module); | ||
}); | ||
function multipleGet(e) { | ||
if (e.name == 'memory') return {}; | ||
return { | ||
"Getting multiple objects from ids when one object isn't found": { | ||
topic: function () { | ||
resources[e].Author.get(['bob', 'pun', 'tim'], this.callback); | ||
}, | ||
"should be successful": function (err, obj) { | ||
assert.isNull(err); | ||
assert.equal(obj.length, 3); | ||
assert.equal(obj[0]._id, 'bob'); | ||
assert.equal(obj[1], null); | ||
assert.equal(obj[2]._id, 'tim'); | ||
} | ||
} | ||
}; | ||
}; |
@@ -21,3 +21,3 @@ var vows = require('vows'), | ||
author.createArticle({ | ||
_id: author._id + '-article-1', | ||
_id: 'article-1', | ||
title: name + '\'s article #1' | ||
@@ -31,3 +31,3 @@ }, this.callback); | ||
author.createArticle({ | ||
_id: author._id + '-article-2', | ||
_id: 'article-2', | ||
title: name + '\'s article #2' | ||
@@ -42,2 +42,38 @@ }, this.callback); | ||
function authorAndArticlesWithoutId(name) { | ||
return { | ||
topic: function () { | ||
this.Author.create({ | ||
name: name | ||
}, this.callback); | ||
}, | ||
'should exist': function (err, author) { | ||
assert.equal(author._id, '1'); | ||
assert.isNull(err); | ||
}, | ||
'with': { | ||
'article #3': { | ||
topic: function (author) { | ||
var self = this; | ||
author.createArticle({ | ||
title: name + '\'s article #3' | ||
}, function(err, i) { | ||
self.callback(err, author); | ||
}); | ||
}, | ||
'should exist': function (err, author) { | ||
author.articles(function(err, articles) { | ||
assert.isArray(articles); | ||
assert.equal(articles[0].title, name + '\'s article #3'); | ||
}); | ||
this.Article.byAuthor('1', function(err, articles) { | ||
assert.isArray(articles); | ||
assert.equal(articles[0].title, name + '\'s article #3'); | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
function category(parentName, childName){ | ||
@@ -47,3 +83,3 @@ return { | ||
this.Category.create({ | ||
_id: 'category-' + parentName, | ||
_id: parentName, | ||
name: parentName | ||
@@ -59,3 +95,3 @@ }, this.callback) | ||
parent.createCategory({ | ||
_id: 'category-' + childName, | ||
_id: childName, | ||
name: childName | ||
@@ -73,7 +109,7 @@ }, this.callback) | ||
function categoryParentTest(name) { | ||
var parent_id = 'category-' + name; | ||
var parent_id = name; | ||
return { | ||
topic: function(){ | ||
// FIXME category pluralized should be categories (maybe use https://github.com/MSNexploder/inflect?) | ||
this.Category.categorys(parent_id, this.callback); | ||
this.Category.categories(parent_id, this.callback); | ||
}, | ||
@@ -99,5 +135,4 @@ 'should return the children': function(err, children){ | ||
function categoryChildTest(name) { | ||
var child_id = 'category-' + name; | ||
function categoryChildTest(parentName, childName) { | ||
var child_id = parentName + '/' + childName; | ||
return { | ||
@@ -109,3 +144,3 @@ topic: function(){ | ||
assert.isNull(err); | ||
assert.equal(child.name, name); | ||
assert.equal(child.name, childName); | ||
}, | ||
@@ -118,3 +153,3 @@ 'and child.category()': { | ||
assert.isNull(err); | ||
assert.notEqual(parent.name, name); | ||
assert.notEqual(parent.name, childName); | ||
} | ||
@@ -208,3 +243,4 @@ } | ||
'Author #2': authorAndArticles('bob'), | ||
'Category #1 & #2': category('alice', 'bob') | ||
'Author #3': authorAndArticlesWithoutId('lenny'), | ||
'Category #1 & #2': category('hip-hop', 'a-tribe-called-quest') | ||
} | ||
@@ -227,5 +263,5 @@ } | ||
'Article.byAuthor(\'bob\')': articleTest('bob'), | ||
'Category.categories()': categoryParentTest('alice'), | ||
'Category.category()': categoryChildTest('bob') | ||
'Category.categories()': categoryParentTest('hip-hop'), | ||
'Category.category()': categoryChildTest('hip-hop', 'a-tribe-called-quest') | ||
} | ||
}).export(module); |
@@ -337,2 +337,14 @@ var assert = require('assert'), | ||
} | ||
}, | ||
"Engine can be initialised":{ | ||
"by name": function () { | ||
resourceful.use('memory'); | ||
assert.isFunction(resourceful.engine); | ||
assert.equal(resourceful.connection.protocol, 'memory'); | ||
}, | ||
"by reference": function () { | ||
resourceful.use(resourceful.engines.Memory); | ||
assert.isFunction(resourceful.engine); | ||
assert.equal(resourceful.connection.protocol, 'memory'); | ||
} | ||
} | ||
@@ -339,0 +351,0 @@ }).addVows({ |
173146
36
3967
3