Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[merge]

  • Loading branch information...
commit 463b45ad4638dd525f435a0dc6ac53e89f89eb14 2 parents 09269bb + 680b02c
@Marak Marak authored
Showing with 1,643 additions and 674 deletions.
  1. +11 −0 examples/create-with-no-id.js
  2. +11 −0 examples/create.js
  3. +16 −0 examples/destroy.js
  4. +15 −0 examples/find.js
  5. +10 −0 examples/get-multiple.js
  6. +20 −0 examples/get.js
  7. +4 −4 examples/relationship-one-to-many.js
  8. +13 −9 examples/relationship-parent-child.js
  9. +14 −0 examples/save.js
  10. +14 −0 examples/update.js
  11. +8 −3 lib/resourceful.js
  12. +7 −4 lib/resourceful/cache.js
  13. +5 −0 lib/resourceful/common.js
  14. +4 −4 lib/resourceful/core.js
  15. +94 −39 lib/resourceful/engines/couchdb/index.js
  16. +3 −1 lib/resourceful/engines/couchdb/view.js
  17. +13 −11 lib/resourceful/engines/memory.js
  18. +188 −97 lib/resourceful/resource.js
  19. +5 −3 package.json
  20. +10 −4 test/cache-test.js
  21. +7 −8 test/{couchdb → }/couchdb-filter-test.js
  22. +0 −55 test/couchdb/couchdb-relationship-fix-test.js
  23. +0 −106 test/couchdb/couchdb-relationship-test.js
  24. +156 −74 test/engines-test.js
  25. +7 −2 test/engines/memory.js
  26. +1 −1  test/events-test.js
  27. +15 −0 test/fixtures/index.js
  28. +254 −0 test/fixtures/relationship.js
  29. +4 −4 test/hooks-async-test.js
  30. +19 −7 test/hooks-sync-test.js
  31. +59 −0 test/macros/index.js
  32. +135 −0 test/macros/relationship.js
  33. +507 −221 test/relationship-test.js
  34. +11 −14 test/resourceful-test.js
  35. +3 −3 test/validation-test.js
