Skip to content
Browse files

Merge branch 'upstreamMaster' of github.com:jvskelton/jugglingdb into…

… jvskelton-upstreamMaster
  • Loading branch information...
2 parents 436cdb8 + 99de934 commit a59bdd97eb1aa824a5c57f58864bf4d4076285cb @1602 committed Jul 3, 2014
Showing with 421 additions and 100 deletions.
  1. +17 −9 README.md
  2. +64 −0 lib/adapters/memory.js
  3. +133 −78 lib/include.js
  4. +76 −9 lib/model.js
  5. +4 −0 lib/relations.js
  6. +35 −1 test/basic-querying.test.js
  7. +92 −3 test/include.test.js
View
26 README.md
@@ -1,5 +1,5 @@
[![Stories in Ready](https://badge.waffle.io/1602/jugglingdb.png?label=ready)](https://waffle.io/1602/jugglingdb)
-## About [<img src="https://secure.travis-ci.org/1602/jugglingdb.png" />](http://travis-ci.org/#!/1602/jugglingdb)
+## About [<img src="https://api.travis-ci.org/1602/jugglingdb.svg" />](http://travis-ci.org/#!/1602/jugglingdb)
[JugglingDB(3)](http://jugglingdb.co) is cross-db ORM for nodejs, providing
**common interface** to access most popular database formats. Currently
@@ -46,62 +46,62 @@ check following list of available adapters
<td><a href="http://www.mongodb.org"><img src="http://mongodb.ru/favicon.ico" alt="MongoDB" /></a> MongoDB</td>
<td><a href="https://github.com/jugglingdb/mongodb-adapter">jugglingdb/mongodb-adapter</a></td>
<td><a href="https://github.com/anatoliychakkaev">Anatoliy Chakkaev</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/mongodb-adapter"><img src="https://travis-ci.org/jugglingdb/mongodb-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/mongodb-adapter"><img src="https://travis-ci.org/jugglingdb/mongodb-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- MySQL -->
<tr>
<td><a href="http://www.mysql.com/"><img src="https://github.com/1602/jugglingdb/raw/master/media/mysql.ico" style="vertical-align:middle"" alt="MySQL" /></a> MySQL</td>
<td><a href="https://github.com/jugglingdb/mysql-adapter">jugglingdb/mysql</a></td>
<td><a href="https://github.com/dgsan">dgsan</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/mysql-adapter"><img src="https://travis-ci.org/jugglingdb/mysql-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/mysql-adapter"><img src="https://travis-ci.org/jugglingdb/mysql-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- CouchDB / nano -->
<tr>
<td><a href="http://couchdb.apache.org/"><img width="16" src="http://couchdb.apache.org/favicon.ico" style="vertical-align:middle"" alt="CouchDB" /></a> CouchDB / nano</td>
<td><a href="https://github.com/jugglingdb/nano-adapter">jugglingdb/nano-adapter</a></td>
<td><a href="https://github.com/nrw">Nicholas Westlake</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/nano-adapter"><img src="https://travis-ci.org/jugglingdb/nano-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/nano-adapter"><img src="https://travis-ci.org/jugglingdb/nano-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- PostgreSQL -->
<tr>
<td><a href="http://www.postgresql.org/"><img src="http://www.postgresql.org/favicon.ico" style="vertical-align:middle"" alt="PostgreSQL" /></a> PostgreSQL</td>
<td><a href="https://github.com/jugglingdb/postgres-adapter">jugglingdb/postgres-adapter</a></td>
<td><a href="https://github.com/anatoliychakkaev">Anatoliy Chakkaev</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/postgres-adapter"><img src="https://travis-ci.org/jugglingdb/postgres-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/postgres-adapter"><img src="https://travis-ci.org/jugglingdb/postgres-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- Redis -->
<tr>
<td><a href="http://redis.io/"><img src="http://redis.io/images/favicon.png" alt="Redis" /></a> Redis</td>
<td><a href="https://github.com/jugglingdb/redis-adapter">jugglingdb-redis</a></td>
<td><a href="https://github.com/anatoliychakkaev">Anatoliy Chakkaev</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/redis-adapter"><img src="https://travis-ci.org/jugglingdb/redis-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/redis-adapter"><img src="https://travis-ci.org/jugglingdb/redis-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- RethinkDB -->
<tr>
<td><a href="http://www.rethinkdb.com/"><img src="http://www.rethinkdb.com/favicon.ico" alt="RethinkDB" width="16" height="16" /></a> RethinkDB</td>
<td><a href="https://github.com/fuwaneko/jugglingdb-rethink">jugglingdb-rethink</a></td>
<td><a href="https://github.com/fuwaneko">Tewi Inaba</a></td>
- <td><a href="https://travis-ci.org/fuwaneko/jugglingdb-rethink"><img src="https://travis-ci.org/fuwaneko/jugglingdb-rethink.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/fuwaneko/jugglingdb-rethink"><img src="https://travis-ci.org/fuwaneko/jugglingdb-rethink.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- SQLite -->
<tr>
<td><a href="http://www.sqlite.org/"><img width="16" src="https://github.com/1602/jugglingdb/raw/master/media/sqlite.png" style="vertical-align:middle" alt="SQLite" /></a> SQLite</td>
<td><a href="https://github.com/jugglingdb/sqlite3-adapter">jugglingdb/sqlite3-adapter</a></td>
<td><a href="https://github.com/anatoliychakkaev">Anatoliy Chakkaev</a></td>
- <td><a href="https://travis-ci.org/jugglingdb/sqlite3-adapter"><img src="https://travis-ci.org/jugglingdb/sqlite3-adapter.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/jugglingdb/sqlite3-adapter"><img src="https://travis-ci.org/jugglingdb/sqlite3-adapter.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<!-- ArangoDB -->
<tr>
<td><a href="http://www.arangodb.org/"><img src="http://www.arangodb.org/wp-content/themes/triagens/images/favicon.ico" style="vertical-align:middle" alt="ArangoDB" /></a> ArangoDB</td>
<td><a href="https://github.com/m0ppers/jugglingdb-arango">jugglingdb-arango</a></td>
<td><a href="https://github.com/m0ppers">Andreas Streichardt</a></td>
- <td><a href="https://travis-ci.org/m0ppers/jugglingdb-arango"><img src="https://travis-ci.org/m0ppers/jugglingdb-arango.png?branch=master" alt="Build Status" /></a></td>
+ <td><a href="https://travis-ci.org/m0ppers/jugglingdb-arango"><img src="https://travis-ci.org/m0ppers/jugglingdb-arango.svg?branch=master" alt="Build Status" /></a></td>
</tr>
<tr>
<td>WebService</td>
@@ -122,6 +122,14 @@ check following list of available adapters
maintainer</td>
<td>n/a</td>
</tr>
+
+ <!-- DynamoDB -->
+ <tr>
+ <td><a href="http://en.wikipedia.org/wiki/Amazon_DynamoDB"> DynamoDB </a></td>
+ <td><a href="https://github.com/tmpaul/jugglingdb-dynamodb">tmpaul/jugglingdb-dynamodb</a></td>
+ <td><a href="https://github.com/tmpaul">tmpaul</a></td>
+ <td><a href="https://travis-ci.org/tmpaul/jugglingdb-dynamodb"><img src="https://travis-ci.org/tmpaul/jugglingdb-dynamodb.svg?branch=master" alt="Build Status" /></a></td>
+ </tr>
</tbody>
</table>
View
64 lib/adapters/memory.js
@@ -105,6 +105,70 @@ Memory.prototype.fromDb = function(model, data) {
return data;
};
+Memory.prototype.select = function all(model, filter, callback) {
+ var self = this;
+ var table = this.table(model);
+ var nodes;
+ if (filter && filter.attributes) {
+ nodes = filter.attributes;
+ } else {
+ nodes = Object.keys(this.cache[table]).map(function (key) {
+ return this.fromDb(model, this.cache[table][key]);
+ }.bind(this));
+ }
+ if (filter) {
+
+ // do we need some sorting?
+ if (filter.order) {
+ var props = this._models[model].properties;
+ var orders = filter.order;
+ if (typeof filter.order === "string") {
+ orders = [filter.order];
+ }
+ orders.forEach(function (key, i) {
+ var reverse = 1;
+ var m = key.match(/\s+(A|DE)SC$/i);
+ if (m) {
+ key = key.replace(/\s+(A|DE)SC/i, '');
+ if (m[1].toLowerCase() === 'de') reverse = -1;
+ }
+ orders[i] = {"key": key, "reverse": reverse};
+ });
+ nodes = nodes.sort(sorting.bind(orders));
+ }
+
+ // do we need some filtration?
+ if (filter.where) {
+ nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
+ }
+
+ // limit/skip
+ filter.skip = filter.skip || 0;
+ filter.limit = filter.limit || nodes.length;
+ nodes = nodes.slice(filter.skip, filter.skip + filter.limit);
+
+ }
+
+ process.nextTick(function () {
+ if (filter && filter.include) {
+ self._models[model].model.include(nodes, filter.include, callback);
+ } else {
+ callback(null, nodes);
+ }
+ });
+
+ function sorting(a, b) {
+ for (var i=0, l=this.length; i<l; i++) {
+ if (a[this[i].key] > b[this[i].key]) {
+ return 1*this[i].reverse;
+ } else if (a[this[i].key] < b[this[i].key]) {
+ return -1*this[i].reverse;
+ }
+ }
+ return 0;
+ }
+};
+
Memory.prototype.all = function all(model, filter, callback) {
var self = this;
var table = this.table(model);
View
211 lib/include.js
@@ -1,4 +1,9 @@
/**
+ * Dependencies
+ */
+var i8n = require('inflection');
+
+/**
* Include mixin for ./model.js
*/
var AbstractClass = require('./model.js');
@@ -22,13 +27,12 @@ var AbstractClass = require('./model.js');
* - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...
*
*/
+
+ /*jshint sub: true */
+
AbstractClass.include = function (objects, include, cb) {
- var self = this;
- if (
- (include.constructor.name == 'Array' && include.length == 0) ||
- (include.constructor.name == 'Object' && Object.keys(include).length == 0)
- ) {
+ if ((include.constructor.name == 'Array' && include.length === 0) || (include.constructor.name == 'Object' && Object.keys(include).length === 0)) {
cb(null, objects);
return;
}
@@ -39,21 +43,30 @@ AbstractClass.include = function (objects, include, cb) {
var objsByKeys = {};
var nbCallbacks = 0;
+
for (var i = 0; i < include.length; i++) {
- var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
+ var callback = processIncludeItem(this, objects, include[i], keyVals, objsByKeys);
if (callback !== null) {
- nbCallbacks++;
- callback(function() {
- nbCallbacks--;
- if (nbCallbacks == 0) {
- cb(null, objects);
- }
- });
+ if (callback instanceof Error) {
+ cb(callback);
+ } else {
+ includeItemCallback(callback);
+ }
} else {
cb(null, objects);
}
}
+ function includeItemCallback(itemCb) {
+ nbCallbacks++;
+ itemCb(function () {
+ nbCallbacks--;
+ if (nbCallbacks === 0) {
+ cb(null, objects);
+ }
+ });
+ }
+
function processIncludeJoin(ij) {
if (typeof ij === 'string') {
ij = [ij];
@@ -69,89 +82,131 @@ AbstractClass.include = function (objects, include, cb) {
}
return ij;
}
+};
+
+function processIncludeItem(cls, objs, include, keyVals, objsByKeys) {
+ var relations = cls.relations;
+ var relationName, subInclude;
+
+ if (include.constructor.name === 'Object') {
+ relationName = Object.keys(include)[0];
+ subInclude = include[relationName];
+ } else {
+ relationName = include;
+ subInclude = [];
+ }
+ var relation = relations[relationName];
- function processIncludeItem(objs, include, keyVals, objsByKeys) {
- var relations = self.relations;
+ if (!relation) {
+ return new Error('Relation "' + relationName + '" is not defined for ' + cls.modelName + ' model');
+ }
- if (include.constructor.name === 'Object') {
- var relationName = Object.keys(include)[0];
- var subInclude = include[relationName];
- } else {
- var relationName = include;
- var subInclude = [];
- }
- var relation = relations[relationName];
+ var req = {
+ 'where': {}
+ };
- if (!relation) {
- return function() {
- cb(new Error('Relation "' + relationName + '" is not defined for ' + self.modelName + ' model'));
+ if (!keyVals[relation.keyFrom]) {
+ objsByKeys[relation.keyFrom] = {};
+ objs.filter(Boolean).forEach(function (obj) {
+ if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) {
+ objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = [];
}
+ objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
+ });
+ keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
+ }
+
+ // deep clone is necessary since inq seems to change the processed array
+ var keysToBeProcessed = {};
+ var inValues = [];
+ for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
+ keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
+ if (keyVals[relation.keyFrom][j] !== 'null' && keyVals[relation.keyFrom][j] !== 'undefined') {
+ inValues.push(keyVals[relation.keyFrom][j]);
}
+ }
- var req = {'where': {}};
+ var _model, _through;
- if (!keyVals[relation.keyFrom]) {
- objsByKeys[relation.keyFrom] = {};
- objs.filter(Boolean).forEach(function(obj) {
- if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) {
- objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = [];
+ function done(err, objsIncluded, cb) {
+ var objectsFrom;
+
+ for (var i = 0; i < objsIncluded.length; i++) {
+ delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
+ objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
+
+ for (var j = 0; j < objectsFrom.length; j++) {
+ if (!objectsFrom[j].__cachedRelations) {
+ objectsFrom[j].__cachedRelations = {};
}
- objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
- });
- keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
+ if (relation.multiple) {
+ if (!objectsFrom[j].__cachedRelations[relationName]) {
+ objectsFrom[j].__cachedRelations[relationName] = [];
+ }
+
+ if (_through) {
+ objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i].__cachedRelations[_through]);
+ } else {
+ objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
+ }
+ } else {
+ if (_through) {
+ objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i].__cachedRelations[_through];
+ } else {
+ objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
+ }
+ }
+ }
}
- if (keyVals[relation.keyFrom].length > 0) {
- // deep clone is necessary since inq seems to change the processed array
- var keysToBeProcessed = {};
- var inValues = [];
- for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
- keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
- if (keyVals[relation.keyFrom][j] !== 'null' && keyVals[relation.keyFrom][j] !== 'undefined') {
- inValues.push(keyVals[relation.keyFrom][j]);
+ // No relation have been found for these keys
+ for (var key in keysToBeProcessed) {
+ objectsFrom = objsByKeys[relation.keyFrom][key];
+ for (var k = 0; k < objectsFrom.length; k++) {
+ if (!objectsFrom[k].__cachedRelations) {
+ objectsFrom[k].__cachedRelations = {};
}
+ objectsFrom[k].__cachedRelations[relationName] = relation.multiple ? [] : null;
}
+ }
+
+ cb(err, objsIncluded);
+ }
- req['where'][relation.keyTo] = {inq: inValues};
+ if (keyVals[relation.keyFrom].length > 0) {
+
+ if (relation.modelThrough) {
+ req['where'][relation.keyTo] = { inq: inValues };
+
+ _model = cls.schema.models[relation.modelThrough.modelName];
+ _through = i8n.camelize(relation.modelTo.modelName, true);
+
+ } else {
+ req['where'][relation.keyTo] = { inq: inValues };
req['include'] = subInclude;
+ }
- return function(cb) {
- relation.modelTo.all(req, function(err, objsIncluded) {
- for (var i = 0; i < objsIncluded.length; i++) {
- delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
- var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
- for (var j = 0; j < objectsFrom.length; j++) {
- if (!objectsFrom[j].__cachedRelations) {
- objectsFrom[j].__cachedRelations = {};
- }
- if (relation.multiple) {
- if (!objectsFrom[j].__cachedRelations[relationName]) {
- objectsFrom[j].__cachedRelations[relationName] = [];
- }
- objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
- } else {
- objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
- }
- }
- }
+ return function (cb) {
- // No relation have been found for these keys
- for (var key in keysToBeProcessed) {
- var objectsFrom = objsByKeys[relation.keyFrom][key];
- for (var j = 0; j < objectsFrom.length; j++) {
- if (!objectsFrom[j].__cachedRelations) {
- objectsFrom[j].__cachedRelations = {};
- }
- objectsFrom[j].__cachedRelations[relationName] = relation.multiple ? [] : null;
- }
- }
- cb(err, objsIncluded);
+ if (_through) {
+
+ relation.modelThrough.all(req, function (err, objsIncluded) {
+
+ _model.include(objsIncluded, _through, function (err, throughIncludes) {
+
+ done(err, throughIncludes, cb);
+ });
});
- };
- }
+ } else {
- return null;
+ relation.modelTo.all(req, function (err, objsIncluded) {
+
+ done(err, objsIncluded, cb);
+ });
+ }
+ };
}
-}
+ return null;
+}
View
85 lib/model.js
@@ -489,12 +489,13 @@ AbstractClass.all = function all(params, cb) {
if (!params || !params.onlyKeys) {
data.forEach(function (d, i) {
var obj = new constr;
- d = constr._fromDB(d);
- obj._initProperties(d, false);
- if (params && params.include && params.collect) {
- data[i] = obj.__cachedRelations[params.collect];
- } else {
- data[i] = obj;
+ if (params && !params.attributes) {
+ obj._initProperties(d, false);
+ if (params && params.include && params.collect) {
+ data[i] = obj.__cachedRelations[params.collect];
+ } else {
+ data[i] = obj;
+ }
}
});
}
@@ -509,6 +510,72 @@ AbstractClass.all = function all(params, cb) {
};
/**
+ * Find all instances of Model, matched by query, unless specific columns are
+* provided in the attributes param, which will return either an array of objects(if array columns provided)
+* or an array of column data if string literal given.
+ * make sure you have marked as `index: true` fields for filter or sort
+ *
+ * @param {Object} params (optional)
+ *
+ * - where: Object `{ key: val, key2: {gt: 'val2'}}`
+* - attributes: Array '['id, 'name'] or String 'id'
+ * - include: String, Object or Array. See AbstractClass.include documentation.
+ * - order: String
+ * - limit: Number
+ * - skip: Number
+ *
+ * @param {Function} callback (required) called with arguments:
+ *
+ * - err (null or Error)
+ * - Array of instances
+ */
+AbstractClass.select = function select(params, cb) {
+ if (arguments.length === 1) {
+ cb = params;
+ params = null;
+ }
+
+ this.schema.models[this.modelName].all(params, function (err, data) {
+ if (data && data.forEach) {
+ if (params && params.attributes && data.length > 0 && typeof data[0] == 'object') {
+ var model = new this.schema.models[this.modelName]();
+ if (Object.keys(data[0]) && Object.getOwnPropertyNames(model).length <= Object.keys(data[0]).length) {
+ var blackList = Object.keys(data[0]).filter(function (currentKey) {
+ var isBlackListed = true;
+
+ if (Array.isArray(params.attributes)) {
+ params.attributes.forEach(function (key) {
+ if(key === currentKey)
+ isBlackListed = false;
+ });
+ } else if (params.attributes == currentKey) {
+ isBlackListed = false;
+ }
+ return isBlackListed;
+ });
+
+ data.forEach(function (item) {
+ blackList.forEach(function (key) {
+ delete item[key];
+ });
+ });
+
+ if (!Array.isArray(params.attributes)) {
+ data = data.map(function (item) {
+ return item[params.attributes];
+ });
+ }
+ }
+ }
+ cb(err, data);
+ }
+ else
+ cb(err, []);
+
+ }.bind(this));
+}
+
+/**
* Iterate through dataset and perform async method iterator. This method
* designed to work with large datasets loading data by batches.
*
@@ -782,8 +849,8 @@ AbstractClass.prototype.toObject = function (onlySchema, cachedRelations) {
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
// };
-AbstractClass.prototype.toJSON = function (cachedRelations) {
- return this.toObject(false, cachedRelations);
+AbstractClass.prototype.toJSON = function () {
+ return this.toObject(false, true);
};
@@ -962,4 +1029,4 @@ function defineReadonlyProp(obj, key, value) {
configurable: true,
value: value
});
-}
+}
View
4 lib/relations.js
@@ -59,6 +59,10 @@ Model.hasMany = function hasMany(anotherClass, params) {
destroy: destroy
};
if (params.through) {
+
+ // Append through relation, like modelTo
+ this.relations[methodName].modelThrough = params.through;
+
var fk2 = i8n.camelize(anotherClass.modelName + '_id', true);
scopeMethods.create = function create(data, done) {
if (typeof data !== 'object') {
View
36 test/basic-querying.test.js
@@ -13,7 +13,7 @@ describe('basic-querying', function() {
role: {type: String, index: true, limit: 100},
order: {type: Number, index: true, sort: true, limit: 100}
});
-
+
db.automigrate(done);
});
@@ -131,7 +131,41 @@ describe('basic-querying', function() {
done();
});
});
+ });
+
+ describe('select', function () {
+
+ it('should query collection and return given attribute as an array of Objects', function(done) {
+ User.select({attributes: ['id']}, function(err, users) {
+ should.exists(users);
+ should.not.exists(err);
+ users.should.be.instanceOf(Array);
+ users.pop().should.be.instanceOf(Object).and.have.property('id');
+ done();
+ });
+ });
+
+ it('should query collection and return given attribute as an array of Numbers', function(done) {
+ User.select({attributes: 'id'}, function(err, users) {
+ should.exists(users);
+ should.not.exists(err);
+ users.should.be.instanceOf(Array);
+ users.pop().should.be.a.Number;
+ done();
+ });
+ });
+ it('should query collection and return given attributes as an array of objects', function(done) {
+ User.select({attributes: ['id', 'name']}, function(err, users) {
+ should.exists(users);
+ should.not.exists(err);
+ should.not.exists(users.pop().mail);
+ should.not.exists(users.pop().order);
+ users.pop().should.be.instanceOf(Object).and.have.property('id');
+ users.pop().should.be.instanceOf(Object).and.have.property('name');
+ done();
+ });
+ });
});
describe('count', function() {
View
95 test/include.test.js
@@ -1,9 +1,11 @@
// This test written in mocha+should.js
var should = require('./init.js');
-var db, User, Post, Passport, City, Street, Building;
+var db, User, Post, Passport, City, Street, Building, Asset;
var nbSchemaRequests = 0;
+var createdUsers = [];
+
describe('include', function() {
before(setup);
@@ -40,6 +42,27 @@ describe('include', function() {
});
});
+ it('should fetch hasAndBelongsToMany relation', function(done) {
+ User.all({include: ['assets']}, function(err, users) {
+ should.not.exist(err);
+ should.exist(users);
+ users.length.should.be.ok;
+ users.forEach(function(user) {
+ user.__cachedRelations.should.have.property('assets');
+ if (user.id === createdUsers[0].id) {
+ user.__cachedRelations.assets.should.have.length(3);
+ }
+ if (user.id === createdUsers[1].id) {
+ user.__cachedRelations.assets.should.have.length(1);
+ }
+ user.__cachedRelations.assets.forEach(function(a) {
+ a.url.should.startWith('http://placekitten.com');
+ });
+ });
+ done();
+ });
+ });
+
it('should fetch Passport - Owner - Posts', function(done) {
Passport.all({include: {owner: 'posts'}}, function(err, passports) {
should.not.exist(err);
@@ -109,7 +132,6 @@ describe('include', function() {
done();
});
});
-
});
function setup(done) {
@@ -127,16 +149,20 @@ function setup(done) {
Post = db.define('Post', {
title: String
});
+ Asset = db.define('Asset', {
+ url: String
+ });
Passport.belongsTo('owner', {model: User});
User.hasMany('passports', {foreignKey: 'ownerId'});
User.hasMany('posts', {foreignKey: 'userId'});
Post.belongsTo('author', {model: User, foreignKey: 'userId'});
+ User.hasAndBelongsToMany('assets');
db.automigrate(function() {
- var createdUsers = [];
var createdPassports = [];
var createdPosts = [];
+ var createdAssets = [];
createUsers();
function createUsers() {
clearAndCreate(
@@ -182,6 +208,28 @@ function setup(done) {
],
function(items) {
createdPosts = items;
+ createAssets();
+ }
+ );
+ }
+
+ function createAssets() {
+ clearAndCreateScoped(
+ 'assets',
+ [
+ {url: 'http://placekitten.com/200/200'},
+ {url: 'http://placekitten.com/300/300'},
+ {url: 'http://placekitten.com/400/400'},
+ {url: 'http://placekitten.com/500/500'}
+ ],
+ [
+ createdUsers[0],
+ createdUsers[0],
+ createdUsers[0],
+ createdUsers[1]
+ ],
+ function(items) {
+ createdAssets = items;
done();
}
);
@@ -192,6 +240,7 @@ function setup(done) {
function clearAndCreate(model, data, callback) {
var createdItems = [];
+
model.destroyAll(function () {
nextItem(null, null);
});
@@ -209,3 +258,43 @@ function clearAndCreate(model, data, callback) {
itemIndex++;
}
}
+
+function clearAndCreateScoped(modelName, data, scope, callback) {
+ var createdItems = [];
+
+ var clearedItemIndex = 0;
+ if (scope && scope.length) {
+
+ scope.forEach(function (instance) {
+ instance[modelName].destroyAll(function (err) {
+ clearedItemIndex++;
+ if (clearedItemIndex >= scope.length) {
+ createItems();
+ }
+ });
+ });
+
+ } else {
+
+ callback(createdItems);
+ }
+
+ var itemIndex = 0;
+ function nextItem(err, lastItem) {
+ itemIndex++;
+
+ if (lastItem !== null) {
+ createdItems.push(lastItem);
+ }
+ if (itemIndex >= data.length) {
+ callback(createdItems);
+ return;
+ }
+ }
+
+ function createItems() {
+ scope.forEach(function (instance, instanceIndex) {
+ instance[modelName].create(data[instanceIndex], nextItem);
+ });
+ }
+}

0 comments on commit a59bdd9

Please sign in to comment.
Something went wrong with that request. Please try again.