merge
This commit is contained in:
commit
0d433c1c5d
4
Cakefile
4
Cakefile
|
@ -1,4 +0,0 @@
|
||||||
{spawn} = require 'child_process'
|
|
||||||
|
|
||||||
task 'build', 'Build lib/ from src/', ->
|
|
||||||
spawn 'coffee', ['-c', '-o', 'lib', 'src'], stdio: 'inherit'
|
|
|
@ -54,7 +54,7 @@ check following list of available adapters
|
||||||
|
|
||||||
<!-- CouchDB / nano -->
|
<!-- CouchDB / nano -->
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="http://couchdb.apache.org/"><img src="http://couchdb.apache.org/favicon.ico" style="vertical-align:middle"" alt="CouchDB" /></a> CouchDB / nano</td>
|
<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="/1602/jugglingdb-nano">jugglingdb-nano</a></td>
|
<td><a href="/1602/jugglingdb-nano">jugglingdb-nano</a></td>
|
||||||
<td><a href="/nrw">Nicholas Westlake</a></td>
|
<td><a href="/nrw">Nicholas Westlake</a></td>
|
||||||
<td><a href="https://travis-ci.org/1602/jugglingdb-nano"><img src="https://travis-ci.org/1602/jugglingdb-nano.png?branch=master" alt="Build Status" /></a></td>
|
<td><a href="https://travis-ci.org/1602/jugglingdb-nano"><img src="https://travis-ci.org/1602/jugglingdb-nano.png?branch=master" alt="Build Status" /></a></td>
|
||||||
|
@ -63,7 +63,7 @@ check following list of available adapters
|
||||||
<!-- PostgreSQL -->
|
<!-- PostgreSQL -->
|
||||||
<tr>
|
<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="http://www.postgresql.org/"><img src="http://www.postgresql.org/favicon.ico" style="vertical-align:middle"" alt="PostgreSQL" /></a> PostgreSQL</td>
|
||||||
<td><a href="/1602/jugglingdb-nano">jugglingdb-postgres</a></td>
|
<td><a href="/1602/jugglingdb-postgres">jugglingdb-postgres</a></td>
|
||||||
<td><a href="/anatoliychakkaev">Anatoliy Chakkaev</a></td>
|
<td><a href="/anatoliychakkaev">Anatoliy Chakkaev</a></td>
|
||||||
<td><a href="https://travis-ci.org/1602/jugglingdb-postgres"><img src="https://travis-ci.org/1602/jugglingdb-postgres.png?branch=master" alt="Build Status" /></a></td>
|
<td><a href="https://travis-ci.org/1602/jugglingdb-postgres"><img src="https://travis-ci.org/1602/jugglingdb-postgres.png?branch=master" alt="Build Status" /></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -78,7 +78,7 @@ check following list of available adapters
|
||||||
|
|
||||||
<!-- SQLite -->
|
<!-- SQLite -->
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="http://www.sqlite.org/"><img src="/1602/jugglingdb/raw/master/media/sqlite.png" style="vertical-align:middle"" alt="SQLite" /></a> SQLite</td>
|
<td><a href="http://www.sqlite.org/"><img width="16" src="/1602/jugglingdb/raw/master/media/sqlite.png" style="vertical-align:middle"" alt="SQLite" /></a> SQLite</td>
|
||||||
<td><a href="/1602/jugglingdb-sqlite3">jugglingdb-sqlite3</a></td>
|
<td><a href="/1602/jugglingdb-sqlite3">jugglingdb-sqlite3</a></td>
|
||||||
<td><a href="/anatoliychakkaev">Anatoliy Chakkaev</a></td>
|
<td><a href="/anatoliychakkaev">Anatoliy Chakkaev</a></td>
|
||||||
<td><a href="https://travis-ci.org/1602/jugglingdb-sqlite3"><img src="https://travis-ci.org/1602/jugglingdb-sqlite3.png?branch=master" alt="Build Status" /></a></td>
|
<td><a href="https://travis-ci.org/1602/jugglingdb-sqlite3"><img src="https://travis-ci.org/1602/jugglingdb-sqlite3.png?branch=master" alt="Build Status" /></a></td>
|
||||||
|
|
2
index.js
2
index.js
|
@ -7,7 +7,7 @@ exports.Validatable = require('./lib/validatable').Validatable;
|
||||||
exports.BaseSQL = require('./lib/sql');
|
exports.BaseSQL = require('./lib/sql');
|
||||||
|
|
||||||
exports.init = function (rw) {
|
exports.init = function (rw) {
|
||||||
if (typeof rw === 'string') {
|
if (global.railway) {
|
||||||
railway.orm = exports;
|
railway.orm = exports;
|
||||||
} else {
|
} else {
|
||||||
rw.orm = {Schema: exports.Schema, AbstractClass: exports.AbstractClass};
|
rw.orm = {Schema: exports.Schema, AbstractClass: exports.AbstractClass};
|
||||||
|
|
|
@ -58,6 +58,10 @@ AbstractClass.prototype._initProperties = function (data, applySetters) {
|
||||||
value: {}
|
value: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (data['__cachedRelations']) {
|
||||||
|
this.__cachedRelations = data['__cachedRelations'];
|
||||||
|
}
|
||||||
|
|
||||||
for (var i in data) this.__data[i] = this.__dataWas[i] = data[i];
|
for (var i in data) this.__data[i] = this.__dataWas[i] = data[i];
|
||||||
|
|
||||||
if (applySetters && ctor.setter) {
|
if (applySetters && ctor.setter) {
|
||||||
|
@ -297,6 +301,7 @@ AbstractClass.find = function find(id, cb) {
|
||||||
* @param {Object} params (optional)
|
* @param {Object} params (optional)
|
||||||
*
|
*
|
||||||
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
|
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
|
||||||
|
* - include: String, Object or Array. See AbstractClass.include documentation.
|
||||||
* - order: String
|
* - order: String
|
||||||
* - limit: Number
|
* - limit: Number
|
||||||
* - skip: Number
|
* - skip: Number
|
||||||
|
@ -387,6 +392,150 @@ AbstractClass.count = function (where, cb) {
|
||||||
this.schema.adapter.count(this.modelName, cb, where);
|
this.schema.adapter.count(this.modelName, cb, where);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to load relations of several objects and optimize numbers of requests.
|
||||||
|
*
|
||||||
|
* @param {Array} objects - array of instances
|
||||||
|
* @param {String}, {Object} or {Array} include - which relations you want to load.
|
||||||
|
* @param {Function} cb - Callback called when relations are loaded
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* - User.include(users, 'posts', function() {}); will load all users posts with only one additional request.
|
||||||
|
* - User.include(users, ['posts'], function() {}); // same
|
||||||
|
* - User.include(users, ['posts', 'passports'], function() {}); // will load all users posts and passports with two
|
||||||
|
* additional requests.
|
||||||
|
* - Passport.include(passports, {owner: 'posts'}, function() {}); // will load all passports owner (users), and all
|
||||||
|
* posts of each owner loaded
|
||||||
|
* - Passport.include(passports, {owner: ['posts', 'passports']}); // ...
|
||||||
|
* - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
) {
|
||||||
|
cb(null, objects);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
include = processIncludeJoin(include);
|
||||||
|
|
||||||
|
var keyVals = {};
|
||||||
|
var objsByKeys = {};
|
||||||
|
|
||||||
|
var nbCallbacks = 0;
|
||||||
|
for (var i = 0; i < include.length; i++) {
|
||||||
|
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
|
||||||
|
if (callback !== null) {
|
||||||
|
nbCallbacks++;
|
||||||
|
callback(function() {
|
||||||
|
nbCallbacks--;
|
||||||
|
if (nbCallbacks == 0) {
|
||||||
|
cb(null, objects);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processIncludeJoin(ij) {
|
||||||
|
if (typeof ij === 'string') {
|
||||||
|
ij = [ij];
|
||||||
|
}
|
||||||
|
if (ij.constructor.name === 'Object') {
|
||||||
|
var newIj = [];
|
||||||
|
for (var key in ij) {
|
||||||
|
var obj = {};
|
||||||
|
obj[key] = ij[key];
|
||||||
|
newIj.push(obj);
|
||||||
|
}
|
||||||
|
return newIj;
|
||||||
|
}
|
||||||
|
return ij;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processIncludeItem(objs, include, keyVals, objsByKeys) {
|
||||||
|
var relations = self.relations;
|
||||||
|
|
||||||
|
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 (!keyVals[relation.keyFrom]) {
|
||||||
|
objsByKeys[relation.keyFrom] = {};
|
||||||
|
for (var j = 0; j < objs.length; j++) {
|
||||||
|
if (!objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]]) {
|
||||||
|
objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]] = [];
|
||||||
|
}
|
||||||
|
objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]].push(objs[j]);
|
||||||
|
}
|
||||||
|
keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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') {
|
||||||
|
inValues.push(keyVals[relation.keyFrom][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return string representation of class
|
* Return string representation of class
|
||||||
*
|
*
|
||||||
|
@ -669,6 +818,14 @@ AbstractClass.prototype.reset = function () {
|
||||||
AbstractClass.hasMany = function hasMany(anotherClass, params) {
|
AbstractClass.hasMany = function hasMany(anotherClass, params) {
|
||||||
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
||||||
var fk = params.foreignKey;
|
var fk = params.foreignKey;
|
||||||
|
|
||||||
|
this.relations[params['as']] = {
|
||||||
|
type: 'hasMany',
|
||||||
|
keyFrom: 'id',
|
||||||
|
keyTo: params['foreignKey'],
|
||||||
|
modelTo: anotherClass,
|
||||||
|
multiple: true
|
||||||
|
};
|
||||||
// each instance of this class should have method named
|
// each instance of this class should have method named
|
||||||
// pluralize(anotherClass.modelName)
|
// pluralize(anotherClass.modelName)
|
||||||
// which is actually just anotherClass.all({where: {thisModelNameId: this.id}}, cb);
|
// which is actually just anotherClass.all({where: {thisModelNameId: this.id}}, cb);
|
||||||
|
@ -736,10 +893,22 @@ AbstractClass.belongsTo = function (anotherClass, params) {
|
||||||
var methodName = params.as;
|
var methodName = params.as;
|
||||||
var fk = params.foreignKey;
|
var fk = params.foreignKey;
|
||||||
|
|
||||||
|
this.relations[params['as']] = {
|
||||||
|
type: 'belongsTo',
|
||||||
|
keyFrom: params['foreignKey'],
|
||||||
|
keyTo: 'id',
|
||||||
|
modelTo: anotherClass,
|
||||||
|
multiple: false
|
||||||
|
};
|
||||||
|
|
||||||
this.schema.defineForeignKey(this.modelName, fk);
|
this.schema.defineForeignKey(this.modelName, fk);
|
||||||
this.prototype['__finders__'] = this.prototype['__finders__'] || {};
|
this.prototype['__finders__'] = this.prototype['__finders__'] || {};
|
||||||
|
|
||||||
this.prototype['__finders__'][methodName] = function (id, cb) {
|
this.prototype['__finders__'][methodName] = function (id, cb) {
|
||||||
|
if (id === null) {
|
||||||
|
cb(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
anotherClass.find(id, function (err,inst) {
|
anotherClass.find(id, function (err,inst) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) return cb(null, null);
|
if (!inst) return cb(null, null);
|
||||||
|
|
|
@ -145,6 +145,8 @@ function filtering(res, model, filter, instance) {
|
||||||
exports.initialize = function(schema, callback) {
|
exports.initialize = function(schema, callback) {
|
||||||
if (!cradle) return;
|
if (!cradle) return;
|
||||||
|
|
||||||
|
// when using cradle if we dont wait for the schema to be connected, the models fails to load correctly.
|
||||||
|
schema.waitForConnect = true;
|
||||||
if (!schema.settings.url) {
|
if (!schema.settings.url) {
|
||||||
var host = schema.settings.host || 'localhost';
|
var host = schema.settings.host || 'localhost';
|
||||||
var port = schema.settings.port || '5984';
|
var port = schema.settings.port || '5984';
|
||||||
|
|
|
@ -27,7 +27,14 @@ module.exports = function init(root) {
|
||||||
var confFile = (root || app.root) + '/config/database';
|
var confFile = (root || app.root) + '/config/database';
|
||||||
var config = {};
|
var config = {};
|
||||||
|
|
||||||
if (existsSync(confFile + '.json')) {
|
if (existsSync(confFile + '.js')) {
|
||||||
|
try {
|
||||||
|
config = require(confFile + '.js');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Could not load config/database.js');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else if (existsSync(confFile + '.json')) {
|
||||||
try {
|
try {
|
||||||
config = JSON.parse(fs.readFileSync(confFile + '.json', 'utf-8'))[app.set('env')];
|
config = JSON.parse(fs.readFileSync(confFile + '.json', 'utf-8'))[app.set('env')];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -49,8 +56,31 @@ module.exports = function init(root) {
|
||||||
|
|
||||||
var schema = new Schema(config && config.driver || 'memory', config);
|
var schema = new Schema(config && config.driver || 'memory', config);
|
||||||
schema.log = log;
|
schema.log = log;
|
||||||
// when using cradle if we dont wait for the schema to be connected, the models fails to load correctly.
|
|
||||||
|
if (schema.waitForConnect) {
|
||||||
schema.on('connected', function() {
|
schema.on('connected', function() {
|
||||||
|
loadSchema(schema, railway, app, models);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
loadSchema(schema, railway, app, models);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check validations and display warning
|
||||||
|
|
||||||
|
var displayWarning = false;
|
||||||
|
Object.keys(models).forEach(function (model) {
|
||||||
|
var Model = models[model];
|
||||||
|
if (Model._validations) {
|
||||||
|
displayWarning = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (displayWarning) {
|
||||||
|
var $ = railway.utils.stylize.$;
|
||||||
|
// require('util').puts($('WARNING:').bold.red + ' ' + $('I can see that you\'ve added validation to db/schema.js. However schema.js file is only used to describe database schema. Therefore validations configured in db/schema.js will be ignored.\nFor business logic (incl. validations) please create models as separate .js files here: app/models/*.js').yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSchema(schema, railway, app, models) {
|
||||||
railway.orm._schemas.push(schema);
|
railway.orm._schemas.push(schema);
|
||||||
|
|
||||||
var context = prepareContext(models, railway, app, schema);
|
var context = prepareContext(models, railway, app, schema);
|
||||||
|
@ -78,21 +108,6 @@ module.exports = function init(root) {
|
||||||
railway.orm._schemas.forEach(function (schema) {
|
railway.orm._schemas.forEach(function (schema) {
|
||||||
schema.freeze();
|
schema.freeze();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// check validations and display warning
|
|
||||||
|
|
||||||
var displayWarning = false;
|
|
||||||
Object.keys(models).forEach(function (model) {
|
|
||||||
var Model = models[model];
|
|
||||||
if (Model._validations) {
|
|
||||||
displayWarning = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (displayWarning) {
|
|
||||||
var $ = railway.utils.stylize.$;
|
|
||||||
// require('util').puts($('WARNING:').bold.red + ' ' + $('I can see that you\'ve added validation to db/schema.js. However schema.js file is only used to describe database schema. Therefore validations configured in db/schema.js will be ignored.\nFor business logic (incl. validations) please create models as separate .js files here: app/models/*.js').yellow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(str, startTime) {
|
function log(str, startTime) {
|
||||||
|
@ -201,6 +216,15 @@ function prepareContext(models, railway, app, defSchema, done) {
|
||||||
if (cname) settings[cname].table = name;
|
if (cname) settings[cname].table = name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the Schema has additional types, add them to the context
|
||||||
|
* e.g. MySQL has an additional Point type
|
||||||
|
*/
|
||||||
|
if (Schema.types && Object.keys(Schema.types).length) {
|
||||||
|
for (var typeName in Schema.types) {
|
||||||
|
ctx[typeName] = Schema.types[typeName];
|
||||||
|
}
|
||||||
|
}
|
||||||
ctx.Text = Schema.Text;
|
ctx.Text = Schema.Text;
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
|
@ -162,6 +162,7 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
||||||
hiddenProperty(NewClass, 'modelName', className);
|
hiddenProperty(NewClass, 'modelName', className);
|
||||||
hiddenProperty(NewClass, 'cache', {});
|
hiddenProperty(NewClass, 'cache', {});
|
||||||
hiddenProperty(NewClass, 'mru', []);
|
hiddenProperty(NewClass, 'mru', []);
|
||||||
|
hiddenProperty(NewClass, 'relations', {});
|
||||||
|
|
||||||
// inherit AbstractClass methods
|
// inherit AbstractClass methods
|
||||||
for (var i in AbstractClass) {
|
for (var i in AbstractClass) {
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
{_} = require 'lodash'
|
|
||||||
|
|
||||||
# api
|
|
||||||
exports.initialize = (schema, callback) ->
|
|
||||||
throw new Error 'url is missing' unless opts = schema.settings
|
|
||||||
db = require('nano')(opts)
|
|
||||||
|
|
||||||
schema.adapter = new NanoAdapter db
|
|
||||||
design = views: by_model: map:
|
|
||||||
'function (doc) { if (doc.model) return emit(doc.model, null); }'
|
|
||||||
db.insert design, '_design/nano', (err, doc) -> callback()
|
|
||||||
|
|
||||||
class NanoAdapter
|
|
||||||
constructor: (@db) ->
|
|
||||||
@_models = {}
|
|
||||||
|
|
||||||
define: (descr) =>
|
|
||||||
m = descr.model.modelName
|
|
||||||
descr.properties._rev = type: String
|
|
||||||
@_models[m] = descr
|
|
||||||
|
|
||||||
create: (args...) => @save args...
|
|
||||||
|
|
||||||
save: (model, data, callback) =>
|
|
||||||
data.model = model
|
|
||||||
helpers.savePrep data
|
|
||||||
|
|
||||||
@db.insert @forDB(model, data), (err, doc) =>
|
|
||||||
callback err, doc.id, doc.rev
|
|
||||||
|
|
||||||
updateOrCreate: (model, data, callback) =>
|
|
||||||
@exists model, data.id, (err, exists) =>
|
|
||||||
if exists
|
|
||||||
@save model, data, callback
|
|
||||||
else
|
|
||||||
@create model, data, (err, id) ->
|
|
||||||
data.id = id
|
|
||||||
callback err, data
|
|
||||||
|
|
||||||
exists: (model, id, callback) =>
|
|
||||||
@db.head id, (err, _, headers) ->
|
|
||||||
return callback null, no if err
|
|
||||||
callback null, headers?
|
|
||||||
|
|
||||||
find: (model, id, callback) =>
|
|
||||||
@db.get id, (err, doc) =>
|
|
||||||
callback err, @fromDB(model, doc)
|
|
||||||
|
|
||||||
destroy: (model, id, callback) =>
|
|
||||||
@db.get id, (err, doc) =>
|
|
||||||
return callback err if err
|
|
||||||
@db.destroy id, doc._rev, (err, doc) =>
|
|
||||||
return callback err if err
|
|
||||||
callback.removed = yes
|
|
||||||
callback()
|
|
||||||
|
|
||||||
updateAttributes: (model, id, data, callback) =>
|
|
||||||
@db.get id, (err, base) =>
|
|
||||||
return callback err if err
|
|
||||||
@save model, helpers.merge(base, data), callback
|
|
||||||
|
|
||||||
count: (model, callback, where) =>
|
|
||||||
@all model, {where}, (err, docs) =>
|
|
||||||
callback err, docs.length
|
|
||||||
|
|
||||||
destroyAll: (model, callback) =>
|
|
||||||
@all model, {}, (err, docs) =>
|
|
||||||
docs = for doc in docs
|
|
||||||
{_id: doc.id, _rev: doc._rev, _deleted: yes}
|
|
||||||
@db.bulk {docs}, (err, body) =>
|
|
||||||
callback err, body
|
|
||||||
|
|
||||||
forDB: (model, data = {}) =>
|
|
||||||
props = @_models[model].properties
|
|
||||||
for k, v of props
|
|
||||||
if data[k] and props[k].type.name is 'Date' and data[k].getTime?
|
|
||||||
data[k] = data[k].getTime()
|
|
||||||
data
|
|
||||||
|
|
||||||
fromDB: (model, data) =>
|
|
||||||
return data unless data
|
|
||||||
props = @_models[model].properties
|
|
||||||
for k, v of props
|
|
||||||
if data[k]? and props[k].type.name is 'Date'
|
|
||||||
date = new Date data[k]
|
|
||||||
date.setTime data[k]
|
|
||||||
data[k] = date
|
|
||||||
data
|
|
||||||
|
|
||||||
all: (model, filter, callback) =>
|
|
||||||
params =
|
|
||||||
keys: [model]
|
|
||||||
include_docs: yes
|
|
||||||
|
|
||||||
@db.view 'nano', 'by_model', params, (err, body) =>
|
|
||||||
docs = for row in body.rows
|
|
||||||
row.doc.id = row.doc._id
|
|
||||||
delete row.doc._id
|
|
||||||
row.doc
|
|
||||||
|
|
||||||
if where = filter?.where
|
|
||||||
for k, v of where
|
|
||||||
where[k] = v.getTime() if _.isDate v
|
|
||||||
docs = _.where docs, where
|
|
||||||
|
|
||||||
if orders = filter?.order
|
|
||||||
orders = [orders] if _.isString orders
|
|
||||||
|
|
||||||
sorting = (a, b) ->
|
|
||||||
for item, i in @
|
|
||||||
ak = a[@[i].key]; bk = b[@[i].key]; rev = @[i].reverse
|
|
||||||
if ak > bk then return 1 * rev
|
|
||||||
if ak < bk then return -1 * rev
|
|
||||||
0
|
|
||||||
|
|
||||||
for key, i in orders
|
|
||||||
orders[i] =
|
|
||||||
reverse: helpers.reverse key
|
|
||||||
key: helpers.stripOrder key
|
|
||||||
|
|
||||||
docs.sort sorting.bind orders
|
|
||||||
callback err, (@fromDB model, doc for doc in docs)
|
|
||||||
|
|
||||||
# helpers
|
|
||||||
helpers =
|
|
||||||
merge: (base, update) ->
|
|
||||||
return update unless base
|
|
||||||
base[k] = update[k] for k, v of update
|
|
||||||
base
|
|
||||||
reverse: (key) ->
|
|
||||||
if hasOrder = key.match(/\s+(A|DE)SC$/i)
|
|
||||||
return -1 if hasOrder[1] is "DE"
|
|
||||||
1
|
|
||||||
stripOrder: (key) ->
|
|
||||||
key.replace(/\s+(A|DE)SC/i, "")
|
|
||||||
savePrep: (data) ->
|
|
||||||
if id = data.id
|
|
||||||
delete data.id
|
|
||||||
data._id = id.toString()
|
|
||||||
if data._rev is null
|
|
||||||
delete data._rev
|
|
|
@ -87,6 +87,26 @@ Object.defineProperty(module.exports, 'skip', {
|
||||||
value: skip
|
value: skip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function clearAndCreate(model, data, callback) {
|
||||||
|
var createdItems = [];
|
||||||
|
model.destroyAll(function () {
|
||||||
|
nextItem(null, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
var itemIndex = 0;
|
||||||
|
function nextItem(err, lastItem) {
|
||||||
|
if (lastItem !== null) {
|
||||||
|
createdItems.push(lastItem);
|
||||||
|
}
|
||||||
|
if (itemIndex >= data.length) {
|
||||||
|
callback(createdItems);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model.create(data[itemIndex], nextItem);
|
||||||
|
itemIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function testOrm(schema) {
|
function testOrm(schema) {
|
||||||
var requestsAreCounted = schema.name !== 'mongodb';
|
var requestsAreCounted = schema.name !== 'mongodb';
|
||||||
|
|
||||||
|
@ -151,6 +171,7 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'});
|
Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'});
|
||||||
|
User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'});
|
||||||
|
|
||||||
var user = new User;
|
var user = new User;
|
||||||
|
|
||||||
|
@ -464,6 +485,30 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!schema.name.match(/redis/) &&
|
||||||
|
schema.name !== 'memory' &&
|
||||||
|
schema.name !== 'neo4j' &&
|
||||||
|
schema.name !== 'cradle'
|
||||||
|
)
|
||||||
|
it('relations key is working', function (test) {
|
||||||
|
test.ok(User.relations, 'Relations key should be defined');
|
||||||
|
test.ok(User.relations.posts, 'posts relation should exist on User');
|
||||||
|
test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany');
|
||||||
|
test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple');
|
||||||
|
test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table');
|
||||||
|
test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table');
|
||||||
|
|
||||||
|
test.ok(Post.relations, 'Relations key should be defined');
|
||||||
|
test.ok(Post.relations.author, 'author relation should exist on Post');
|
||||||
|
test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo');
|
||||||
|
test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple');
|
||||||
|
test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table');
|
||||||
|
test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table');
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should handle hasMany relationship', function (test) {
|
it('should handle hasMany relationship', function (test) {
|
||||||
User.create(function (err, u) {
|
User.create(function (err, u) {
|
||||||
if (err) return console.log(err);
|
if (err) return console.log(err);
|
||||||
|
@ -481,7 +526,6 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('hasMany should support additional conditions', function (test) {
|
it('hasMany should support additional conditions', function (test) {
|
||||||
|
|
||||||
User.create(function (e, u) {
|
User.create(function (e, u) {
|
||||||
|
@ -600,6 +644,235 @@ function testOrm(schema) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
schema.name === 'mysql' ||
|
||||||
|
schema.name === 'sqlite3' ||
|
||||||
|
schema.name === 'postgres'
|
||||||
|
)
|
||||||
|
it('should handle include function', function (test) {
|
||||||
|
var createdUsers = [];
|
||||||
|
var createdPassports = [];
|
||||||
|
var createdPosts = [];
|
||||||
|
var context = null;
|
||||||
|
|
||||||
|
createUsers();
|
||||||
|
function createUsers() {
|
||||||
|
clearAndCreate(
|
||||||
|
User,
|
||||||
|
[
|
||||||
|
{name: 'User A', age: 21},
|
||||||
|
{name: 'User B', age: 22},
|
||||||
|
{name: 'User C', age: 23},
|
||||||
|
{name: 'User D', age: 24},
|
||||||
|
{name: 'User E', age: 25}
|
||||||
|
],
|
||||||
|
function(items) {
|
||||||
|
createdUsers = items;
|
||||||
|
createPassports();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPassports() {
|
||||||
|
clearAndCreate(
|
||||||
|
Passport,
|
||||||
|
[
|
||||||
|
{number: '1', ownerId: createdUsers[0].id},
|
||||||
|
{number: '2', ownerId: createdUsers[1].id},
|
||||||
|
{number: '3'}
|
||||||
|
],
|
||||||
|
function(items) {
|
||||||
|
createdPassports = items;
|
||||||
|
createPosts();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPosts() {
|
||||||
|
clearAndCreate(
|
||||||
|
Post,
|
||||||
|
[
|
||||||
|
{title: 'Post A', userId: createdUsers[0].id},
|
||||||
|
{title: 'Post B', userId: createdUsers[0].id},
|
||||||
|
{title: 'Post C', userId: createdUsers[0].id},
|
||||||
|
{title: 'Post D', userId: createdUsers[1].id},
|
||||||
|
{title: 'Post E'}
|
||||||
|
],
|
||||||
|
function(items) {
|
||||||
|
createdPosts = items;
|
||||||
|
makeTests();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTests() {
|
||||||
|
var unitTests = [
|
||||||
|
function() {
|
||||||
|
context = ' (belongsTo simple string from passports to users)';
|
||||||
|
Passport.all({include: 'owner'}, testPassportsUser);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (belongsTo simple string from posts to users)';
|
||||||
|
Post.all({include: 'author'}, testPostsUser);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (belongsTo simple array)';
|
||||||
|
Passport.all({include: ['owner']}, testPassportsUser);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (hasMany simple string from users to posts)';
|
||||||
|
User.all({include: 'posts'}, testUsersPosts);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (hasMany simple string from users to passports)';
|
||||||
|
User.all({include: 'passports'}, testUsersPassports);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (hasMany simple array)';
|
||||||
|
User.all({include: ['posts']}, testUsersPosts);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (Passports - User - Posts in object)';
|
||||||
|
Passport.all({include: {'owner': 'posts'}}, testPassportsUserPosts);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (Passports - User - Posts in array)';
|
||||||
|
Passport.all({include: [{'owner': 'posts'}]}, testPassportsUserPosts);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (Passports - User - Posts - User)';
|
||||||
|
Passport.all({include: {'owner': {'posts': 'author'}}}, testPassportsUserPosts);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
context = ' (User - Posts AND Passports)';
|
||||||
|
User.all({include: ['posts', 'passports']}, testUsersPostsAndPassports);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function testPassportsUser(err, passports, callback) {
|
||||||
|
testBelongsTo(passports, 'owner', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPostsUser(err, posts, callback) {
|
||||||
|
testBelongsTo(posts, 'author', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testBelongsTo(items, relationName, callback) {
|
||||||
|
if (typeof callback === 'undefined') {
|
||||||
|
callback = nextUnitTest;
|
||||||
|
}
|
||||||
|
var nbInitialRequests = nbSchemaRequests;
|
||||||
|
var nbItemsRemaining = items.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
testItem(items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItem(item) {
|
||||||
|
var relation = item.constructor.relations[relationName];
|
||||||
|
var modelNameFrom = item.constructor.modelName;
|
||||||
|
var modelNameTo = relation.modelTo.modelName;
|
||||||
|
item[relationName](function(err, relatedItem) {
|
||||||
|
if (relatedItem !== null) {
|
||||||
|
test.equal(relatedItem[relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instance match ' + modelNameFrom + '\'s instance' + context);
|
||||||
|
} else {
|
||||||
|
test.ok(item[relation.keyFrom] == null, 'User match passport even when user is null.' + context);
|
||||||
|
}
|
||||||
|
nbItemsRemaining--;
|
||||||
|
if (nbItemsRemaining == 0) {
|
||||||
|
requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context)
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testUsersPosts(err, users, expectedUserNumber, callback) {
|
||||||
|
if (typeof expectedUserNumber === 'undefined') {
|
||||||
|
expectedUserNumber = 5;
|
||||||
|
}
|
||||||
|
test.equal(users.length, expectedUserNumber, 'Exactly ' + expectedUserNumber + ' users returned by query' + context);
|
||||||
|
testHasMany(users, 'posts', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testUsersPassports(err, users, callback) {
|
||||||
|
testHasMany(users, 'passports', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testHasMany(items, relationName, callback) {
|
||||||
|
if (typeof callback === 'undefined') {
|
||||||
|
callback = nextUnitTest;
|
||||||
|
}
|
||||||
|
var nbInitialRequests = nbSchemaRequests;
|
||||||
|
var nbItemRemaining = items.length;
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
testItem(items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItem(item) {
|
||||||
|
var relation = item.constructor.relations[relationName];
|
||||||
|
var modelNameFrom = item.constructor.modelName;
|
||||||
|
var modelNameTo = relation.modelTo.modelName;
|
||||||
|
item[relationName](function(err, relatedItems) {
|
||||||
|
for (var j = 0; j < relatedItems.length; j++) {
|
||||||
|
test.equal(relatedItems[j][relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instances match ' + modelNameFrom + '\'s instance' + context);
|
||||||
|
}
|
||||||
|
nbItemRemaining--;
|
||||||
|
if (nbItemRemaining == 0) {
|
||||||
|
requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context)
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPassportsUserPosts(err, passports) {
|
||||||
|
testPassportsUser(err, passports, function() {
|
||||||
|
var nbPassportsRemaining = passports.length;
|
||||||
|
for (var i = 0; i < passports.length; i++) {
|
||||||
|
if (passports[i].ownerId !== null) {
|
||||||
|
passports[i].owner(function(err, user) {
|
||||||
|
testUsersPosts(null, [user], 1, function() {
|
||||||
|
nextPassport();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
nextPassport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function nextPassport() {
|
||||||
|
nbPassportsRemaining--
|
||||||
|
if (nbPassportsRemaining == 0) {
|
||||||
|
nextUnitTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testUsersPostsAndPassports(err, users) {
|
||||||
|
testUsersPosts(err, users, 5, function() {
|
||||||
|
testUsersPassports(err, users, function() {
|
||||||
|
nextUnitTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var testNum = 0;
|
||||||
|
function nextUnitTest() {
|
||||||
|
if (testNum >= unitTests.length) {
|
||||||
|
test.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unitTests[testNum]();
|
||||||
|
testNum++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nextUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it('should destroy all records', function (test) {
|
it('should destroy all records', function (test) {
|
||||||
Post.destroyAll(function (err) {
|
Post.destroyAll(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
Loading…
Reference in New Issue