View
11 examples/create-with-no-id.js
@@ -0,0 +1,11 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ name: 'Marak'
+}, function(err, marak){
+ console.log(marak);
+});
View
11 examples/create.js
@@ -0,0 +1,11 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak'
+}, function(err, marak){
+ console.log(marak);
+});
View
16 examples/destroy.js
@@ -0,0 +1,16 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('couchdb', { database: "test2"} );
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak'
+}, function(err, marak){
+ Author.destroy('Marak', function(err, result){
+ Author.get('Marak', function(err, result){
+ console.log(err, result);
+ })
+ })
+});
+
View
15 examples/find.js
@@ -0,0 +1,15 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('couchdb', { database: "test2"} );
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak',
+ name: "brown"
+}, function(err, marak){
+ Author.find({name: "brown"}, function(err, result){
+ console.log(err, result);
+ })
+});
+
View
10 examples/get-multiple.js
@@ -0,0 +1,10 @@
+var resourceful = require('../lib/resourceful');
+
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.get(['tim', 'bob', 'mat'], function(err, people){
+ console.log(err, people)
+});
View
20 examples/get.js
@@ -0,0 +1,20 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak'
+}, function(err, marak){
+ //
+ // "marak" exists already in scope from the create
+ //
+ console.log(marak);
+ //
+ // We can also re-fetch the resource
+ //
+ Author.get('Marak', function(err, result){
+ console.log(err, result);
+ })
+});
View
8 examples/relationship-one-to-many.js
@@ -1,5 +1,5 @@
var resourceful = require('../lib/resourceful');
-resourceful.use('memory' );
+resourceful.use('memory');
//
// First, create two resources: Author and Article
@@ -19,12 +19,12 @@ Article.parent('author');
// Create a new author "bob"
//
Author.create({
- _id: 'bob'
+ id: 'bob'
}, function(err, bob){
//
// Create a new article for bob
//
- bob.createArticle({ _id: "cool-story", title: "A cool story by bob." }, function(err, result){
+ bob.createArticle({ id: "cool-story", title: "A cool story by bob." }, function(err, result){
console.log(bob.article_ids);
//
// Get all of bob's articles
@@ -32,7 +32,7 @@ Author.create({
bob.articles(function(err, result){
console.log(result);
});
- Article.get('bob/cool-story', function(err, result){
+ Article.get('author/bob/cool-story', function(err, result){
console.log(result);
})
});
View
22 examples/relationship-parent-child.js
@@ -1,22 +1,26 @@
var resourceful = require('../lib/resourceful');
resourceful.use('memory');
+//
+// TODO: This seems to not be working on couchdb. Test coverage should be added for this.
+//
+
var Category = resourceful.define('category');
Category.parent('category');
Category.create({
- _id: 'music',
+ id: 'music',
}, function(err, music){
music.createCategory({
- _id: 'hip-hop',
+ id: 'hip-hop',
}, function(err, hiphop){
music.createCategory({
- _id: 'rap',
+ id: 'rap',
}, function(err, rap){
- hiphop.createCategory({ _id: "a-tribe-called-quest", title: "Hello!" }, function(err, result){
- hiphop.createCategory({ _id: "busta-rhymes", title: "Hello!" }, function(err, busta){
- rap.createCategory({ _id: "wu-tang", title: "Hello!" }, function(err, wutang){
- wutang.createCategory({ _id: "Enter the 36 Chambers", title: "Hello!" }, function(err, result){
+ hiphop.createCategory({ id: "a-tribe-called-quest", title: "Hello!" }, function(err, result){
+ hiphop.createCategory({ id: "busta-rhymes", title: "Hello!" }, function(err, busta){
+ rap.createCategory({ id: "wu-tang", title: "Hello!" }, function(err, wutang){
+ wutang.createCategory({ id: "Enter the 36 Chambers", title: "Hello!" }, function(err, result){
music.categories(function(err, result){
console.log('music', err, result);
});
@@ -32,8 +36,8 @@ Category.create({
Category.categories('music', function(err, result){
console.log('music', result)
});
- Category.categories('music/rap', function(err, result){
- console.log('music/rap', result)
+ Category.categories('category/music/rap', function(err, result){
+ console.log('category/music/rap', result)
});
});
});
View
14 examples/save.js
@@ -0,0 +1,14 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak'
+}, function(err, marak){
+ marak.name = "foobar";
+ marak.save(function(err, result){
+ console.log(err, result);
+ })
+});
View
14 examples/update.js
@@ -0,0 +1,14 @@
+var resourceful = require('../lib/resourceful');
+resourceful.use('memory');
+
+var Author = resourceful.define('author');
+Author.string('name');
+
+Author.create({
+ id: 'Marak',
+ name: 'foobar'
+}, function(err, marak){
+ marak.update({ name: 'barfoo'}, function(err, result){
+ console.log(err, result);
+ })
+});
View
11 lib/resourceful.js
@@ -11,17 +11,22 @@ resourceful.init = require('./resourceful/init');
resourceful.use = require('./resourceful/core').use;
resourceful.connect = require('./resourceful/core').connect;
-resourceful.typeOf = require('./resourceful/common').typeOf;
resourceful.connection = require('./resourceful/core').connection;
-resourceful.mixin = require('./resourceful/common').mixin;
-resourceful.clone = require('./resourceful/common').clone;
resourceful.resources = require('./resourceful/core').resources;
resourceful.register = require('./resourceful/core').register;
resourceful.unregister = require('./resourceful/core').unregister;
resourceful.engines = require('./resourceful/engines');
resourceful.instantiate = require('./resourceful/core').instantiate;
+
+resourceful.typeOf = require('./resourceful/common').typeOf;
+resourceful.mixin = require('./resourceful/common').mixin;
+resourceful.clone = require('./resourceful/common').clone;
+resourceful.async = require('./resourceful/common').async;
resourceful.capitalize = require('./resourceful/common').capitalize;
resourceful.pluralize = require('./resourceful/common').pluralize;
+resourceful.lowerize = require('./resourceful/common').lowerize;
resourceful.render = require('./resourceful/common').render;
resourceful.resources.Resource = resourceful.define('resource');
+
+resourceful.uuid = require('node-uuid');
View
11 lib/resourceful/cache.js
@@ -1,6 +1,7 @@
var resourceful = require('../resourceful');
exports.cache = true;
+
exports.caches = {
stores: [],
push: function (store) {
@@ -25,27 +26,29 @@ exports.Cache.prototype.get = function (id) {
if (!id) { return; }
else if (Array.isArray(id)) {
return id.map(function (k) {
- return that.store[k.toString()];
+ return JSON.parse(that.store[k.toString()] || "null");
});
}
else {
- return this.store[id.toString()];
+ return JSON.parse(this.store[id.toString()] || "null");
}
};
exports.Cache.prototype.put = function (id, obj) {
if (!resourceful.cache) return;
if (!this.has(id)) this.size++;
- this.store[id] = obj;
+ this.store[id] = JSON.stringify(obj);
};
exports.Cache.prototype.update = function (id, obj) {
if (!resourceful.cache) return;
if (id in this.store) {
+ var jsObj = JSON.parse(this.store[id] || "{}");
for (var k in obj) {
- try { this.store[id][k] = obj[k]; }
+ try { jsObj[k] = obj[k]; }
catch (ex) { }
}
+ this.store[id] = JSON.stringify(jsObj);
}
};
View
5 lib/resourceful/common.js
@@ -6,6 +6,7 @@
*
*/
var util = require('util'),
+ async = require('async'),
inflect = require('i')();
var common = exports;
@@ -73,6 +74,10 @@ common.typeOf = function (value) {
return derived;
};
+common.async = async;
+
common.capitalize = inflect.camelize;
common.pluralize = inflect.pluralize;
+
+common.lowerize = inflect.underscore;
View
8 lib/resourceful/core.js
@@ -32,6 +32,7 @@ resourceful.use = function (engine, options) {
throw new Error("invalid engine ");
}
+ this.key = this.engine.key || 'id';
this.connect(options || {});
return this;
};
@@ -128,16 +129,15 @@ resourceful.define = function (name, definition) {
// in the Resource Factory.
//
Factory.resource = name;
- Factory.key = '_id';
+ Factory.lowerResource = common.lowerize(name);
+
Factory.views = {};
Factory._children = [];
Factory._parents = [];
Factory.schema = {
name: name,
- properties: {
- _id: { type: 'string', unique: true }
- },
+ properties: {},
links: []
};
View
133 lib/resourceful/engines/couchdb/index.js
@@ -56,18 +56,36 @@ Couchdb.prototype.get = function (id, callback) {
return callback(e);
}
- return Array.isArray(id)
- ? callback(null, res.rows.map(function (r) { return r.doc; }))
- : callback(null, res);
+ if (Array.isArray(id)) {
+ res = res.rows.map(function (r) {
+ if (r.doc) {
+ r.doc.id = r.doc._id;
+ delete r.doc._id;
+ }
+ return r.doc;
+ });
+ } else {
+ res.id = res._id;
+ delete res._id;
+ }
+
+ return callback(null, res);
});
};
Couchdb.prototype.put = function (id, doc, callback) {
+ delete doc.id;
return this.request('put', id, doc, function (e, res) {
- if (e) return callback(e);
+ if (e) {
+ if (e.headers) {
+ e.status = e.headers.status;
+ }
+ return callback(e);
+ }
- res.status = 201;
- callback(null, resourceful.mixin({}, doc, res));
+ doc._rev = res.rev;
+ doc.id = id;
+ callback(null, doc);
});
};
@@ -75,8 +93,9 @@ Couchdb.prototype.post = Couchdb.prototype.create = function (doc, callback) {
return this.request('post', doc, function (e, res) {
if (e) return callback(e);
- res.status = 201;
- callback(null, resourceful.mixin({}, doc, res));
+ doc.id = res.id;
+ doc._rev = res.rev;
+ callback(null, doc);
});
};
@@ -93,23 +112,35 @@ Couchdb.prototype.save = function (id, doc, callback) {
// checks for presence of _id in doc, just in case the caller forgot
// to add an id as first argument
- if (doc._id) {
- return this.put.apply(this, [doc._id, doc, callback]);
+ if (doc.id) {
+ return this.put.apply(this, [doc.id, doc, callback]);
} else {
return this.post.call(this, doc, callback);
}
};
+
Couchdb.prototype.update = function (id, doc, callback) {
- return this.cache.has(id)
- ? this.put(id, resourceful.mixin({}, this.cache.get(id).toJSON(), doc), callback)
- : this.request('merge', id, doc, callback);
+ if (this.cache.has(id)) {
+ var r = this.cache.get(id);
+ this.put(id, resourceful.mixin({}, r, doc), callback)
+ } else {
+ var that = this;
+ this.request('merge', id, doc, function (err, res) {
+ that.get(id, callback);
+ });
+ }
};
-Couchdb.prototype.destroy = function () {
- var that = this,
- args = Array.prototype.slice.call(arguments),
- id = args[0],
- rev;
+Couchdb.prototype.destroy = function (id, callback) {
+ var that = this, args, rev,
+ cb = callback;
+
+ callback = function (err, res) {
+ if (err) return cb(err);
+ cb(null, that.cache.get(id) || { id: res.id, _rev: res.rev });
+ };
+
+ args = [id, callback];
if (Array.isArray(id)) {
rev = id[1];
@@ -121,12 +152,13 @@ Couchdb.prototype.destroy = function () {
}
if (this.cache.has(id)) {
args.splice(1, -1, this.cache.get(id)._rev);
+ args[0] = id;
return this.request.apply(this, ['remove'].concat(args));
}
this.head(id, function (e, headers, res) {
if (res === 404 || !headers['etag']) {
- e = e || { reason: 'not_found' };
+ e = e || { reason: 'not_found', status: 404 };
}
if (headers.etag) {
@@ -142,20 +174,40 @@ Couchdb.prototype.view = function (path, opts, callback) {
callback(null, res.rows.map(function (r) {
// With `include_docs=true`, the 'doc' attribute is set instead of 'value'.
- return r.doc || r.value;
+ if (r.doc && r.doc._id) {
+ r.doc.id = r.doc._id;
+ delete r.doc._id;
+ return r.doc;
+ } else {
+ if (r.value._id) {
+ r.value.id = r.value._id;
+ delete r.value._id;
+ }
+ return r.value;
+ }
}));
});
};
Couchdb.prototype.find = function (conditions, callback) {
- mapFunction = "function (doc) { var obj = " + JSON.stringify(conditions) + "; if (function () { ";
- mapFunction+= "for (var k in obj) { if (obj[k] !== doc[k]) return false; } return true;";
- mapFunction+= "}()) { emit(doc._id, doc); }}";
- this.connection.temporaryView({
- map: mapFunction
- }, function (e, res) {
+ this.connection.temporaryView(resourceful.render({
+ map: function (doc) {
+ var obj = $conditions;
+ if (function () {
+ for (var k in obj) {
+ if (obj[k] !== doc[k]) return false;
+ }
+ return true;
+ }()) {
+ doc.id = doc._id.split('/').slice(1).join('/');
+ delete doc._id;
+ emit(doc.id, doc);
+ }
+ }
+ }, { conditions: JSON.stringify(conditions) }), function (e, res) {
if (e) return callback(e);
callback(null, res.rows.map(function (r) {
+ r.value.id = r.key;
// With `include_docs=true`, the 'doc' attribute is set instead of 'value'.
return r.doc || r.value;
}));
@@ -208,17 +260,20 @@ Couchdb.prototype.sync = function (factory, callback) {
//
// Relationship hook
//
-Couchdb._byParent = function (factory, parent, rfactory) {
- factory.filter('by' + parent, { include_docs: true }, resourceful.render({
- map: function (doc) {
- if (doc.resource === $resource) {
- for (var i = 0; i < doc.$children.length; i++) {
- emit(doc._id, { _id: doc.$children[i] });
- }
- }
- }
- }, {
- resource: JSON.stringify(parent),
- children: (''+factory.resource).toLowerCase() + '_ids'
- }));
+Couchdb._byParent = function (factory, rfactory) {
+ var conn = this
+ , parent = rfactory.lowerResource
+ , child = factory.lowerResource;
+
+ factory['by' + rfactory.resource] = function (id, callback) {
+ rfactory.get.call(rfactory, id, function (err, res) {
+ if (err) return callback(err);
+
+ var children = res[child + '_ids'].map(function (e) {
+ return parent + "/" + id + "/" + e;
+ });
+
+ factory.get.call(factory, children, callback);
+ });
+ };
};
View
4 lib/resourceful/engines/couchdb/view.js
@@ -39,7 +39,7 @@ exports.filter = function (name /* [options], filter */) {
if (object[k] !== doc[k]) return false;
}
return true;
- }()) { emit(doc._id, doc); }
+ }()) { emit(doc.id, doc); }
}
}
}, { object: filter, resource: JSON.stringify(R.resource) });
@@ -48,6 +48,8 @@ exports.filter = function (name /* [options], filter */) {
R.views[name] = resourceful.render({
map: function (doc) {
if (doc.resource === $resource) {
+ doc.id = doc._id;
+ delete doc._id;
emit($key, doc);
}
}
View
24 lib/resourceful/engines/memory.js
@@ -38,7 +38,7 @@ Memory.prototype.load = function (data) {
if (data instanceof Array) {
var tmp = {};
data.forEach(function (e) {
- tmp[e._id] = e;
+ tmp[e.id] = JSON.stringify(e);
});
data = tmp;
}
@@ -47,7 +47,7 @@ Memory.prototype.load = function (data) {
// Update cache
if (this.uri) {
- exports.stores[this.uri] = data;
+ exports.stores[this.uri] = JSON.parse(JSON.stringify(this.store));
}
return this;
@@ -66,17 +66,16 @@ Memory.prototype.save = function (key, val, callback) {
var callback = args.pop(), val = args.pop();
if (!args.length || !key) {
key = this.increment();
- val._id = key;
+ val.id = key;
}
// Forces key to be a string
key += '';
- val._id += '';
+ val.id += '';
this.request(function () {
- var update = key in this.store;
- this.store[key] = val;
- callback(null, resourceful.mixin({ status: update ? 200 : 201 }, val));
+ this.store[key] = JSON.stringify(val);
+ callback(null, val);
});
};
@@ -85,14 +84,15 @@ Memory.prototype.put = function () {
};
Memory.prototype.update = function (key, obj, callback) {
- this.put(key, resourceful.mixin({}, this.store[key], obj), callback);
+ var jsObj = JSON.parse(this.store[key] || "{}");
+ this.put(key, resourceful.mixin({}, jsObj, obj), callback);
};
Memory.prototype.get = function (key, callback) {
this.request(function () {
key = key.toString();
return key in this.store ?
- callback(null, this.store[key])
+ callback(null, JSON.parse(this.store[key] || "null"))
:
callback({ status: 404 });
});
@@ -119,8 +119,10 @@ Memory.prototype.filter = function (filter, callback) {
store = this.store;
Object.keys(this.store).forEach(function (k) {
- if (filter(store[k])) {
- result.push(store[k]);
+ var obj = JSON.parse(store[k]);
+ if (filter(obj)) {
+ obj.id = obj.id.split('/').slice(1).join('/');
+ result.push(obj);
}
});
View
285 lib/resourceful/resource.js
@@ -114,17 +114,21 @@ Resource.unregister = function () {
return resourceful.unregister(this.resource);
};
-Resource._request = function (/* method, [key, obj], callback */) {
+Resource._request = function (/* method, [id, obj], callback */) {
var args = Array.prototype.slice.call(arguments),
that = this,
+ key = this.key,
callback = args.pop(),
method = args.shift(),
- key = args.shift(),
+ id = args.shift(),
obj = args.shift();
- if (key) args.push(key);
+ if (id) args.push(id);
if (obj) args.push(obj.properties ? obj.properties : obj);
- else obj = this.connection.cache.get(key) || {_id: key};
+ else {
+ obj = that.connection.cache.get(id) || {};
+ obj[key] = id;
+ }
this.runBeforeHooks(method, obj, callback, function () {
args.push(function (e, result) {
@@ -148,38 +152,16 @@ Resource._request = function (/* method, [key, obj], callback */) {
});
} else {
if (method === 'destroy') {
- that.connection.cache.clear(key);
+ that.connection.cache.clear(id);
} else {
- if (result.rev) {
- // Set the revision if available, so it can be saved
- // to the cache. If we're saving a new object,
- // '_rev' won't be a member of 'resource._properties'
- // so we need to set it here so resource.toJSON() includes it.
- if (obj instanceof resourceful.Resource && !obj._rev) {
- resourceful.defineProperty(obj, '_rev');
- }
- obj._rev = result.rev;
- }
-
- if (method === 'save') {
- obj._id = result._id || result.id;
- }
-
- result = resourceful.instantiate.call(that, ['get', 'update'].indexOf(method) !== -1 ? result : obj);
-
- if (method === 'update') {
- // TODO: Not necessary for identity map
- that.connection.cache.update(key, obj);
- } else {
- // TODO: We only need to do this if the key doesn't exist
- that.connection.cache.put(key, result);
- }
+ that.connection.cache.put(result[key], result);
+ result = resourceful.instantiate.call(that, result);
}
}
- that.runAfterHooks(method, null, obj, function (e, res) {
+
+ that.runAfterHooks(method, null, result, function (e, res) {
if (e) { that.emit('error', e); }
- else { that.emit(method,
- method === 'destroy' ? obj : res || result); }
+ else { that.emit(method, res || result); }
if (callback) {
callback(e || null, result);
}
@@ -191,13 +173,54 @@ Resource._request = function (/* method, [key, obj], callback */) {
};
Resource.get = function (id, callback) {
- if (this.schema.properties._id && this.schema.properties._id.sanitize) {
- id = this.schema.properties._id.sanitize(id);
+ var key = this.key;
+
+ if (this.schema.properties[key] && this.schema.properties[key].sanitize) {
+ id = this.schema.properties[key].sanitize(id);
}
- return id
- ? this._request('get', id, callback)
- : callback && callback(new Error('key is undefined'));
+ var newid, oldid;
+ if (id && id[key]) {
+ newid = this.lowerResource + "/" + id[key];
+ oldid = id[key];
+ }
+ else if(Array.isArray(id)) {
+ for(var i in id) {
+ id[i] = this.lowerResource + "/" + id[i];
+ }
+ newid = id;
+ }
+ else if(id) {
+ newid = this.lowerResource + "/" + id;
+ oldid = id;
+ }
+ else {
+ if(callback) {
+ return callback(new Error('key is undefined'));
+ }
+ return;
+ }
+
+ this._request('get', newid, function(err, res){
+ //
+ // Remap back original ids
+ //
+ if(res && typeof res[key] !== 'undefined') {
+ res[key] = oldid;
+ }
+ if(Array.isArray(res)) {
+ for(var r in res) {
+ if (res[r] && res[r][key]) {
+ res[r][key] = res[r][key].split('/').slice(1).join('/')
+ }
+ }
+ }
+ if(res) {
+ callback(err, res);
+ } else {
+ return callback(err, res)
+ }
+ });
};
Resource.new = function (attrs) {
@@ -209,7 +232,8 @@ Resource.create = function (attrs, callback) {
attrs.ctime = attrs.mtime = Date.now();
}
- var that = this;
+ var that = this,
+ key = this.key;
var instance = new(that)(attrs);
var validate = that.prototype.validate(instance, that.schema);
@@ -223,6 +247,7 @@ Resource.create = function (attrs, callback) {
return;
}
+ var oldid = instance[key];
this.runBeforeHooks("create", instance, callback, function (err, result) {
if (!validate.valid) {
@@ -239,12 +264,8 @@ Resource.create = function (attrs, callback) {
return that.emit('error', e);
}
instance.save(function (e, res) {
- if (res) {
- instance._id = instance._id || res.id;
- instance._rev = instance._rev || res.rev;
- }
if (callback) {
- callback(e, instance);
+ callback(e, res);
}
});
});
@@ -252,7 +273,8 @@ Resource.create = function (attrs, callback) {
};
Resource.save = function (obj, callback) {
- var validate = this.prototype.validate(obj, this.schema);
+ var key = this.key,
+ validate = this.prototype.validate(obj, this.schema);
if (!validate.valid) {
var e = { validate: validate, value: instance, schema: that.schema };
@@ -266,22 +288,44 @@ Resource.save = function (obj, callback) {
}
}
- return this._request("save", obj.key, obj, callback);
+ var newid, oldid;
+
+ if (typeof obj !== 'undefined' && obj[key]) {
+ oldid = obj[key];
+ } else {
+ oldid = resourceful.uuid.v4();
+ }
+
+ newid = this.lowerResource + '/' + oldid;
+ obj[key] = newid;
+
+ return this._request("save", newid, obj, function(err, res){
+ if (res && res[key] && typeof oldid !== 'undefined') {
+ res[key] = oldid;
+ }
+ callback(err, res);
+ });
};
Resource.destroy = function (id, callback) {
- if (this.schema.properties._id && this.schema.properties._id.sanitize) {
- id = this.schema.properties._id.sanitize(id);
+ var key = this.key;
+
+ if (this.schema.properties[key] && this.schema.properties[key].sanitize) {
+ id = this.schema.properties[key].sanitize(id);
}
- return id
- ? this._request('destroy', id, callback)
+ var newid = this.lowerResource + "/" + id;
+
+ return newid
+ ? this._request('destroy', newid, callback)
: callback && callback(new Error('key is undefined'));
};
Resource.update = function (id, obj, callback) {
- if (this.schema.properties._id && this.schema.properties._id.sanitize) {
- id = this.schema.properties._id.sanitize(id);
+ var key = this.key;
+
+ if (this.schema.properties[key] && this.schema.properties[key].sanitize) {
+ id = this.schema.properties[key].sanitize(id);
}
if (this._timestamps) {
@@ -308,9 +352,17 @@ Resource.update = function (id, obj, callback) {
}
return;
}
-
+ var newid = this.lowerResource + "/" + id,
+ oldid = id;
+ obj[key] = newid;
+ obj.resource = this._resource;
return id
- ? this._request('update', id, obj, callback)
+ ? this._request('update', newid, obj, function(err, result){
+ if(result) {
+ result[key] = oldid;
+ }
+ callback(err, result);
+ })
: callback && callback(new Error('key is undefined'));
};
@@ -380,9 +432,13 @@ Resource.__defineGetter__('properties', function () {
return this.schema.properties;
});
-// Define getter / setter for key property. The key property is required by CouchDB
-Resource.__defineSetter__('key', function (val) { return this._key = val; });
-Resource.__defineGetter__('key', function () { return this._key; });
+// Define getter / setter for key property. The key property should be defined for all engines
+Resource.__defineGetter__('key', function () {
+ return this._key || resourceful.key || 'id';
+});
+Resource.__defineSetter__('key', function (val) {
+ return this._key = val;
+});
Resource.__defineGetter__('children', function (name) {
return this._children.map(function (c) { return resourceful.resources[c]; });
@@ -420,27 +476,28 @@ function relationship(factory, type, r, options) {
rstringc; // Resource capitalized string
if (typeof(r) === 'string') {
- rstring = r.toLowerCase();
- rfactory = resourceful.resources[resourceful.capitalize(r)];
+ rstringc = resourceful.capitalize(r);
+ rfactory = resourceful.resources[rstringc];
// We're dealing with .child('name-of-this-resource')
- if (!rfactory && rstring === factory.resource.toLowerCase()) {
+ if (!rfactory && rstringc === factory.resource) {
rfactory = factory;
}
} else if (typeof(r) === 'function') {
rstringc = r.resource;
- rstring = rstringc.toLowerCase();
rfactory = r;
} else {
throw new(TypeError)("argument must be a string or constructor");
}
+
+ rstring = rfactory.lowerResource;
rstringp = resourceful.pluralize(rstring);
- rstringc = rstringc || resourceful.capitalize(rstring);
- if (factory._children.indexOf(rstringc) !== -1) return;
if (rfactory === undefined) throw new Error("unknown resource " + rstring);
if (type == 'child') {
+ if (factory._children.indexOf(rstringc) !== -1) return;
+
factory._children.push(rstringc);
factory.property(rstring + '_ids', Array, { 'default': [], required: true });
//
@@ -450,21 +507,23 @@ function relationship(factory, type, r, options) {
engine._children.call(this, factory, rstringp, rfactory);
} else {
factory[rstringp] = function (id, callback) {
- return rfactory['by' + factory.resource](id, callback);
+ return rfactory['by' + factory.resource](id, callback)
};
}
//
// parent.children(callback)
//
factory.prototype[rstringp] = function (callback) {
- return this.constructor[rstringp](this._id, callback);
+ return this.constructor[rstringp](this.key, callback);
};
//
// Parent.createChild(id, child, callback)
//
- factory['create' + rstringc] = function (id, child, callback) {
- var key = factory.resource.toLowerCase() + '_id';
+ factory['create' + rstringc] = function (parent, child, callback) {
+ var key = factory.lowerResource + '_id',
+ id = parent.key || parent,
+ cid = child[rfactory.key] || resourceful.uuid.v4();
function notifyParent(c, callback) {
factory.get(id, function(err, p) {
@@ -472,36 +531,46 @@ function relationship(factory, type, r, options) {
if(callback) return callback(err);
}
- p[rstring + '_ids'].push(c._id || c.id);
- p.save(callback);
+ var rstringi = rstring + '_ids';
+
+ if (p[rstringi] && !Array.isArray(p[rstringi])) {
+ p[rstringi] = [p[rstringi]];
+ }
+ p[rstringi] = p[rstringi] || [];
+
+ if (p[rstringi].indexOf(cid) < 0) {
+ p[rstringi].push(cid);
+ }
+
+ p.save(function(err, result){
+ callback(err, c);
+ });
});
}
+ child[rfactory.key] = factory.lowerResource + '/' + id + '/' + cid;
+
if (child instanceof rfactory) {
child[key] = id;
- child._id = id + '/' + child._id;
child.save(function(err) {
if(err) {
if(callback) return callback(err);
}
-
notifyParent(child, callback);
});
} else {
var inheritance = {};
inheritance[key] = id;
child = resourceful.mixin({}, child, inheritance);
- child._id = id + '/' + child._id;
rfactory.create(child, function(err, c) {
if(err) {
if(callback) return callback(err);
}
-
- notifyParent(c, function(e) {
+ notifyParent(c, function(e, r) {
if(e) {
if(callback) return callback(e);
} else {
- if(callback) callback(err, c);
+ if(callback) callback(err, r);
}
});
});
@@ -512,24 +581,44 @@ function relationship(factory, type, r, options) {
// parent.createChild(child, callback)
//
factory.prototype['create' + rstringc] = function (child, callback) {
- factory['create' + rstringc](this._id, child, callback);
+ factory['create' + rstringc](this, child, callback);
};
+ factory.before('destroy', function (obj, next) {
+ obj = obj[rfactory.key] || obj;
+ obj = obj.split('/').slice(1).join('/');
+
+ factory.get(obj, function (e, p) {
+ if (e) { return next(e); }
+
+ p[rstringp](function (e, c) {
+ if (e) { return next(e); }
+
+ resourceful.async.forEachSeries(c, function (i, cb) {
+ i.destroy(cb);
+ }, function (e) {
+ next(e);
+ });
+ });
+ });
+ });
+
// Notify child about new parent
rfactory.parent(factory);
} else {
+ if (factory._parents.indexOf(rstringc) !== -1) return;
+
factory._parents.push(rstringc);
//
// Child.byParent(id, callback)
//
if (engine._byParent) {
- engine._byParent.call(engine, factory, rstringc, rfactory);
+ engine._byParent.call(factory._connection, factory, rfactory);
} else {
- factory['by' + rstringc] = engine._byParent || function (id, callback) {
+ factory['by' + rstringc] = function (id, callback) {
var filter = {};
filter[rstring + '_id'] = id;
-
factory.find(filter, callback);
};
}
@@ -538,7 +627,10 @@ function relationship(factory, type, r, options) {
// child.parent(callback)
//
factory.prototype[rstring] = function (callback) {
- return rfactory.get(this[rstring + '_id'], callback);
+ if (this[rstring + '_id']) {
+ return rfactory.get(this[rstring + '_id'], callback);
+ }
+ callback(null, null);
};
factory.property(rstring + '_id', [String, null], {
'default': null,
@@ -546,21 +638,24 @@ function relationship(factory, type, r, options) {
});
factory.before('destroy', function(obj, next) {
- factory.get(obj, function(e, i) {
+ obj = obj[rfactory.key] || obj;
+ obj = obj.split('/').slice(1).join('/');
+
+ factory.get(obj, function(e, c) {
if(e) { return next(e); }
- rfactory.get(i[rstring + '_id'], function(err, p) {
- if(err) { return next(err); }
+ c[rstring](function(err, p) {
+ if(err || !p) { return next(err); }
+ var key = factory.lowerResource + '_ids';
+ obj = obj.replace(rfactory.lowerResource + '/' + p.key + '/', '');
- var key = factory.resource.toLowerCase() + '_ids';
- if(~p[key].indexOf(obj)) {
+ if(p[key].indexOf(obj) > -1) {
p[key].splice(p[key].indexOf(obj), 1);
p.save(function(err) {
if(err) { return next(err); }
next();
});
- }
- else {
+ } else {
next();
}
});
@@ -710,7 +805,6 @@ Resource.prototype.save = function (callback) {
if (!err) {
self.isNewRecord = false;
}
-
if (callback) {
callback(err, res);
}
@@ -721,22 +815,22 @@ Resource.prototype.save = function (callback) {
};
Resource.prototype.update = function (obj, callback) {
- return this.constructor.update(this._id, obj, callback);
+ return this.constructor.update(this.key, obj, callback);
};
Resource.prototype.saveAttachment = function (attachment, callback) {
return this.constructor.saveAttachment({
- id: this._id,
+ id: this.key,
rev: this._rev
}, attachment, callback);
};
Resource.prototype.destroy = function (callback) {
- return this.constructor.destroy(this._id, callback);
+ return this.constructor.destroy(this.key, callback);
};
Resource.prototype.reload = function (callback) {
- return this.constructor.get(this._id, callback);
+ return this.constructor.get(this.key, callback);
};
Resource.prototype.readProperty = function (k, getter) {
@@ -769,17 +863,14 @@ Resource.prototype.toString = function () {
return JSON.stringify(this.toJSON());
};
+Resource.prototype.__defineGetter__('slug', function () {
+ return resourceful.pluralize(this.resource.toLowerCase()) + "/" + this.id;
+});
Resource.prototype.__defineGetter__('key', function () {
return this[this.constructor.key];
});
-Resource.prototype.__defineGetter__('id', function () {
- return this.constructor.key === '_id'
- ? this._id
- : undefined;
-});
-
Resource.prototype.__defineGetter__('isValid', function () {
return this.validate().valid;
});
View
8 package.json
@@ -1,7 +1,7 @@
{
"name": "resourceful",
"description": "A storage agnostic resource-oriented ODM for building prototypical models with validation and sanitization.",
- "version": "0.2.2",
+ "version": "0.3.0",
"url": "http://github.com/flatiron/resourceful",
"keywords": [
"ODM",
@@ -20,9 +20,11 @@
"url": "git://github.com/flatiron/resourceful.git"
},
"dependencies": {
+ "async": "0.1.x",
"cradle": "0.6.x",
"i": "0.3.x",
- "revalidator": "0.1.x"
+ "revalidator": "0.1.x",
+ "node-uuid": "1.3.x"
},
"devDependencies": {
"vows": "~0.6.0"
@@ -33,7 +35,7 @@
},
"scripts": {
"browserify": "browserify lib/browser.js -o build/resourceful.js",
- "test": "vows --spec -i"
+ "test": "vows --spec"
}
}
View
14 test/cache-test.js
@@ -10,7 +10,7 @@ var Article = resourceful.define('Article', function () {
vows.describe('resourceful/resource/cache', {
"When creating an instance, and saving it": {
topic: function () {
- this.article = new(Article)({ _id: '43', title: "The Last Article", published: true });
+ this.article = new(Article)({ id: '43', title: "The Last Article", published: true });
this.article.save(this.callback);
},
"and then loading it back up with `get()`": {
@@ -18,7 +18,10 @@ vows.describe('resourceful/resource/cache', {
Article.get('43', this.callback);
},
"it should return the previous instance": function (res) {
- assert.strictEqual(res._properties, this.article._properties);
+ assert.equal(res.id, '43');
+ assert.isTrue(res.published);
+ assert.equal(res.resource, 'Article');
+ assert.equal(res.title, 'The Last Article');
}
},
"and then loading it back up with `find()`": {
@@ -26,14 +29,17 @@ vows.describe('resourceful/resource/cache', {
Article.find({ title: "The Last Article" }, this.callback);
},
"it should return the previous instance": function (res) {
- assert.strictEqual(res[0]._properties, this.article._properties);
+ assert.equal(res[0].id, '43');
+ assert.isTrue(res[0].published);
+ assert.equal(res[0].resource, 'Article');
+ assert.equal(res[0].title, 'The Last Article');
}
}
}
}).addBatch({
"When creating an instance, and saving it": {
topic: function () {
- this.article = new(Article)({ _id: '43', title: "The Last Article", published: true });
+ this.article = new(Article)({ id: '43', title: "The Last Article", published: true });
this.article.save(this.callback);
},
"and then clearing the cache and loading it back up with `get()`": {
View
15 test/couchdb/couchdb-filter-test.js → test/couchdb-filter-test.js
@@ -2,7 +2,7 @@ var assert = require('assert'),
events = require('events'),
cradle = require('cradle'),
vows = require('vows'),
- resourceful = require('../../lib/resourceful');
+ resourceful = require('../lib/resourceful');
var Article;
@@ -11,18 +11,17 @@ resourceful.env = 'test';
vows.describe('resourceful/resource/view').addVows({
"A database containing articles and other resources": {
topic: function () {
- resourceful.use('couchdb', 'couchdb://127.0.0.1:5984/test');
+ resourceful.use('couchdb', 'couchdb://localhost:5984/test');
var promise = new(events.EventEmitter);
var db = new(cradle.Connection)().database('test');
db.destroy(function () {
db.create(function () {
db.save([
- { resource: 'Article', title: 'The Great Gatsby', published: true, author: 'fitzgerald', tags: ['classic'] },
- { resource: 'Article', title: 'Finding vim', published: false, author: 'cloudhead', tags: ['hacking', 'vi'] },
- { resource: 'Article', title: 'On Writing', published: true, author: 'cloudhead', tags: ['writing'] },
- { resource: 'Article', title: 'vi Zen', published: false, author: 'cloudhead', tags: ['vi', 'zen'] },
- { resource: 'Article', title: 'Channeling force', published: true, author: 'yoda', tags: ['force', 'zen'] },
- { resource: 'Body', name: 'fitzgerald' }
+ { _id: 'article/1', resource: 'Article', title: 'The Great Gatsby', published: true, author: 'fitzgerald', tags: ['classic'] },
+ { _id: 'article/2', resource: 'Article', title: 'Finding vim', published: false, author: 'cloudhead', tags: ['hacking', 'vi'] },
+ { _id: 'article/3', resource: 'Article', title: 'On Writing', published: true, author: 'cloudhead', tags: ['writing'] },
+ { _id: 'article/4', resource: 'Article', title: 'vi Zen', published: false, author: 'cloudhead', tags: ['vi', 'zen'] },
+ { _id: 'article/5', resource: 'Article', title: 'Channeling force', published: true, author: 'yoda', tags: ['force', 'zen'] },
], function () {
promise.emit('success');
});
View
55 test/couchdb/couchdb-relationship-fix-test.js
@@ -1,55 +0,0 @@
-var resourceful = require('../../lib/resourceful'),
- vows = require('vows'),
- assert = require('assert'),
- cradle = require('cradle');
-
-resourceful.use('couchdb', {database: 'test'});
-var Author = resourceful.define('author', function () {
- this.property('name');
-});
-var Book = resourceful.define('book', function () {
- this.property('title');
- this.parent('Author');
-});
-
-vows.describe('relationships in couchdb').addBatch({
- 'when using relationships': {
- topic: function() {
-
- var db = new(cradle.Connection)().database('test')
- var todo = 2,
- that = this;
- db.create(function() {
-
- Author.create({
- name:'William Shakespeare'
- }, function(e, shakespeare) {
-
- function go() {
- --todo || that.callback(null, shakespeare);
- }
-
- shakespeare.createBook({
- title:'Romeo and Juliet'
- }, go);
- setTimeout(function() {
- shakespeare.createBook({
- title:'Hamlet'
- }, go);
- }, 500);
- });
- });
- },
- 'author should have articles': function(e, s) {
- s.books(function(e, a) {
- assert.equal(a.length, 2);
- a[0].destroy(function() {
-
- s.books(function(e, x) {
- assert.equal(x.length, 1);
- });
- });
- });
- }
- }
-}).export(module);
View
106 test/couchdb/couchdb-relationship-test.js
@@ -1,106 +0,0 @@
-var assert = require('assert'),
- cradle = require('cradle'),
- vows = require('vows'),
- resourceful = require('../../lib/resourceful');
-
-var numberOfArticles = 5;
-
-resourceful.env = 'test';
-
-vows.describe('resourceful/resource/relationship').addBatch({
- "One-To-Many:": {
- "An empty database": {
- topic: function () {
- resourceful.use('couchdb', 'couchdb://127.0.0.1:5984/test');
- var db = new(cradle.Connection)().database('test'), callback = this.callback;
- db.destroy(function () {
- db.create(function () {
- callback();
- });
- })
- },
- "and a Resource definition for Author and Article": {
- topic: function () {
- var Article = this.Article = resourceful.define('article', function () {});
- this.Author = resourceful.define('author', function () { this.child('article') });
- this.Article.parent('author');
-
- var callback = this.callback,
- pending = numberOfArticles,
- done = function(){--pending || callback()};
-
- this.Author.create({_id:'yoda'},function(err,author){
- author.createArticle({ _id: 'a-1', title: 'Channeling force', tags: ['force', 'zen'] },done)
- });
- Article.create({ _id: 'a-2', title: 'The Great Gatsby', author: 'fitzgerald', tags: ['classic'] },done);
- Article.create({ _id: 'a-3', title: 'Finding vim', author: 'cloudhead', tags: ['hacking', 'vi'] },done);
- Article.create({ _id: 'a-4', title: 'On Writing', author: 'cloudhead', tags: ['writing'] },done);
- Article.create({ _id: 'a-5', title: 'vi Zen', author: 'cloudhead', tags: ['vi', 'zen'] },done);
- },
- "Author should have a <articles> method": function () {
- assert.isFunction(this.Author.articles);
-// },
-// "Author should have a <articles> method": {
-// topic: function () {
-// this.Author.articles('yoda', this.callback);
-// },
-// "which will return all author's articles": function (articles) {
-// assert.equal(articles.length, 1);
-// assert.instanceOf(articles[0], this.Article);
-// }
- },
- "Author should have a <parents> property which is empty": function () {
- assert.isArray(this.Author.parents);
- assert.isEmpty(this.Author.parents);
- },
- "Author should have a <children> property": function (Author, Article) {
- assert.isArray(this.Author.children);
- assert.include(this.Author.children,this.Article);
- },
- "Article should have a <parents> property which includes Author": function (Author, Article) {
- assert.isArray(this.Article.parents);
- assert.include(this.Article.parents,this.Author);
- },
- "Article should have a <children> property which is empty": function (Author, Article) {
- assert.isArray(this.Article.children);
- assert.isEmpty(this.Article.children);
- },
- "Article should have a <byAuthor> filter": function (Author, Article) {
- assert.isFunction(this.Article.byAuthor);
- assert.isObject(this.Article.views.byAuthor);
- },
- "when instantiated": {
- topic: function () {
- this.author = new(this.Author);
- this.article = new(this.Article);
- return null;
- },
- "author should have a <articles> method": function () {
- assert.isFunction(this.author.articles);
- },
- "author should have a <article_ids> property": function (_, Author, Article) {
- assert.isArray(this.author.article_ids);
- },
- "article should have a <author_id> property": function (Author, Article) {
- assert.include(this.article,'author_id');
- assert.isNull(this.article.author_id);
- },
- "article should have a <author> method": function (Author, Article) {
- assert.isFunction(this.article.author);
- }
-// },
-// "Article should have a <byAuthor> method":{
-// topic: function () {
-// this.Article.byAuthor('yoda',this.callback);
-// },
-// "which will return all articles by that author": function (articles) {
-// assert.isArray(articles);
-// assert.equal(articles.length, 1);
-// assert.equal(articles[0].author_id,'yoda');
-// assert.equal(articles[0].id,'a-1');
-// }
- }
- }
- }
- }
-}).export(module);
View
230 test/engines-test.js
@@ -2,58 +2,29 @@ var path = require('path')
, assert = require('assert')
, fs = require('fs')
, vows = require('vows')
+ , macros = require('./macros')
+ , fixtures = require('./fixtures')
, resourceful = require('../lib/resourceful');
+//
+// Load resourceful engines for testing from /engines/ folder
+//
var engines = fs.readdirSync(path.join(__dirname, 'engines')).map(function (e) { return require('./engines/' + e.slice(0,-3)); });
+//
+// For every engine, we'll need to create a new resources,
+// that each connect to the respective engine
+//
var resources = {};
engines.forEach(function (e) {
+ //
+ // Create a new object to hold resources which will be defined in macros
+ //
resources[e] = {};
- vows.describe('resourceful/engines/' + e.name)
- .addBatch({
- 'In database "test"': {
- topic: function () {
- e.load(resourceful, [
- { _id: 'bob', age: 35, hair: 'black', resource: 'Author'},
- { _id: 'tim', age: 16, hair: 'brown', resource: 'Author'},
- { _id: 'mat', age: 29, hair: 'black', resource: 'Author'},
- { _id: 'bob/1', title: 'Nodejs sucks!', year: 2003, fiction: true, resource: 'Book'},
- { _id: 'tim/1', title: 'Nodejitsu rocks!', year: 2008, fiction: false, resource: 'Book'},
- { _id: 'bob/2', title: 'Loling at you', year: 2011, fiction: true, resource: 'Book'},
- { _id: 'dummy/1', hair: 'black', resource: 'Dummy'},
- { _id: 'dummy/2', hair: 'blue', resource: 'Dummy'}
- ], this.callback);
- },
- 'Defining resource "book"': {
- topic: function () {
- return resources[e].Book = resourceful.define('book', function () {
- this.use(e.name, e.options);
- this.string('title');
- this.number('year');
- this.bool('fiction');
- });
- },
- 'will be successful': function (book) {
- assert.equal(Object.keys(book.properties).length, 4);
- }
- },
- 'Defining resource "author"': {
- topic: function () {
- return resources[e].Author = resourceful.define('author', function () {
- this.use(e.name, e.options);
-
- this.number('age');
- this.string('hair').sanitize('lower');
- });
- },
- 'will be successful': function (author) {
- assert.equal(Object.keys(author.properties).length, 3);
- }
- }
- }
- }).addBatch({
+ vows.describe('resourceful/engines/' + e.name)
+ .addBatch(macros.defineResources(e, resources)).addBatch({
'In database "test"': {
topic: function () {
return null;
@@ -66,6 +37,9 @@ engines.forEach(function (e) {
assert.isNull(err);
assert.isArray(obj);
assert.equal(obj.length, 3);
+ assert.equal(obj[0].id, 'bob');
+ assert.equal(obj[1].id, 'mat');
+ assert.equal(obj[2].id, 'tim');
}
}
}
@@ -87,7 +61,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -115,13 +89,13 @@ engines.forEach(function (e) {
},
"a Resource.create() request": {
topic: function () {
- resources[e].Author.create({ _id: 'han', age: 30, hair: 'red'}, this.callback);
+ resources[e].Author.create({id: 'han', age: 30, hair: 'red'}, this.callback);
},
"should return the newly created object": function (err, obj) {
assert.isNull(err);
assert.strictEqual(obj.constructor, resources[e].Author);
assert.instanceOf(obj, resources[e].Author);
- assert.equal(obj._id, 'han');
+ assert.equal(obj.id, 'han');
assert.equal(obj.age, 30);
assert.equal(obj.hair, 'red');
assert.equal(obj.resource, 'Author');
@@ -142,7 +116,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'han');
+ assert.equal(obj.id, 'han');
assert.equal(obj.age, 30);
assert.equal(obj.hair, 'red');
assert.equal(obj.resource, 'Author');
@@ -155,16 +129,81 @@ engines.forEach(function (e) {
}
}
}).addBatch({
+ 'In database "test"': {
+ topic: function () {
+ return null;
+ },
+ "a diffirent Resource.create() request with the same id": {
+ topic: function () {
+ resources[e].Creature.create({id: 'han'}, this.callback);
+ },
+ "should return the newly created object": function (err, obj) {
+ assert.isNull(err);
+ assert.strictEqual(obj.constructor, resources[e].Creature);
+ assert.instanceOf(obj, resources[e].Creature);
+ assert.equal(obj.id, 'han');
+ assert.equal(obj.resource, 'Creature');
+ },
+ "should not be a new record": function (err, obj) {
+ assert.isNull(err);
+ assert.isFalse(obj.isNewRecord);
+ },
+ "should create the record in the db": {
+ topic: function () {
+ resources[e].Creature.get('han', this.callback);
+ },
+ "should respond with a Resource instance": function (err, obj) {
+ assert.isNull(err);
+ assert.isObject(obj);
+ assert.instanceOf(obj, resourceful.Resource);
+ assert.equal(obj.constructor, resources[e].Creature);
+ },
+ "should respond with the right object": function (err, obj) {
+ assert.isNull(err);
+ assert.equal(obj.id, 'han');
+ assert.equal(obj.resource, 'Creature');
+ },
+ "should not be a new record": function (err, obj) {
+ assert.isNull(err);
+ assert.isFalse(obj.isNewRecord);
+ }
+ }
+ }
+ }
+ }).addBatch({
+ 'In database "test"': {
+ topic: function () {
+ return null;
+ },
+ "and a Resource.destroy() request": {
+ topic: function () {
+ resources[e].Author.destroy('han', this.callback);
+ },
+ "should be successful": function (err, obj) {
+ assert.isNull(err);
+ },
+ "and Resource.get() the destroyed object": {
+ topic: function () {
+ resources[e].Author.get('han', this.callback);
+ },
+ "should respond with an error": function (err, obj) {
+ assert.equal(err.status, 404);
+ assert.isUndefined(obj);
+ }
+ }
+ }
+ }
+ }).addBatch({
"Instantiating a new instance": {
topic: function () {
- return resources[e].Author.new({_id: 'kim', age: 32, hair: 'gold'});
+ return resources[e].Author.new({id: 'han', age: 30, hair: 'red'});
},
"should be a new record": function (obj) {
assert.isTrue(obj.isNewRecord);
},
"should not be in the db": {
topic: function () {
- resources[e].Author.get('kim', this.callback);
+ resources[e].Author.get('han', this.callback);
},
"should respond with an error": function (err, obj) {
assert.equal(err.status, 404);
@@ -173,16 +212,55 @@ engines.forEach(function (e) {
}
}
}).addBatch({
- 'In database "test"': {
+ "Instantiating a new instance": {
topic: function () {
- return null;
+ return resources[e].Author.new({id: 'han', age: 30, hair: 'red'});
},
- "and a Resource.destroy() request": {
- topic: function () {
- resources[e].Author.destroy('han', this.callback);
+ "should be a new record": function (obj) {
+ assert.isTrue(obj.isNewRecord);
+ },
+ "a Resource.prototype.save() request": {
+ topic: function (obj) {
+ obj.save(this.callback);
},
- "should be successful": function (err, obj) {
+ "should respond with a Resource instance": function (err, obj) {
+ assert.isNull(err);
+ assert.isObject(obj);
+ assert.instanceOf(obj, resourceful.Resource);
+ assert.equal(obj.constructor, resources[e].Author);
+ },
+ "should respond with the right object": function (err, obj) {
+ assert.isNull(err);
+ assert.equal(obj.id, 'han');
+ assert.equal(obj.age, 30);
+ assert.equal(obj.hair, 'red');
+ assert.equal(obj.resource, 'Author');
+ },
+ "should not be a new record": function (err, obj) {
assert.isNull(err);
+ assert.isFalse(obj.isNewRecord);
+ },
+ "should create the object in db": {
+ topic: function () {
+ resources[e].Author.get('han', this.callback);
+ },
+ "should respond with a Resource instance": function (err, obj) {
+ assert.isNull(err);
+ assert.isObject(obj);
+ assert.instanceOf(obj, resourceful.Resource);
+ assert.equal(obj.constructor, resources[e].Author);
+ },
+ "should respond with the right object": function (err, obj) {
+ assert.isNull(err);
+ assert.equal(obj.id, 'han');
+ assert.equal(obj.age, 30);
+ assert.equal(obj.hair, 'red');
+ assert.equal(obj.resource, 'Author');
+ },
+ "should not be a new record": function (err, obj) {
+ assert.isNull(err);
+ assert.isFalse(obj.isNewRecord);
+ }
}
}
}
@@ -205,10 +283,14 @@ engines.forEach(function (e) {
assert.isArray(obj);
assert.instanceOf(obj[0], resourceful.Resource);
assert.instanceOf(obj[1], resourceful.Resource);
- assert.equal(obj[0]._id, 'bob');
+ assert.equal(obj[0].id, 'bob');
assert.equal(obj[0].age, 35);
assert.equal(obj[0].hair, 'black');
assert.equal(obj[0].resource, 'Author');
+ assert.equal(obj[1].id, 'mat');
+ assert.equal(obj[1].age, 29);
+ assert.equal(obj[1].hair, 'black');
+ assert.equal(obj[1].resource, 'Author');
},
"should not be a new record": function (err, obj) {
assert.isNull(err);
@@ -235,7 +317,7 @@ engines.forEach(function (e) {
},
"it should have 'bob' object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -256,7 +338,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -277,7 +359,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -296,7 +378,7 @@ engines.forEach(function (e) {
},
"it should have 'bob' object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -318,7 +400,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -339,7 +421,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -358,7 +440,7 @@ engines.forEach(function (e) {
},
"it should have 'bob' object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -381,7 +463,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'red');
assert.equal(obj.resource, 'Author');
@@ -402,7 +484,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'red');
assert.equal(obj.resource, 'Author');
@@ -421,7 +503,7 @@ engines.forEach(function (e) {
},
"it should have 'bob' object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 31);
assert.equal(obj.hair, 'red');
assert.equal(obj.resource, 'Author');
@@ -442,7 +524,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -463,7 +545,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -478,7 +560,7 @@ engines.forEach(function (e) {
}).addBatch({
'In database "test"': {
topic: function () {
- resources[e].Author.create({ _id: 'han', age: 30, hair: 'red'}, this.callback);
+ resources[e].Author.get('han', this.callback);
},
"a Resource.prototype.destroy() request": {
topic: function (obj) {
@@ -514,7 +596,7 @@ engines.forEach(function (e) {
},
"should respond with the right object": function (err, obj) {
assert.isNull(err);
- assert.equal(obj._id, 'bob');
+ assert.equal(obj.id, 'bob');
assert.equal(obj.age, 35);
assert.equal(obj.hair, 'black');
assert.equal(obj.resource, 'Author');
@@ -532,7 +614,7 @@ engines.forEach(function (e) {
},
"should be successful": function (err, obj) {
assert.isNull(err);
- assert.notEqual(obj._id, undefined);
+ assert.notEqual(obj.id, undefined);
assert.equal(obj.age, 51);
assert.equal(obj.hair, 'white');
assert.equal(obj.resource, 'Author');
@@ -551,9 +633,9 @@ function multipleGet(e) {
"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');
+ assert.equal(obj[0].id, 'bob');
+ assert.equal(obj[1] || null, null);
+ assert.equal(obj[2].id, 'tim');
}
}
};
View
9 test/engines/memory.js
@@ -4,7 +4,12 @@ engine.name = 'memory';
engine.options = { uri: 'test' };
engine.load = function (resourceful, data, callback) {
+ data = data.map(function (r) {
+ r.id = r._id;
+ delete r._id;
+ return r;
+ });
+
new(resourceful.engines.Memory)(engine.options).load(data);
callback();
-}
-;
+};
View
2  test/events-test.js
@@ -24,7 +24,7 @@ vows.describe('resourceful/events').addBatch({
},
"when calling save() on an instance of Article": {
topic: function (A) {
- new(A)({ _id: '66', title: 'an Article' }).save(this.callback);
+ new(A)({ id: '66', title: 'an Article' }).save(this.callback);
},
"should trigger the bound function": function (e, res) {
assert.isNull(e);
View
15 test/fixtures/index.js
@@ -0,0 +1,15 @@
+var fixtures = exports;
+
+fixtures.testData = [
+ { _id: 'author/bob', resource: 'Author', age: 35, hair: 'black'},
+ { _id: 'author/mat', resource: 'Author', age: 29, hair: 'black'},
+ { _id: 'author/tim', resource: 'Author', age: 16, hair: 'brown'},
+ { _id: 'book/author/bob/1', resource: 'Book', title: 'Nodejs rocks!', year: 2003, fiction: true},
+ { _id: 'book/author/tim/1', resource: 'Book', title: 'Nodejitsu rocks!', year: 2008, fiction: false},
+ { _id: 'book/author/bob/2', resource: 'Book', title: 'Loling at you', year: 2011, fiction: true},
+ { _id: 'zine/1', resource: 'Zine', title: 'Danger'},
+ { _id: 'zine/2', resource: 'Zine', title: 'Doom'},
+ { _id: 'zine/author/bob/1', resource: 'Zine', title: 'Doom'},
+ { _id: 'dummy/1', resource: 'Dummy', hair: 'black'},
+ { _id: 'dummy/2', resource: 'Dummy', hair: 'blue'}
+];
View
254 test/fixtures/relationship.js
@@ -0,0 +1,254 @@
+var fixtures = exports;
+
+fixtures.testData = [
+ {
+ _id: 'user/marak',
+ name: 'marak',
+ resource: 'User',
+ repository_ids: ['colors', 'npmtop'],
+ membership_ids: ['nodedocs', 'nodejitsu', 'nodeapps'],
+ follower_ids: ['pavan', 'christian'],
+ following_ids: ['pavan']
+ },
+ {
+ _id: 'user/pavan',
+ name: 'pavan',
+ resource: 'User',
+ repository_ids: ['bullet', 'octonode'],
+ membership_ids: ['nodejitsu', 'nodeapps'],
+ follower_ids: ['marak', 'christian'],
+ following_ids: ['marak']
+ },
+ {
+ _id: 'user/christian',
+ name: 'christian',
+ resource: 'User',
+ repository_ids: ['repository-1', 'repository-2'],
+ membership_ids: ['nodejitsu'],
+ follower_ids: [],
+ following_ids: ['marak', 'pavan']
+ },
+ {
+ _id: 'repository/user/marak/colors',
+ name: 'colors',
+ resource: 'Repository',
+ user_id: 'marak'
+ },
+ {
+ _id: 'repository/user/marak/npmtop',
+ name: 'npmtop',
+ resource: 'Repository',
+ user_id: 'marak'
+ },
+ {
+ _id: 'repository/user/pavan/bullet',
+ name: 'bullet',
+ resource: 'Repository',
+ user_id: 'pavan'
+ },
+ {
+ _id: 'repository/user/pavan/octonode',
+ name: 'octonode',
+ resource: 'Repository',
+ user_id: 'pavan'
+ },
+ {
+ _id: 'repository/user/christian/repository-1',
+ name: 'repository-1',
+ resource: 'Repository',
+ pull_request_ids: ['1'],
+ user_id: 'christian'
+ },
+ {
+ _id: 'repository/user/christian/repository-2',
+ name: 'repository-2',
+ resource: 'Repository',
+ user_id: 'christian'
+ },
+ {
+ _id: 'team/nodeapps',
+ name: 'nodeapps',
+ resource: 'Team',
+ member_ids: ['marak', 'pavan']
+ },
+ {
+ _id: 'team/nodejitsu',
+ name: 'nodejitsu',
+ resource: 'Team',
+ member_ids: ['marak', 'pavan', 'christian']
+ },
+ {
+ _id: 'team/nodedocs',
+ name: 'nodedocs',
+ resource: 'Team',
+ member_ids: ['marak']
+ },
+ {
+ _id: 'member/team/nodeapps/marak',
+ user: 'marak',
+ resource: 'Member',
+ team_id: 'nodeapps'
+ },
+ {
+ _id: 'member/team/nodeapps/pavan',
+ user: 'pavan',
+ resource: 'Member',
+ team_id: 'nodeapps'
+ },
+ {
+ _id: 'member/team/nodejitsu/marak',
+ user: 'marak',
+ resource: 'Member',
+ team_id: 'nodejitsu'
+ },
+ {
+ _id: 'member/team/nodejitsu/pavan',
+ user: 'pavan',
+ resource: 'Member',
+ team_id: 'nodejitsu'
+ },