Browse files

Added flags for hooks, namespacing and mapping

  • Loading branch information...
1 parent 4b9724b commit a859383aaced0e218edf82ffe62bd7a13f185fea @masylum committed Jul 14, 2011
View
151 README.md
@@ -33,11 +33,26 @@ Models don't map data from the db, they just define the logic.
var USER = require('mongolia').model(db, 'users');
```
-## mongo commands
+## mongo proxied collection commands
-Calls to the db are done using the function `mongo`.
-Mongolia proxies all the `collection` methods defiend on the driver plus some custom methods.
+Calls to the db are done using the method `mongo`.
+`mongo` proxies all the `collection` methods defined on the driver plus some custom methods.
+This allows mongolia to extend the driver with extra functionalties:
+
+ * Namespacing: Allows you to filter the documents going and coming from the db.
+ * Mapping: Allows you to apply functions to the documents attributes going and coming from the db.
+ * Hooks: They are triggered before and after a call is done.
+
+The default usage is:
+
+`mongo('method[:namespace]', args)`
+
+If you want to disable any functionality you can by doing:
+
+`mongo({method: method[, namespace: namespace, namespacing: false, mapping: false, hooks: false])`
+
+Example:
``` javascript
var Db = require('mongodb/lib/mongodb/db').Db,
Server = require('mongodb/lib/mongodb/connection').Server,
@@ -46,60 +61,71 @@ var Db = require('mongodb/lib/mongodb/db').Db,
db.open(function () {
var User = require('./user.js')(db);
- User.mongo('findOne', {name: 'foo'}, function (error, user) {
- console.log(user);
- });
+ User.mongo('findOne', {name: 'foo'}, console.log);
+ User.mongo({method: 'insert', hooks: false}, {name: 'foo'}, console.log);
});
```
All the `collection` methods from the driver are supported.
If you need more information visit the [driver](http://github.com/christkv/node-mongodb-native) documentation
-## Custom mongo collection commands
+### Custom mongo collection commands
Mongolia provides some useful commands that are not available using the driver.
* `findArray`: find that returns an array instead of a cursor.
* `mapReduceArray`: mapReduce that returns an array with the results.
* `mapReduceCursor`: mapReduce that returns a cursor.
-## Hooks
-
-Mongolia let you define some hooks on your models that will be triggered after a mongoDB command.
+### Namespacing
- * `beforeInsert(documents, callback)`: triggered *before* an `insert`.
- * `afterInsert(documents, callback)`: triggered *after* an `insert.
+Secure your data access defining visibility namespaces.
- * `beforeUpdate(query, update, callback)`: triggered *before* an `update` or `findAndModify` command.
- * `afterUpdate(query, update, callback)`: triggered *after* an `update` or `findAndModify` command.
+You can namespace a call to the database by appending `:namespace` on
+your proxied method.
- * `beforeRemove(query, callback)`: triggered *before* a `remove` command.
- * `afterRemove(query, callback)`: triggered *after* a `remove` command.
+If called without a namespace, the method will work ignoring the `namespace` directives.
-Example:
+You can `extend` other namespaces and `add` or `remove` some data visibility.
``` javascript
-var COMMENT = require('mongolia').model(db, 'comments'),
- Post = require('./post');
+var USER = require('mongolia').model(db, 'users');
-COMMENT.beforeInsert = function (documents, callback) {
- documents.forEach(function (doc) {
- doc.created_at = new Date();
- });
- callback(null, documents);
+USER.namespaces = {
+ public: ['account.email', 'account.name', '_id'],
+ private: {
+ extend: 'public',
+ add: ['password'],
+ },
+ accounting: {
+ extend: 'private',
+ add: ['credit_card_number'] // don't do this at home
+ }
};
-COMMENT.atferInsert = function (documents, callback) {
- Post(db).mongo('update', {_id: documents[0].post._id}, {'$inc': {num_posts: 1}}, callback);
-};
+USER.mongo('insert:public', {account: {email: 'foo@bar.com'}, password: 'fleiba', credit_card_number: 123, is_active: true});
+// insert => {account: {email: 'foo@bar.com'}}
+
+USER.validateAndUpdate({account: {email: 'foo@bar.com'}}, {'$set': {'account.email': 'super@mail.com', password: '123'}, {namespace: 'public'});
+// updates => {'$set': {'account.email': 'super@mail.com'}}
+
+USER.mongo('findArray:public', {account: {email: 'foo@bar.com'}});
+// find => {account: {email: 'foo@bar.com', name: 'paco'}}
+
+USER.mongo('findArray:accounting', {account: {email: 'foo@bar.com'}});
+// find => {account: {email: 'foo@bar.com', name: 'paco'}, password: 'fleiba', credit_card_number: 123}
```
-## Mappings and type casting
+Use this feature wisely to filter data coming from forms.
+
+### Mappings and type casting
Mongolia `maps` allows you to cast the data before is stored to the database.
Mongolia will apply the specified function for each attribute on the `maps` object.
+By default we provide the map `_id -> ObjectId`, so you don't need to cast it.
+
``` javascript
var USER = require('mongolia').model(db, 'users');
@@ -118,47 +144,44 @@ USER.mongo('insert', {email: 'foo@bar.com', password: 123, name: 'john', is_dele
// stored => {password: '123', name: 'JOHN', is_deleted: true}
```
-## Namespacing
+### Hooks
-Secure your data access defining visibility namespaces.
+Mongolia let you define some hooks on your models that will be triggered after a mongoDB command.
-You can namespace a call to the database by appending `:namespace` on
-your proxied method.
+ * `beforeInsert(documents, callback)`: triggered *before* an `insert`.
+ * `afterInsert(documents, callback)`: triggered *after* an `insert.
-If called without a namespace, the method will work ignoring the `namespace` directives.
+ * `beforeUpdate(query, update, callback)`: triggered *before* an `update` or `findAndModify` command.
+ * `afterUpdate(query, update, callback)`: triggered *after* an `update` or `findAndModify` command.
-You can `extend` other namespaces and `add` or `remove` some data visibility.
+ * `beforeRemove(query, callback)`: triggered *before* a `remove` command.
+ * `afterRemove(query, callback)`: triggered *after* a `remove` command.
+
+Example:
``` javascript
-var USER = require('mongolia').model(db, 'users');
+var COMMENT = require('mongolia').model(db, 'comments'),
+ Post = require('./post');
-USER.namespaces = {
- public: ['account.email', 'account.name', '_id'],
- private: {
- extend: 'public',
- add: ['password'],
- },
- accounting: {
- extend: 'private',
- add: ['credit_card_number'] // don't do this at home
- }
+COMMENT.beforeInsert = function (documents, callback) {
+ documents.forEach(function (doc) {
+ doc.created_at = new Date();
+ });
+ callback(null, documents);
};
-USER.mongo('insert:public', {account: {email: 'foo@bar.com'}, password: 'fleiba', credit_card_number: 123, is_active: true});
-// insert => {account: {email: 'foo@bar.com'}}
-
-USER.validateAndUpdate({account: {email: 'foo@bar.com'}}, {'$set': {'account.email': 'super@mail.com', password: '123'}, {namespace: 'public'});
-// updates => {'$set': {'account.email': 'super@mail.com'}}
-
-USER.mongo('findArray:public', {account: {email: 'foo@bar.com'}});
-// find => {account: {email: 'foo@bar.com', name: 'paco'}}
+COMMENT.atferInsert = function (documents, callback) {
+ documents.forEach(function (doc) {
+ Post(db).mongo('update', {_id: doc.post._id}, {'$inc': {num_posts: 1}}); // fire and forget
+ });
+ callback(null, documents);
+};
-USER.mongo('findArray:accounting', {account: {email: 'foo@bar.com'}});
-// find => {account: {email: 'foo@bar.com', name: 'paco'}, password: 'fleiba', credit_card_number: 123}
+USER.mongo('insert', {email: 'foo@bar.com'});
+// stored => {email: 'foo@bar.com', created_at: Thu, 14 Jul 2011 12:13:39 GMT}
+// Post#num_posts is increased
```
-Use this feature wisely to filter data coming from forms.
-
## Embedded documents
Mongolia helps you to _denormalize_ your mongo database.
@@ -429,6 +452,20 @@ var User = function (db) {
return USER;
};
```
+
+## Tests
+
+Mongolia is fully tested using [testosterone](http://github.com/masylum/testosterone)
+To run the tests use:
+
+```bash
+make
+```
+
+## Example
+
+Monoglia has a fully working blog example on the `example` folder.
+
## Contributors
In no specific order.
View
72 lib/helpers/collection_proxy.js
@@ -1,8 +1,9 @@
-var PROXY = {},
+var PROXY = {}
+ , _ = require('underscore');
- _apply = function (collection, fn, args) {
- return collection[fn].apply(collection, args);
- };
+function _apply(collection, fn, args) {
+ return collection[fn].apply(collection, args);
+}
Object.defineProperty(PROXY, 'namespacer', {value: require('./namespacer'), writable: true});
Object.defineProperty(PROXY, 'mapper', {value: require('./mapper'), writable: true});
@@ -11,38 +12,43 @@ Object.defineProperty(PROXY, 'mapper', {value: require('./mapper'), writable: tr
* Proxies calls
*
* @param {Object} model
- * @param {String} fn
- * @param {String} namespace
- * @param {Object} collection
+ * @param {Object} options
* @param {Array} args
* @param {Function} callback
*/
-PROXY.proxy = function (model, fn, namespace, collection, args, callback) {
+PROXY.proxy = function (model, options, args, callback) {
+ var fn = options.method
+ , arguments_length = arguments.length;
- // noop
- if (arguments.length < 6) {
- callback = function () {};
- }
+ model.getCollection(function (error, collection) {
- if (model.namespaces && model.namespaces[namespace]) {
- // has side effects, alters args
- PROXY.namespacer.filter(model.namespaces, namespace, fn, args);
- }
+ // noop
+ if (arguments_length < 4) {
+ callback = function () {};
+ }
- if (model.maps) {
- // has side effects, alters args
- PROXY.mapper.map(model.maps, fn, args);
- }
+ if (error) return callback(error, null);
- // overwritten method
- if (PROXY[fn] !== undefined) {
- return PROXY[fn](model, collection, args, callback);
+ if (options.namespacing && model.namespaces && model.namespaces[options.namespace]) {
+ // has side effects, alters args
+ PROXY.namespacer.filter(model.namespaces, options.namespace, fn, args);
+ }
- // driver method
- } else {
- args[args.length - 1] = callback;
- _apply(collection, fn, args);
- }
+ if (options.mapping && model.maps) {
+ // has side effects, alters args
+ PROXY.mapper.map(model.maps, fn, args);
+ }
+
+ // overwritten method with hooks, or custom method
+ if (typeof PROXY[fn] !== 'undefined' && (typeof collection[fn] !== 'undefined' ? options.hooks : true)) {
+ return PROXY[fn](model, collection, args, callback);
+
+ // driver method or hooks disabled
+ } else {
+ args[args.length - 1] = callback;
+ _apply(collection, fn, args);
+ }
+ });
};
/**
@@ -71,9 +77,8 @@ PROXY.findArray = function (model, collection, args, callback) {
*/
PROXY.insert = function (model, collection, args, callback) {
args[args.length - 1] = function (error, ret) {
- if (error) {
- return callback(error, null);
- }
+ if (error) return callback(error, null);
+
model.afterInsert(args[0], function (error, _) {
callback(error, ret);
});
@@ -84,12 +89,9 @@ PROXY.insert = function (model, collection, args, callback) {
}
model.beforeInsert(args[0], function (error, documents) {
- if (error) {
- return callback(error, null);
- }
+ if (error) return callback(error, null);
args[0] = documents;
-
_apply(collection, 'insert', args);
});
};
View
12 lib/helpers/mapper.js
@@ -1,7 +1,7 @@
var MAPPER = {}
, _ = require('underscore');
-MAPPER.filterUpdate = function (maps, arg) {
+MAPPER.mapDocument = function (maps, arg) {
/* 0: dont match
* 1: partial matching, needs more inspection
@@ -57,14 +57,14 @@ MAPPER.filterUpdate = function (maps, arg) {
el[attr][i] = getObject(maps, attr)(el[attr][i]);
});
} else {
- el[attr] = getObject(maps, attr)(el[attr]);
+ el[attr] = getObject(maps, attr)(el[attr]);
}
} else {
if (is_matching === 1 && typeof el[attr] === 'object') {
filter(maps[attr], el[attr], level + 1);
}
}
- }
+ }
}
}
}
@@ -82,10 +82,10 @@ MAPPER.filterUpdate = function (maps, arg) {
MAPPER.map = function (maps, fn, args) {
if (fn === 'insert' || fn.match(/^find/)) {
- MAPPER.filterUpdate(maps, args[0]);
+ MAPPER.mapDocument(maps, args[0]);
} else if (fn === 'update') {
- MAPPER.filterUpdate(maps, args[0]);
- MAPPER.filterUpdate(maps, args[1]);
+ MAPPER.mapDocument(maps, args[0]);
+ MAPPER.mapDocument(maps, args[1]);
}
};
View
112 lib/model.js
@@ -15,8 +15,8 @@ module.exports = function (db, collection_name) {
}
var MODEL = {
- db: db,
- collection_proxy: require('./helpers/collection_proxy')
+ db: db
+ , collection_proxy: require('./helpers/collection_proxy')
};
/**
@@ -32,33 +32,50 @@ module.exports = function (db, collection_name) {
};
/**
- * Proxies `collection` calls by triggering the proper hooks
+ * Proxies `collection` calls and then:
+ *
+ * + Namespacing: Filters sent and received documents according to namespaces.
+ * + Mapping: Applies functions to sent and received document attributes.
+ * + Hooks: Calls some before* and after* hooks
+ *
+ * This functionalities are enabled by default, but you can disable them by providing
+ * options as the first param.
*
* Adds custom `findArray`, `mapReduceCursor` and `mapReduceArray`.
*
- * @param {String} function name
- * @param {Mixed} options
+ * @param {String|Object} options
+ * @param {Mixed} args
* @param {Function} callback
* @returns itself
*/
- MODEL.mongo = function (fn, options, callback) {
- var args = Array.prototype.slice.call(arguments, 0);
+ MODEL.mongo = function (options, args, callback) {
+ var parsed_method;
+
+ args = Array.prototype.slice.call(arguments, 0);
+ options = args.shift();
+
+ if (typeof options === 'string') {
+ parsed_method = options.match(/([a-zA-Z]*)(?::([a-zA-Z]*))?/); // extract the namespace
+ options = {
+ method: parsed_method[1]
+ , namespace: parsed_method[2]
+ };
+ }
- fn = args.shift();
+ if (!options.method) {
+ throw (Error('`method` is mandatory!'));
+ }
+
+ options = _.extend({
+ hooks: true
+ , namespacing: true
+ , mapping: true
+ }, options);
callback = args[args.length - 1];
- if (typeof callback !== 'function') {
- callback = function () {}; // noop
- }
+ callback = typeof callback === 'function' ? callback : function () {}; // noop
- MODEL.getCollection(function (error, collection) {
- if (error) {
- callback(error, null);
- } else {
- var parsed_fn = fn.match(/([a-zA-Z]*)(?::([a-zA-Z]*))?/); // extract the namespace
- MODEL.collection_proxy.proxy(MODEL, parsed_fn[1], parsed_fn[2], collection, args, callback);
- }
- });
+ MODEL.collection_proxy.proxy(MODEL, options, args, callback);
return MODEL;
};
@@ -184,7 +201,7 @@ module.exports = function (db, collection_name) {
*
* @param {Object} query - Document query
* @param {Object} update - Update object
- * @param {Object} options - Update options. Defaults to {upsert: true, multi: false}
+ * @param {Object} options - Update options. Defaults to {upsert: false, multi: false}
* @param {Function} callback - The callback returns a validator
*
* @returns itself
@@ -201,7 +218,7 @@ module.exports = function (db, collection_name) {
var namespace = options.namespace ? ':' + options.namespace : '';
delete options.namespace;
- options = _.defaults(options, {upsert: true, multi: false});
+ options = _.defaults(options, {upsert: false, multi: false});
MODEL.mongo('findOne', query, function (error, document) {
if (error) return callback(error);
@@ -212,8 +229,8 @@ module.exports = function (db, collection_name) {
if (validator.hasErrors()) {
callback(null, validator);
} else {
- MODEL.mongo('update' + namespace, {'_id': document._id}, update, options, function (error, doc) {
- callback(null, validator);
+ MODEL.mongo('update' + namespace, {_id: document._id}, update, options, function (error, _) {
+ callback(error, validator);
});
}
});
@@ -224,7 +241,7 @@ module.exports = function (db, collection_name) {
/**
* Get the document ready to embed to this model according to the skeleton
- * TODO: Refactor this method!
+ * TODO: Refactor this method! Or take it away as a helper
*
* @param {String} name
* @param {Object} document
@@ -318,14 +335,10 @@ module.exports = function (db, collection_name) {
/**
* Update all the embedded objects
*
- * @param {ObjectID} id
- * Id of the embedded document to be updated
- * @param {String} document_name
- * Name for the embedded document
- * @param {Object} document
- * Document to be embedded
- * @param {Object} options
- * Update options. Defaults to {upsert: true, multi: true}
+ * @param {ObjectID} id - Id of the embedded document to be updated
+ * @param {String} document_name - Name for the embedded document
+ * @param {Object} document - Document to be embedded
+ * @param {Object} options - Update options. Defaults to {upsert: false, multi: true}
* @param {Function} callback
*
* @returns itself
@@ -346,34 +359,23 @@ module.exports = function (db, collection_name) {
options = options || {};
}
- options = _.defaults(options, {upsert: true, multi: true});
+ options = _.extend({upsert: false, multi: true}, options);
Object.keys(query).forEach(function (attr) {
scoped_query[document_name + '.' + attr] = query[attr];
});
- // Use native driver call to avoid recursive hooks
- MODEL.getCollection(function (error, collection) {
- if (error) {
- return callback(error);
- }
-
- collection.update(scoped_query, {'$set': update}, options, callback);
- });
+ MODEL.mongo({method: 'update', hooks: false}, scoped_query, {'$set': update}, options, callback);
return MODEL;
};
/**
* Push an embedded document.
*
- * @param {Object} query
- * Query to match the objects to push the embedded doc
- * @param {String} document_name
- * Name for the embedded document array
- * @param {Object} document
- * Document to be push
- * @param {Object} options
- * Update options. Defaults to {upsert: true, multi: true}
+ * @param {Object} query - Query to match the objects to push the embedded doc
+ * @param {String} document_name - Name for the embedded document array
+ * @param {Object} document - Document to be push
+ * @param {Object} options - Update options. Defaults to {upsert: false, multi: true}
* @param {Function} callback
*
* @returns itself
@@ -391,16 +393,8 @@ module.exports = function (db, collection_name) {
options = options || {};
}
- options = _.defaults(options, {upsert: true, multi: true});
-
- // Use native driver call to avoid recursive hooks
- MODEL.getCollection(function (error, collection) {
- if (error) {
- return callback(error);
- }
-
- collection.update(query, {'$push': update}, options, callback);
- });
+ options = _.extend({upsert: false, multi: true}, options);
+ MODEL.mongo({method: 'update', hooks: false}, query, {'$push': update}, options, callback);
return MODEL;
};
View
2 package.json
@@ -1,7 +1,7 @@
{
"name": "mongolia",
"description": "Layer on top of the mongodb driver to implement data logic.",
- "version": "1.3.5",
+ "version": "1.4.0",
"author": "Pau Ramon <masylum@gmail.com>",
"contributors": [
{"name": "Pau Ramon", "email": "masylum@gmail.com"},
View
62 test/helpers/collection_proxy_test.js
@@ -5,44 +5,54 @@ var testosterone = require('testosterone')({post: 3000, sync: true, title: 'mong
Collection = require('mongodb/lib/mongodb/collection').Collection.prototype,
Model;
+function _stubGetCollection() {
+ gently.expect(Model, 'getCollection', function (_callback) {
+ _callback(null, Collection);
+ });
+}
+
testosterone
.before(function () {
- Model = {};
+ Model = require('../../').model({}, 'models');
})
.add('`proxy` delegates every call to collection_proxy or native driver collection functions', function () {
- var cb = function () {},
+ var cb = function cb() {},
args = ['zemba', cb];
['update', 'insert', 'findArray'].forEach(function (method) {
+ _stubGetCollection();
gently.expect(CollectionProxy, method, function (_model, _collection, _args, _callback) {
assert.equal(Model, _model);
assert.equal(Collection, _collection);
assert.deepEqual(args, _args);
assert.equal(cb, _callback);
});
- CollectionProxy.proxy(Model, method, undefined, Collection, args, cb);
+ CollectionProxy.proxy(Model, {method: method, hooks: true}, args, cb);
});
- ['find', 'foo'].forEach(function (method) {
+ ['find', 'foo', 'update', 'insert', 'findArray'].forEach(function (method) {
+ _stubGetCollection();
gently.expect(Collection, method, function (_arg, _callback) {
assert.equal(_arg, args[0]);
assert.deepEqual(_callback, args[1]);
});
- CollectionProxy.proxy(Model, method, undefined, Collection, args, cb);
+ CollectionProxy.proxy(Model, {method: method, hooks: false}, args, cb);
});
})
- .add('`proxy` modifies the arguments according to `namespaces`', function () {
+ .add('`proxy` modifies the arguments according to `namespaces` unless the `namespacing` option is set to `false`', function () {
var cb = function () {},
args = [{foo: 'bar'}, cb];
Model.namespaces = {
foo: ['zemba', 'fleiba']
};
+ // namespacing: true
+ _stubGetCollection();
gently.expect(CollectionProxy.namespacer, 'filter', function (_namespaces, _namespace, _fn, _args) {
assert.deepEqual(_namespaces, Model.namespaces);
assert.deepEqual(_namespace, 'foo');
@@ -55,19 +65,27 @@ testosterone
assert.deepEqual(_callback, args[1]);
});
- CollectionProxy.proxy(Model, 'find', 'foo', Collection, args, cb);
+ CollectionProxy.proxy(Model, {method: 'find', namespace: 'foo', namespacing: true}, args, cb);
+
+ // namespacing: false
+ _stubGetCollection();
+ gently.expect(Collection, 'find', function (_arg, _callback) {
+ assert.equal(_arg, args[0]);
+ assert.deepEqual(_callback, args[1]);
+ });
+
+ CollectionProxy.proxy(Model, {method: 'find', namespace: 'foo', namespacing: false}, args, cb);
})
- .add('`proxy` maps the arguments according to `maps`', function () {
+ .add('`proxy` maps the arguments according to `maps` unless the `mapping` option is set to `false`', function () {
var cb = function () {},
- args = [{foo: 'bar'}, cb];
+ args = [{foo: 'bar', _id: '3'}, cb];
- Model.maps = {
- foo: Boolean
- };
+ Model.maps = {foo: Boolean};
+ _stubGetCollection();
gently.expect(CollectionProxy.mapper, 'map', function (_maps, _fn, _args) {
- assert.deepEqual(_maps, Model.maps);
+ assert.deepEqual(_maps, {foo: Boolean});
assert.equal(_fn, 'find');
assert.deepEqual(_args, args);
});
@@ -77,30 +95,40 @@ testosterone
assert.deepEqual(_callback, args[1]);
});
- CollectionProxy.proxy(Model, 'find', undefined, Collection, args, cb);
+ CollectionProxy.proxy(Model, {method: 'find', mapping: true}, args, cb);
+
+ _stubGetCollection();
+ gently.expect(Collection, 'find', function (_arg, _callback) {
+ assert.equal(_arg, args[0]);
+ assert.deepEqual(_callback, args[1]);
+ });
+
+ CollectionProxy.proxy(Model, {method: 'find', mapping: false}, args, cb);
})
.add('`proxy` can be called with no callback', function () {
var args = ['zemba'];
['update', 'insert', 'findArray'].forEach(function (method) {
+ _stubGetCollection();
gently.expect(CollectionProxy, method, function (_model, _collection, _args, _callback) {
assert.equal(_model, Model);
assert.equal(_collection, Collection);
assert.deepEqual(_args, args);
assert.equal(typeof _callback, 'function');
});
- CollectionProxy.proxy(Model, method, undefined, Collection, args);
+ CollectionProxy.proxy(Model, {method: method, hooks: true}, args);
});
- ['find', 'foo'].forEach(function (method) {
+ ['find', 'foo', 'update', 'insert', 'findArray'].forEach(function (method) {
+ _stubGetCollection();
gently.expect(Collection, method, function (_arg, _callback) {
assert.equal(_arg, args[0]);
assert.deepEqual(_callback, args[1]);
});
- CollectionProxy.proxy(Model, method, undefined, Collection, args);
+ CollectionProxy.proxy(Model, {method: method, hooks: false}, args);
});
})
View
15 test/helpers/mapper_test.js
@@ -3,16 +3,13 @@ var testosterone = require('testosterone')({post: 3000, sync: true, title: 'mong
Mapper = require('./../../lib/helpers/mapper');
testosterone
- .before(function () {
- })
-
- .add('`filterUpdate` should filter documents before being inserted or updated', function () {
- var test = Mapper.filterUpdate
+ .add('`mapDocument` should filter documents before being inserted or updated', function () {
+ var test = Mapper.mapDocument
, arg
, toUpper = function (val) {
return val.toUpperCase();
}
- , update = {zemba: 'FOO', nested: {id: 123, name: '300'}, list: [1,2,3], foo: true}
+ , update = {zemba: 'FOO', nested: {id: 123, name: '300'}, list: [1, 2, 3], foo: true}
, maps = { zemba: toUpper
, nested: { id: Number
, name: String
@@ -22,22 +19,22 @@ testosterone
};
// document
- arg = {zemba: 'foo', nested: {id: '123', name: 300}, list: [1,2,3], foo: 'true'};
+ arg = {zemba: 'foo', nested: {id: '123', name: 300}, list: [1, 2, 3], foo: 'true'};
test(maps, arg);
assert.deepEqual(arg, update);
assert.equal(typeof arg.nested.id, typeof update.nested.id);
assert.equal(typeof arg.nested.name, typeof update.nested.name);
// array
- arg = [{zemba: 'fleiba'}, {zemba: 'foo', nested: {id: '123', name: 300}, list: [1,2,3], foo: 'true'}];
+ arg = [{zemba: 'fleiba'}, {zemba: 'foo', nested: {id: '123', name: 300}, list: [1, 2, 3], foo: 'true'}];
test(maps, arg);
assert.deepEqual(arg[0], {zemba: 'FLEIBA'});
assert.deepEqual(arg[1], update);
assert.equal(typeof arg[1].nested.id, typeof update.nested.id);
assert.equal(typeof arg[1].nested.name, typeof update.nested.name);
// with sepcial ops
- arg = {'$set': {zemba: 'foo', nested: {id: '123', name: 300}, list: [1,2,3], foo: 'true'}};
+ arg = {'$set': {zemba: 'foo', nested: {id: '123', name: 300}, list: [1, 2, 3], foo: 'true'}};
test(maps, arg);
assert.deepEqual(arg, {'$set': update});
assert.equal(typeof arg.$set.nested.id, typeof update.nested.id);
View
8 test/integration/integration_test.js
@@ -78,7 +78,13 @@ db.open(function (error) {
var User = Model(db, 'users'),
Country = Model(db, 'countries');
- Country.mongo('findOne', {name: 'Andorra'}, function (error, doc) {
+ Country.maps = {
+ name: function capitalize(val) {
+ return val.charAt(0).toUpperCase() + val.slice(1);
+ }
+ };
+
+ Country.mongo('findOne', {name: 'andorra'}, function (error, doc) {
User.updateEmbeddedDocument({_id: doc._id}, 'country', {name: 'France'}, {}, function (error) {
User.mongo('findOne', {name: 'zemba'}, done(function (error, doc) {
assert.equal(doc.country.name, 'France');
View
102 test/model_test.js
@@ -4,7 +4,7 @@ var testosterone = require('testosterone')({sync: true, title: 'mongolia/model.j
Model = require('./../lib/model'),
- _db = {},
+ _db = {bson_serializer: {}},
_mock_validator = function (ret) {
return {
hasErrors: function () {
@@ -44,18 +44,11 @@ testosterone
})
.add('`mongo` proxies collection calls', function () {
- var callback = function (error, doc) {},
- query = {name: 'zemba'},
- collection = {collectionName: 'users'};
+ var callback = function (error, doc) {}
+ , query = {name: 'zemba'};
- gently.expect(_db, 'collection', function (_collection_name, _callback) {
- _callback(null, collection);
- });
-
- gently.expect(User.collection_proxy, 'proxy', function (_model, _fn, _namespace, _collection, _args, _callback) {
- assert.equal(_fn, 'findArray');
- assert.deepEqual(_namespace, undefined);
- assert.deepEqual(_collection, collection);
+ gently.expect(User.collection_proxy, 'proxy', function (_model, _options, _args, _callback) {
+ assert.deepEqual(_options, {hooks: true, namespacing: true, mapping: true, method: 'findArray'});
assert.deepEqual(_args[0], query);
assert.equal(_callback, callback);
});
@@ -64,37 +57,53 @@ testosterone
})
.add('`mongo` proxies namespaced collection calls', function () {
- var callback = function (error, doc) {},
- query = {name: 'zemba'},
- collection = {collectionName: 'users'};
-
- gently.expect(_db, 'collection', function (_collection_name, _callback) {
- _callback(null, collection);
- });
-
- gently.expect(User.collection_proxy, 'proxy', function (_model, _fn, _namespace, _collection, _args, _callback) {
- assert.equal(_fn, 'findArray');
- assert.deepEqual(_namespace, 'public');
- assert.deepEqual(_collection, collection);
+ var callback = function (error, doc) {}
+ , query = {name: 'zemba'};
+
+ gently.expect(User.collection_proxy, 'proxy', function (_model, _options, _args, _callback) {
+ assert.deepEqual(_options, {
+ hooks: true
+ , namespacing: true
+ , mapping: true
+ , method: 'findArray'
+ , namespace: 'public'
+ });
assert.deepEqual(_args[0], query);
assert.equal(_callback, callback);
});
User.mongo('findArray:public', query, callback);
})
- .add('`mongo` can be called without a callback', function () {
- var query = {name: 'zemba'},
- collection = {collectionName: 'users'};
-
- gently.expect(_db, 'collection', function (_collection_name, _callback) {
- _callback(null, collection);
+ .add('`mongo` proxies with options', function () {
+ var callback = function (error, doc) {}
+ , query = {name: 'zemba'};
+
+ gently.expect(User.collection_proxy, 'proxy', function (_model, _options, _args, _callback) {
+ assert.deepEqual(_options, {
+ hooks: false
+ , namespacing: true
+ , mapping: true
+ , method: 'findArray'
+ , namespace: 'public'
+ });
+ assert.deepEqual(_args[0], query);
+ assert.equal(_callback, callback);
});
- gently.expect(User.collection_proxy, 'proxy', function (_model, _fn, _namespace, _collection, _args, _callback) {
- assert.equal(_fn, 'findArray');
- assert.deepEqual(_namespace, undefined);
- assert.deepEqual(_collection, collection);
+ User.mongo({method: 'findArray', namespace: 'public', hooks: false}, query, callback);
+ })
+
+ .add('`mongo` can be called without a callback', function () {
+ var query = {name: 'zemba'};
+
+ gently.expect(User.collection_proxy, 'proxy', function (_model, _options, _args, _callback) {
+ assert.deepEqual(_options, {
+ hooks: true
+ , namespacing: true
+ , mapping: true
+ , method: 'findArray'
+ });
assert.deepEqual(_args[0], query);
assert.equal(typeof _callback, 'function');
});
@@ -211,7 +220,7 @@ testosterone
.add('`validateAndUpdate` when the model is valid updates it afterwards', function () {
var query = {foo: 'bar'}
- , document = {foo: 'bar', fleiba: 'foo'}
+ , document = {_id: '123', foo: 'bar'}
, update = {'$set': {fleiba: 'John'}}
, validator = _mock_validator(false)
, options = {}
@@ -233,7 +242,9 @@ testosterone
gently.expect(User, 'mongo', function (_action, _document, _update, _options, _callback) {
assert.equal(_action, 'update');
+ assert.deepEqual(_document._id, document._id);
assert.deepEqual(_update, update);
+ assert.deepEqual(_options, options);
_callback(null, _document);
});
@@ -318,22 +329,18 @@ testosterone
.add('`updateEmbeddedDocument` updates embedded objects', function () {
var embeddedDocument = {name: 'john', surname: 'snow', bo: 'vale'},
- options = {},
- collection = {foo: 'bar'},
+ options = {upsert: true},
callback = function () {};
User.skeletons = {
author: ['_id', 'name', 'surname']
};
- gently.expect(User, 'getCollection', function (_callback) {
- _callback(null, collection);
- });
-
- gently.expect(collection, 'update', function (_query, _update, _options, _callback) {
+ gently.expect(User, 'mongo', function (_opts, _query, _update, _options, _callback) {
+ assert.deepEqual(_opts, {method: 'update', hooks: false});
assert.deepEqual(_query, {'author._id': 1});
assert.deepEqual(_update, {'$set': {'author.name': 'john', 'author.surname': 'snow'}});
- assert.equal(_options, options);
+ assert.deepEqual(_options, {upsert: true, multi: true});
assert.equal(_callback, callback);
});
@@ -352,14 +359,11 @@ testosterone
return embeddedDocument;
});
- gently.expect(User, 'getCollection', function (_callback) {
- _callback(null, collection);
- });
-
- gently.expect(collection, 'update', function (_query, _update, _options, _callback) {
+ gently.expect(User, 'mongo', function (_opts, _query, _update, _options, _callback) {
+ assert.deepEqual(_opts, {method: 'update', hooks: false});
assert.deepEqual(_query, {_id: 1});
assert.deepEqual(_update, {'$push': {author: embeddedDocument}});
- assert.deepEqual(_options, {upsert: true, multi: true});
+ assert.deepEqual(_options, {upsert: false, multi: true});
});
User.pushEmbeddedDocument({_id: 1}, 'author', embeddedDocument);

0 comments on commit a859383

Please sign in to comment.