Merge branch 'release/2.8.0' into production
This commit is contained in:
commit
3753e36da7
|
@ -6,7 +6,7 @@ var fs = require('fs');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the Oracle connector against the given data source
|
* Initialize the Memory connector against the given data source
|
||||||
*
|
*
|
||||||
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
||||||
* @param {Function} [callback] The callback function
|
* @param {Function} [callback] The callback function
|
||||||
|
@ -406,9 +406,10 @@ function applyFilter(filter) {
|
||||||
if (typeof value === 'string' && (example instanceof RegExp)) {
|
if (typeof value === 'string' && (example instanceof RegExp)) {
|
||||||
return value.match(example);
|
return value.match(example);
|
||||||
}
|
}
|
||||||
if (example === undefined || value === undefined) {
|
if (example === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof example === 'object') {
|
if (typeof example === 'object') {
|
||||||
// ignore geo near filter
|
// ignore geo near filter
|
||||||
if (example.near) {
|
if (example.near) {
|
||||||
|
@ -425,6 +426,10 @@ function applyFilter(filter) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('neq' in example) {
|
||||||
|
return compare(example.neq, value) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (example.like || example.nlike) {
|
if (example.like || example.nlike) {
|
||||||
|
|
||||||
var like = example.like || example.nlike;
|
var like = example.like || example.nlike;
|
||||||
|
@ -445,7 +450,8 @@ function applyFilter(filter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not strict equality
|
// not strict equality
|
||||||
return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value);
|
return (example !== null ? example.toString() : example)
|
||||||
|
== (value != null ? value.toString() : value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
var util = require('util');
|
||||||
|
var Connector = require('loopback-connector').Connector;
|
||||||
|
var utils = require('../utils');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Transient connector against the given data source
|
||||||
|
*
|
||||||
|
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
||||||
|
* @param {Function} [callback] The callback function
|
||||||
|
*/
|
||||||
|
exports.initialize = function initializeDataSource(dataSource, callback) {
|
||||||
|
dataSource.connector = new Transient(null, dataSource.settings);
|
||||||
|
dataSource.connector.connect(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Transient = Transient;
|
||||||
|
|
||||||
|
function Transient(m, settings) {
|
||||||
|
settings = settings || {};
|
||||||
|
if (typeof settings.generateId === 'function') {
|
||||||
|
this.generateId = settings.generateId.bind(this);
|
||||||
|
}
|
||||||
|
this.defaultIdType = settings.defaultIdType || String;
|
||||||
|
if (m instanceof Transient) {
|
||||||
|
this.isTransaction = true;
|
||||||
|
this.constructor.super_.call(this, 'transient', settings);
|
||||||
|
this._models = m._models;
|
||||||
|
} else {
|
||||||
|
this.isTransaction = false;
|
||||||
|
this.constructor.super_.call(this, 'transient', settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Transient, Connector);
|
||||||
|
|
||||||
|
Transient.prototype.getDefaultIdType = function() {
|
||||||
|
return this.defaultIdType;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.getTypes = function() {
|
||||||
|
return ['db', 'nosql', 'transient'];
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.connect = function (callback) {
|
||||||
|
if (this.isTransaction) {
|
||||||
|
this.onTransactionExec = callback;
|
||||||
|
} else {
|
||||||
|
process.nextTick(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.generateId = function(model, data, idName) {
|
||||||
|
var idType;
|
||||||
|
var props = this._models[model].properties;
|
||||||
|
if (idName) idType = props[idName] && props[idName].type;
|
||||||
|
idType = idType || this.getDefaultIdType();
|
||||||
|
if (idType === Number) {
|
||||||
|
return Math.floor(Math.random() * 10000); // max. 4 digits
|
||||||
|
} else {
|
||||||
|
return crypto.randomBytes(Math.ceil(24/2))
|
||||||
|
.toString('hex') // convert to hexadecimal format
|
||||||
|
.slice(0, 24); // return required number of characters
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.exists = function exists(model, id, callback) {
|
||||||
|
process.nextTick(function () { callback(null, false); }.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.find = function find(model, id, callback) {
|
||||||
|
process.nextTick(function () { callback(null, null); }.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.all = function all(model, filter, callback) {
|
||||||
|
process.nextTick(function () { callback(null, []); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.count = function count(model, callback, where) {
|
||||||
|
process.nextTick(function () { callback(null, 0); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.create = function create(model, data, callback) {
|
||||||
|
var props = this._models[model].properties;
|
||||||
|
var idName = this.idName(model);
|
||||||
|
if (idName && props[idName]) {
|
||||||
|
var id = this.getIdValue(model, data) || this.generateId(model, data, idName);
|
||||||
|
id = (props[idName] && props[idName].type && props[idName].type(id)) || id;
|
||||||
|
this.setIdValue(model, data, id);
|
||||||
|
}
|
||||||
|
this.flush('create', id, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.save = function save(model, data, callback) {
|
||||||
|
this.flush('save', data, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.update =
|
||||||
|
Transient.prototype.updateAll = function updateAll(model, where, data, cb) {
|
||||||
|
this.flush('update', null, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
|
||||||
|
if (!id) {
|
||||||
|
var err = new Error('You must provide an id when updating attributes!');
|
||||||
|
if (cb) {
|
||||||
|
return cb(err);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIdValue(model, data, id);
|
||||||
|
this.save(model, data, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.destroy = function destroy(model, id, callback) {
|
||||||
|
this.flush('destroy', null, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.destroyAll = function destroyAll(model, where, callback) {
|
||||||
|
if (!callback && 'function' === typeof where) {
|
||||||
|
callback = where;
|
||||||
|
where = undefined;
|
||||||
|
}
|
||||||
|
this.flush('destroyAll', null, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Flush the cache - noop.
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
Transient.prototype.flush = function (action, result, callback) {
|
||||||
|
process.nextTick(function () { callback && callback(null, result); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.transaction = function () {
|
||||||
|
return new Transient(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.exec = function (callback) {
|
||||||
|
this.onTransactionExec();
|
||||||
|
setTimeout(callback, 50);
|
||||||
|
};
|
12
lib/dao.js
12
lib/dao.js
|
@ -8,8 +8,7 @@ module.exports = DataAccessObject;
|
||||||
* Module dependencies
|
* Module dependencies
|
||||||
*/
|
*/
|
||||||
var jutil = require('./jutil');
|
var jutil = require('./jutil');
|
||||||
var validations = require('./validations.js');
|
var ValidationError = require('./validations').ValidationError;
|
||||||
var ValidationError = validations.ValidationError;
|
|
||||||
var Relation = require('./relations.js');
|
var Relation = require('./relations.js');
|
||||||
var Inclusion = require('./include.js');
|
var Inclusion = require('./include.js');
|
||||||
var List = require('./list.js');
|
var List = require('./list.js');
|
||||||
|
@ -40,15 +39,12 @@ function DataAccessObject() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function idName(m) {
|
function idName(m) {
|
||||||
return m.getDataSource().idName
|
return m.definition.idName() || 'id';
|
||||||
? m.getDataSource().idName(m.modelName) : 'id';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIdValue(m, data) {
|
function getIdValue(m, data) {
|
||||||
return data && data[m.getDataSource().idName(m.modelName)];
|
return data && data[idName(m)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setIdValue(m, data, value) {
|
function setIdValue(m, data, value) {
|
||||||
|
@ -331,7 +327,7 @@ DataAccessObject.findByIds = function(ids, cond, cb) {
|
||||||
cond = {};
|
cond = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
var pk = this.dataSource.idName(this.modelName) || 'id';
|
var pk = idName(this);
|
||||||
if (ids.length === 0) {
|
if (ids.length === 0) {
|
||||||
process.nextTick(function() { cb(null, []); });
|
process.nextTick(function() { cb(null, []); });
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -392,6 +392,7 @@ DataSource.prototype.defineScopes = function (modelClass, scopes) {
|
||||||
* @param relations
|
* @param relations
|
||||||
*/
|
*/
|
||||||
DataSource.prototype.defineRelations = function (modelClass, relations) {
|
DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
// Create a function for the closure in the loop
|
// Create a function for the closure in the loop
|
||||||
var createListener = function (name, relation, targetModel, throughModel) {
|
var createListener = function (name, relation, targetModel, throughModel) {
|
||||||
|
@ -416,7 +417,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
throughModel.once('dataAccessConfigured', function (model) {
|
throughModel.once('dataAccessConfigured', function (model) {
|
||||||
if (isModelDataSourceAttached(targetModel)) {
|
if (isModelDataSourceAttached(targetModel)) {
|
||||||
// The target model is resolved
|
// The target model is resolved
|
||||||
var params = traverse(relations).clone();
|
var params = traverse(relation).clone();
|
||||||
params.as = name;
|
params.as = name;
|
||||||
params.model = targetModel;
|
params.model = targetModel;
|
||||||
params.through = model;
|
params.through = model;
|
||||||
|
@ -428,7 +429,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
|
|
||||||
// Set up the relations
|
// Set up the relations
|
||||||
if (relations) {
|
if (relations) {
|
||||||
for (var rn in relations) {
|
Object.keys(relations).forEach(function(rn) {
|
||||||
var r = relations[rn];
|
var r = relations[rn];
|
||||||
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
||||||
var targetModel, polymorphicName;
|
var targetModel, polymorphicName;
|
||||||
|
@ -447,12 +448,12 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.model) {
|
if (r.model) {
|
||||||
targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model, true);
|
targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var throughModel = null;
|
var throughModel = null;
|
||||||
if (r.through) {
|
if (r.through) {
|
||||||
throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through, true);
|
throughModel = isModelClass(r.through) ? r.through : self.getModel(r.through, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((targetModel && !isModelDataSourceAttached(targetModel))
|
if ((targetModel && !isModelDataSourceAttached(targetModel))
|
||||||
|
@ -469,7 +470,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
}
|
}
|
||||||
modelClass[r.type].call(modelClass, rn, params);
|
modelClass[r.type].call(modelClass, rn, params);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,7 @@ ModelDefinition.prototype.build = function (forceRebuild) {
|
||||||
this.properties = null;
|
this.properties = null;
|
||||||
this.relations = [];
|
this.relations = [];
|
||||||
this._ids = null;
|
this._ids = null;
|
||||||
|
this.json = null;
|
||||||
}
|
}
|
||||||
if (this.properties) {
|
if (this.properties) {
|
||||||
return this.properties;
|
return this.properties;
|
||||||
|
|
|
@ -11,7 +11,7 @@ var util = require('util');
|
||||||
var jutil = require('./jutil');
|
var jutil = require('./jutil');
|
||||||
var List = require('./list');
|
var List = require('./list');
|
||||||
var Hookable = require('./hooks');
|
var Hookable = require('./hooks');
|
||||||
var validations = require('./validations.js');
|
var validations = require('./validations');
|
||||||
var _extend = util._extend;
|
var _extend = util._extend;
|
||||||
|
|
||||||
// Set up an object for quick lookup
|
// Set up an object for quick lookup
|
||||||
|
|
|
@ -391,13 +391,13 @@ util.inherits(HasOne, Relation);
|
||||||
* EmbedsOne subclass
|
* EmbedsOne subclass
|
||||||
* @param {RelationDefinition|Object} definition
|
* @param {RelationDefinition|Object} definition
|
||||||
* @param {Object} modelInstance
|
* @param {Object} modelInstance
|
||||||
* @returns {EmbedsMany}
|
* @returns {EmbedsOne}
|
||||||
* @constructor
|
* @constructor
|
||||||
* @class EmbedsOne
|
* @class EmbedsOne
|
||||||
*/
|
*/
|
||||||
function EmbedsOne(definition, modelInstance) {
|
function EmbedsOne(definition, modelInstance) {
|
||||||
if (!(this instanceof EmbedsOne)) {
|
if (!(this instanceof EmbedsOne)) {
|
||||||
return new EmbedsMany(definition, modelInstance);
|
return new EmbedsOne(definition, modelInstance);
|
||||||
}
|
}
|
||||||
assert(definition.type === RelationTypes.embedsOne);
|
assert(definition.type === RelationTypes.embedsOne);
|
||||||
Relation.apply(this, arguments);
|
Relation.apply(this, arguments);
|
||||||
|
@ -489,7 +489,6 @@ function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
||||||
}
|
}
|
||||||
if (typeof modelTo === 'string') {
|
if (typeof modelTo === 'string') {
|
||||||
modelToName = (singularize ? i8n.singularize(params.as) : params.as).toLowerCase();
|
modelToName = (singularize ? i8n.singularize(params.as) : params.as).toLowerCase();
|
||||||
console.log(modelToName)
|
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
||||||
}
|
}
|
||||||
if (typeof modelTo !== 'function') {
|
if (typeof modelTo !== 'function') {
|
||||||
|
@ -504,9 +503,9 @@ function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
||||||
* @param {Object|String} params Name of the polymorphic relation or params
|
* @param {Object|String} params Name of the polymorphic relation or params
|
||||||
* @returns {Object} The normalized parameters
|
* @returns {Object} The normalized parameters
|
||||||
*/
|
*/
|
||||||
function polymorphicParams(params) {
|
function polymorphicParams(params, as) {
|
||||||
if (typeof params === 'string') params = { as: params };
|
if (typeof params === 'string') params = { as: params };
|
||||||
if (typeof params.as !== 'string') params.as = 'reference'; // default
|
if (typeof params.as !== 'string') params.as = as || 'reference'; // default
|
||||||
params.foreignKey = params.foreignKey || i8n.camelize(params.as + '_id', true);
|
params.foreignKey = params.foreignKey || i8n.camelize(params.as + '_id', true);
|
||||||
params.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true);
|
params.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true);
|
||||||
return params;
|
return params;
|
||||||
|
@ -541,13 +540,17 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
|
|
||||||
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
||||||
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
||||||
|
var keyThrough = params.keyThrough || i8n.camelize(modelTo.modelName + '_id', true);
|
||||||
|
|
||||||
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
||||||
var discriminator, polymorphic;
|
var discriminator, polymorphic;
|
||||||
|
|
||||||
if (params.polymorphic) {
|
if (params.polymorphic) {
|
||||||
polymorphic = polymorphicParams(params.polymorphic);
|
polymorphic = polymorphicParams(params.polymorphic);
|
||||||
if (params.invert) polymorphic.invert = true;
|
if (params.invert) {
|
||||||
|
polymorphic.invert = true;
|
||||||
|
keyThrough = polymorphic.foreignKey;
|
||||||
|
}
|
||||||
discriminator = polymorphic.discriminator;
|
discriminator = polymorphic.discriminator;
|
||||||
if (!params.invert) {
|
if (!params.invert) {
|
||||||
fk = polymorphic.foreignKey;
|
fk = polymorphic.foreignKey;
|
||||||
|
@ -568,14 +571,12 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
properties: params.properties,
|
properties: params.properties,
|
||||||
scope: params.scope,
|
scope: params.scope,
|
||||||
options: params.options,
|
options: params.options,
|
||||||
|
keyThrough: keyThrough,
|
||||||
polymorphic: polymorphic
|
polymorphic: polymorphic
|
||||||
});
|
});
|
||||||
|
|
||||||
definition.modelThrough = params.through;
|
definition.modelThrough = params.through;
|
||||||
|
|
||||||
var keyThrough = definition.throughKey || i8n.camelize(modelTo.modelName + '_id', true);
|
|
||||||
definition.keyThrough = keyThrough;
|
|
||||||
|
|
||||||
modelFrom.relations[relationName] = definition;
|
modelFrom.relations[relationName] = definition;
|
||||||
|
|
||||||
if (!params.through) {
|
if (!params.through) {
|
||||||
|
@ -636,16 +637,34 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
filter.where[fk] = this[idName];
|
filter.where[fk] = this[idName];
|
||||||
|
|
||||||
definition.applyScope(this, filter);
|
definition.applyScope(this, filter);
|
||||||
|
|
||||||
if (params.through && params.polymorphic && params.invert) {
|
if (definition.modelThrough) {
|
||||||
filter.where[discriminator] = modelTo.modelName; // overwrite
|
var throughRelationName;
|
||||||
filter.collect = params.polymorphic;
|
|
||||||
filter.include = filter.collect;
|
// find corresponding belongsTo relations from through model as collect
|
||||||
} else if (params.through) {
|
for(var r in definition.modelThrough.relations) {
|
||||||
filter.collect = i8n.camelize(modelTo.modelName, true);
|
var relation = definition.modelThrough.relations[r];
|
||||||
filter.include = filter.collect;
|
|
||||||
|
// should be a belongsTo and match modelTo and keyThrough
|
||||||
|
// if relation is polymorphic then check keyThrough only
|
||||||
|
if (relation.type === RelationTypes.belongsTo &&
|
||||||
|
(relation.polymorphic && !relation.modelTo || relation.modelTo === definition.modelTo) &&
|
||||||
|
(relation.keyFrom === definition.keyThrough)
|
||||||
|
) {
|
||||||
|
throughRelationName = relation.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definition.polymorphic && definition.polymorphic.invert) {
|
||||||
|
filter.collect = definition.polymorphic.as;
|
||||||
|
filter.include = filter.collect;
|
||||||
|
} else {
|
||||||
|
filter.collect = throughRelationName || i8n.camelize(modelTo.modelName, true);
|
||||||
|
filter.include = filter.collect;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}, scopeMethods, definition.options);
|
}, scopeMethods, definition.options);
|
||||||
|
|
||||||
|
@ -918,14 +937,18 @@ HasManyThrough.prototype.create = function create(data, done) {
|
||||||
/**
|
/**
|
||||||
* Add the target model instance to the 'hasMany' relation
|
* Add the target model instance to the 'hasMany' relation
|
||||||
* @param {Object|ID} acInst The actual instance or id value
|
* @param {Object|ID} acInst The actual instance or id value
|
||||||
|
* @param {Object} [data] Optional data object for the through model to be created
|
||||||
*/
|
*/
|
||||||
HasManyThrough.prototype.add = function (acInst, done) {
|
HasManyThrough.prototype.add = function (acInst, data, done) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var definition = this.definition;
|
var definition = this.definition;
|
||||||
var modelThrough = definition.modelThrough;
|
var modelThrough = definition.modelThrough;
|
||||||
var pk1 = definition.keyFrom;
|
var pk1 = definition.keyFrom;
|
||||||
|
|
||||||
var data = {};
|
if (typeof data === 'function') {
|
||||||
|
done = data;
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
var query = {};
|
var query = {};
|
||||||
|
|
||||||
// The primary key for the target model
|
// The primary key for the target model
|
||||||
|
@ -1056,21 +1079,23 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
|
|
||||||
var idName, relationName, fk;
|
var idName, relationName, fk;
|
||||||
if (params.polymorphic) {
|
if (params.polymorphic) {
|
||||||
|
relationName = params.as || (typeof modelTo === 'string' ? modelTo : null); // initially
|
||||||
|
|
||||||
if (params.polymorphic === true) {
|
if (params.polymorphic === true) {
|
||||||
// modelTo arg will be the name of the polymorphic relation (string)
|
// modelTo arg will be the name of the polymorphic relation (string)
|
||||||
polymorphic = polymorphicParams(modelTo);
|
polymorphic = polymorphicParams(modelTo, relationName);
|
||||||
} else {
|
} else {
|
||||||
polymorphic = polymorphicParams(params.polymorphic);
|
polymorphic = polymorphicParams(params.polymorphic, relationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
modelTo = null; // will lookup dynamically
|
modelTo = null; // will lookup dynamically
|
||||||
|
|
||||||
idName = params.idName || 'id';
|
idName = params.idName || 'id';
|
||||||
relationName = params.as || polymorphic.as;
|
relationName = params.as || polymorphic.as; // finally
|
||||||
fk = polymorphic.foreignKey;
|
fk = polymorphic.foreignKey;
|
||||||
discriminator = polymorphic.discriminator;
|
discriminator = polymorphic.discriminator;
|
||||||
|
|
||||||
if (typeof polymorphic.idType === 'string') { // explicit key type
|
if (polymorphic.idType) { // explicit key type
|
||||||
modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: polymorphic.idType, index: true });
|
modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: polymorphic.idType, index: true });
|
||||||
} else { // try to use the same foreign key type as modelFrom
|
} else { // try to use the same foreign key type as modelFrom
|
||||||
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName);
|
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName);
|
||||||
|
@ -1842,8 +1867,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
type: [modelTo], default: function() { return []; }
|
type: [modelTo], default: function() { return []; }
|
||||||
});
|
});
|
||||||
|
|
||||||
// unique id is required
|
if (typeof modelTo.dataSource.connector.generateId !== 'function') {
|
||||||
modelTo.validatesPresenceOf(idName);
|
modelTo.validatesPresenceOf(idName); // unique id is required
|
||||||
|
}
|
||||||
|
|
||||||
if (!params.polymorphic) {
|
if (!params.polymorphic) {
|
||||||
modelFrom.validate(propertyName, function(err) {
|
modelFrom.validate(propertyName, function(err) {
|
||||||
|
@ -1853,7 +1879,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
return ids.indexOf(id) === pos;
|
return ids.indexOf(id) === pos;
|
||||||
});
|
});
|
||||||
if (ids.length !== uniqueIds.length) {
|
if (ids.length !== uniqueIds.length) {
|
||||||
this.errors.add(propertyName, 'Contains duplicate `' + idName + '`', 'uniqueness');
|
this.errors.add(propertyName, 'contains duplicate `' + idName + '`', 'uniqueness');
|
||||||
err(false);
|
err(false);
|
||||||
}
|
}
|
||||||
}, { code: 'uniqueness' })
|
}, { code: 'uniqueness' })
|
||||||
|
@ -1877,7 +1903,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
self.errors.add(propertyName, 'Contains invalid item', 'invalid');
|
self.errors.add(propertyName, 'contains invalid item', 'invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasErrors) err(false);
|
if (hasErrors) err(false);
|
||||||
|
@ -2143,7 +2169,6 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var propertyName = this.definition.keyFrom;
|
var propertyName = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
|
||||||
|
|
||||||
if (typeof targetModelData === 'function' && !cb) {
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
cb = targetModelData;
|
cb = targetModelData;
|
||||||
|
@ -2170,16 +2195,22 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.build = function(targetModelData) {
|
EmbedsMany.prototype.build = function(targetModelData) {
|
||||||
var pk = this.definition.keyTo;
|
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
var forceId = this.definition.options.forceId;
|
||||||
|
var connector = modelTo.dataSource.connector;
|
||||||
|
|
||||||
|
var pk = this.definition.keyTo;
|
||||||
|
var pkProp = modelTo.definition.properties[pk]
|
||||||
|
var pkType = pkProp && pkProp.type;
|
||||||
|
|
||||||
var embeddedList = this.embeddedList();
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
targetModelData = targetModelData || {};
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
if (typeof targetModelData[pk] !== 'number' && autoId) {
|
var assignId = (forceId || targetModelData[pk] === undefined);
|
||||||
|
|
||||||
|
if (assignId && pkType === Number) {
|
||||||
var ids = embeddedList.map(function(m) {
|
var ids = embeddedList.map(function(m) {
|
||||||
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
||||||
});
|
});
|
||||||
|
@ -2188,6 +2219,9 @@ EmbedsMany.prototype.build = function(targetModelData) {
|
||||||
} else {
|
} else {
|
||||||
targetModelData[pk] = 1;
|
targetModelData[pk] = 1;
|
||||||
}
|
}
|
||||||
|
} else if (assignId && typeof connector.generateId === 'function') {
|
||||||
|
var id = connector.generateId(modelTo.modelName, targetModelData, pk);
|
||||||
|
targetModelData[pk] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.definition.applyProperties(modelInstance, targetModelData);
|
this.definition.applyProperties(modelInstance, targetModelData);
|
||||||
|
@ -2325,7 +2359,7 @@ RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo,
|
||||||
return ids.indexOf(id) === pos;
|
return ids.indexOf(id) === pos;
|
||||||
});
|
});
|
||||||
if (ids.length !== uniqueIds.length) {
|
if (ids.length !== uniqueIds.length) {
|
||||||
var msg = 'Contains duplicate `' + modelTo.modelName + '` instance';
|
var msg = 'contains duplicate `' + modelTo.modelName + '` instance';
|
||||||
this.errors.add(relationName, msg, 'uniqueness');
|
this.errors.add(relationName, msg, 'uniqueness');
|
||||||
err(false);
|
err(false);
|
||||||
}
|
}
|
||||||
|
|
62
lib/scope.js
62
lib/scope.js
|
@ -1,6 +1,8 @@
|
||||||
var i8n = require('inflection');
|
var i8n = require('inflection');
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
var defineCachedRelations = utils.defineCachedRelations;
|
var defineCachedRelations = utils.defineCachedRelations;
|
||||||
|
var DefaultModelBaseClass = require('./model.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module exports
|
* Module exports
|
||||||
*/
|
*/
|
||||||
|
@ -13,10 +15,26 @@ function ScopeDefinition(definition) {
|
||||||
this.modelTo = definition.modelTo || definition.modelFrom;
|
this.modelTo = definition.modelTo || definition.modelFrom;
|
||||||
this.name = definition.name;
|
this.name = definition.name;
|
||||||
this.params = definition.params;
|
this.params = definition.params;
|
||||||
this.methods = definition.methods;
|
this.methods = definition.methods || {};
|
||||||
this.options = definition.options;
|
this.options = definition.options || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScopeDefinition.prototype.targetModel = function(receiver) {
|
||||||
|
if (typeof this.options.modelTo === 'function') {
|
||||||
|
var modelTo = this.options.modelTo.call(this, receiver) || this.modelTo;
|
||||||
|
} else {
|
||||||
|
var modelTo = this.modelTo;
|
||||||
|
}
|
||||||
|
if (!(modelTo.prototype instanceof DefaultModelBaseClass)) {
|
||||||
|
var msg = 'Invalid target model for scope `';
|
||||||
|
msg += (this.isStatic ? this.modelFrom : this.modelFrom.constructor).modelName;
|
||||||
|
msg += this.isStatic ? '.' : '.prototype.';
|
||||||
|
msg += this.name + '`.';
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
return modelTo;
|
||||||
|
};
|
||||||
|
|
||||||
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
||||||
var name = this.name;
|
var name = this.name;
|
||||||
var self = receiver;
|
var self = receiver;
|
||||||
|
@ -42,7 +60,8 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
|
||||||
|| actualRefresh) {
|
|| actualRefresh) {
|
||||||
// It either doesn't hit the cache or refresh is required
|
// It either doesn't hit the cache or refresh is required
|
||||||
var params = mergeQuery(actualCond, scopeParams);
|
var params = mergeQuery(actualCond, scopeParams);
|
||||||
return this.modelTo.find(params, function (err, data) {
|
var targetModel = this.targetModel(receiver);
|
||||||
|
return targetModel.find(params, function (err, data) {
|
||||||
if (!err && saveOnCache) {
|
if (!err && saveOnCache) {
|
||||||
defineCachedRelations(self);
|
defineCachedRelations(self);
|
||||||
self.__cachedRelations[name] = data;
|
self.__cachedRelations[name] = data;
|
||||||
|
@ -74,7 +93,6 @@ ScopeDefinition.prototype.defineMethod = function(name, fn) {
|
||||||
* @param methods An object of methods keyed by the method name to be bound to the class
|
* @param methods An object of methods keyed by the method name to be bound to the class
|
||||||
*/
|
*/
|
||||||
function defineScope(cls, targetClass, name, params, methods, options) {
|
function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
|
|
||||||
// collect meta info about scope
|
// collect meta info about scope
|
||||||
if (!cls._scopeMeta) {
|
if (!cls._scopeMeta) {
|
||||||
cls._scopeMeta = {};
|
cls._scopeMeta = {};
|
||||||
|
@ -84,7 +102,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
// are same
|
// are same
|
||||||
if (cls === targetClass) {
|
if (cls === targetClass) {
|
||||||
cls._scopeMeta[name] = params;
|
cls._scopeMeta[name] = params;
|
||||||
} else {
|
} else if (targetClass) {
|
||||||
if (!targetClass._scopeMeta) {
|
if (!targetClass._scopeMeta) {
|
||||||
targetClass._scopeMeta = {};
|
targetClass._scopeMeta = {};
|
||||||
}
|
}
|
||||||
|
@ -100,7 +118,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
name: name,
|
name: name,
|
||||||
params: params,
|
params: params,
|
||||||
methods: methods,
|
methods: methods,
|
||||||
options: options || {}
|
options: options
|
||||||
});
|
});
|
||||||
|
|
||||||
if(isStatic) {
|
if(isStatic) {
|
||||||
|
@ -127,7 +145,9 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
get: function () {
|
get: function () {
|
||||||
|
var targetModel = definition.targetModel(this);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var f = function(condOrRefresh, cb) {
|
var f = function(condOrRefresh, cb) {
|
||||||
if(arguments.length === 1) {
|
if(arguments.length === 1) {
|
||||||
definition.related(self, f._scope, condOrRefresh);
|
definition.related(self, f._scope, condOrRefresh);
|
||||||
|
@ -135,15 +155,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
definition.related(self, f._scope, condOrRefresh, cb);
|
definition.related(self, f._scope, condOrRefresh, cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
f._receiver = this;
|
||||||
f._scope = typeof definition.params === 'function' ?
|
f._scope = typeof definition.params === 'function' ?
|
||||||
definition.params.call(self) : definition.params;
|
definition.params.call(self) : definition.params;
|
||||||
|
|
||||||
f._targetClass = definition.modelTo.modelName;
|
f._targetClass = targetModel.modelName;
|
||||||
if (f._scope.collect) {
|
if (f._scope.collect) {
|
||||||
f._targetClass = i8n.capitalize(f._scope.collect);
|
f._targetClass = i8n.capitalize(f._scope.collect);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.build = build;
|
f.build = build;
|
||||||
f.create = create;
|
f.create = create;
|
||||||
f.destroyAll = destroyAll;
|
f.destroyAll = destroyAll;
|
||||||
|
@ -151,6 +172,8 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
for (var i in definition.methods) {
|
for (var i in definition.methods) {
|
||||||
f[i] = definition.methods[i].bind(self);
|
f[i] = definition.methods[i].bind(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!targetClass) return f;
|
||||||
|
|
||||||
// Define scope-chaining, such as
|
// Define scope-chaining, such as
|
||||||
// Station.scope('active', {where: {isActive: true}});
|
// Station.scope('active', {where: {isActive: true}});
|
||||||
|
@ -160,7 +183,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
Object.defineProperty(f, name, {
|
Object.defineProperty(f, name, {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
get: function () {
|
get: function () {
|
||||||
mergeQuery(f._scope, targetClass._scopeMeta[name]);
|
mergeQuery(f._scope, targetModel._scopeMeta[name]);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -207,16 +230,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
* @param {Object} The data object
|
* @param {Object} The data object
|
||||||
* @param {Object} The where clause
|
* @param {Object} The where clause
|
||||||
*/
|
*/
|
||||||
function setScopeValuesFromWhere(data, where) {
|
function setScopeValuesFromWhere(data, where, targetModel) {
|
||||||
for (var i in where) {
|
for (var i in where) {
|
||||||
if (i === 'and') {
|
if (i === 'and') {
|
||||||
// Find fixed property values from each subclauses
|
// Find fixed property values from each subclauses
|
||||||
for (var w = 0, n = where[i].length; w < n; w++) {
|
for (var w = 0, n = where[i].length; w < n; w++) {
|
||||||
setScopeValuesFromWhere(data, where[i][w]);
|
setScopeValuesFromWhere(data, where[i][w], targetModel);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var prop = targetClass.definition.properties[i];
|
var prop = targetModel.definition.properties[i];
|
||||||
if (prop) {
|
if (prop) {
|
||||||
var val = where[i];
|
var val = where[i];
|
||||||
if (typeof val !== 'object' || val instanceof prop.type
|
if (typeof val !== 'object' || val instanceof prop.type
|
||||||
|
@ -233,9 +256,10 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
function build(data) {
|
function build(data) {
|
||||||
data = data || {};
|
data = data || {};
|
||||||
// Find all fixed property values for the scope
|
// Find all fixed property values for the scope
|
||||||
|
var targetModel = definition.targetModel(this._receiver);
|
||||||
var where = (this._scope && this._scope.where) || {};
|
var where = (this._scope && this._scope.where) || {};
|
||||||
setScopeValuesFromWhere(data, where);
|
setScopeValuesFromWhere(data, where, targetModel);
|
||||||
return new targetClass(data);
|
return new targetModel(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(data, cb) {
|
function create(data, cb) {
|
||||||
|
@ -256,14 +280,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
if (typeof where === 'function') cb = where, where = {};
|
if (typeof where === 'function') cb = where, where = {};
|
||||||
var scoped = (this._scope && this._scope.where) || {};
|
var scoped = (this._scope && this._scope.where) || {};
|
||||||
var filter = mergeQuery({ where: scoped }, { where: where || {} });
|
var filter = mergeQuery({ where: scoped }, { where: where || {} });
|
||||||
targetClass.destroyAll(filter.where, cb);
|
var targetModel = definition.targetModel(this._receiver);
|
||||||
|
targetModel.destroyAll(filter.where, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function count(where, cb) {
|
function count(where, cb) {
|
||||||
if (typeof where === 'function') cb = where, where = {};
|
if (typeof where === 'function') cb = where, where = {};
|
||||||
var scoped = (this._scope && this._scope.where) || {};
|
var scoped = (this._scope && this._scope.where) || {};
|
||||||
var filter = mergeQuery({ where: scoped }, { where: where || {} });
|
var filter = mergeQuery({ where: scoped }, { where: where || {} });
|
||||||
targetClass.count(filter.where, cb);
|
var targetModel = definition.targetModel(this._receiver);
|
||||||
|
targetModel.count(filter.where, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var extend = util._extend;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module exports
|
* Module exports
|
||||||
*/
|
*/
|
||||||
|
@ -369,7 +371,9 @@ var validators = {
|
||||||
|
|
||||||
function getConfigurator(name, opts) {
|
function getConfigurator(name, opts) {
|
||||||
return function () {
|
return function () {
|
||||||
configure(this, name, arguments, opts);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
args[1] = args[1] || {};
|
||||||
|
configure(this, name, args, opts);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,9 +411,10 @@ function getConfigurator(name, opts) {
|
||||||
*/
|
*/
|
||||||
Validatable.prototype.isValid = function (callback, data) {
|
Validatable.prototype.isValid = function (callback, data) {
|
||||||
var valid = true, inst = this, wait = 0, async = false;
|
var valid = true, inst = this, wait = 0, async = false;
|
||||||
|
var validations = this.constructor.validations;
|
||||||
|
|
||||||
// exit with success when no errors
|
// exit with success when no errors
|
||||||
if (!this.constructor._validations) {
|
if (typeof validations !== 'object') {
|
||||||
cleanErrors(this);
|
cleanErrors(this);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
this.trigger('validate', function (validationsDone) {
|
this.trigger('validate', function (validationsDone) {
|
||||||
|
@ -431,21 +436,25 @@ Validatable.prototype.isValid = function (callback, data) {
|
||||||
var inst = this,
|
var inst = this,
|
||||||
asyncFail = false;
|
asyncFail = false;
|
||||||
|
|
||||||
this.constructor._validations.forEach(function (v) {
|
var attrs = Object.keys(validations || {});
|
||||||
if (v[2] && v[2].async) {
|
|
||||||
async = true;
|
attrs.forEach(function(attr) {
|
||||||
wait += 1;
|
var attrValidations = validations[attr] || [];
|
||||||
process.nextTick(function () {
|
attrValidations.forEach(function(v) {
|
||||||
validationFailed(inst, v, done);
|
if (v.options && v.options.async) {
|
||||||
});
|
async = true;
|
||||||
} else {
|
wait += 1;
|
||||||
if (validationFailed(inst, v)) {
|
process.nextTick(function () {
|
||||||
valid = false;
|
validationFailed(inst, attr, v, done);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (validationFailed(inst, attr, v)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!async) {
|
if (!async) {
|
||||||
validationsDone.call(inst, function () {
|
validationsDone.call(inst, function () {
|
||||||
if (valid) cleanErrors(inst);
|
if (valid) cleanErrors(inst);
|
||||||
|
@ -487,11 +496,9 @@ function cleanErrors(inst) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validationFailed(inst, v, cb) {
|
function validationFailed(inst, attr, conf, cb) {
|
||||||
var attr = v[0];
|
var opts = conf.options || {};
|
||||||
var conf = v[1];
|
|
||||||
var opts = v[2] || {};
|
|
||||||
|
|
||||||
if (typeof attr !== 'string') return false;
|
if (typeof attr !== 'string') return false;
|
||||||
|
|
||||||
// here we should check skip validation conditions (if, unless)
|
// here we should check skip validation conditions (if, unless)
|
||||||
|
@ -615,12 +622,12 @@ function blank(v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function configure(cls, validation, args, opts) {
|
function configure(cls, validation, args, opts) {
|
||||||
if (!cls._validations) {
|
if (!cls.validations) {
|
||||||
Object.defineProperty(cls, '_validations', {
|
Object.defineProperty(cls, 'validations', {
|
||||||
writable: true,
|
writable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
value: []
|
value: {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
args = [].slice.call(args);
|
args = [].slice.call(args);
|
||||||
|
@ -634,9 +641,13 @@ function configure(cls, validation, args, opts) {
|
||||||
conf.customValidator = args.pop();
|
conf.customValidator = args.pop();
|
||||||
}
|
}
|
||||||
conf.validation = validation;
|
conf.validation = validation;
|
||||||
args.forEach(function (attr) {
|
var attr = args[0];
|
||||||
cls._validations.push([attr, conf, opts]);
|
if (typeof attr === 'string') {
|
||||||
});
|
var validation = extend({}, conf);
|
||||||
|
validation.options = opts || {};
|
||||||
|
cls.validations[attr] = cls.validations[attr] || [];
|
||||||
|
cls.validations[attr].push(validation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Errors() {
|
function Errors() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-datasource-juggler",
|
"name": "loopback-datasource-juggler",
|
||||||
"version": "2.7.0",
|
"version": "2.8.0",
|
||||||
"description": "LoopBack DataSoure Juggler",
|
"description": "LoopBack DataSoure Juggler",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
|
11
test/init.js
11
test/init.js
|
@ -12,10 +12,17 @@ module.exports = require('should');
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var ModelBuilder = require('../').ModelBuilder;
|
||||||
var Schema = require('../').Schema;
|
var Schema = require('../').Schema;
|
||||||
|
|
||||||
if (!('getSchema' in global)) {
|
if (!('getSchema' in global)) {
|
||||||
global.getSchema = function () {
|
global.getSchema = function (connector, settings) {
|
||||||
return new Schema('memory');
|
return new Schema(connector || 'memory', settings);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!('getModelBuilder' in global)) {
|
||||||
|
global.getModelBuilder = function () {
|
||||||
|
return new ModelBuilder();
|
||||||
|
};
|
||||||
|
}
|
|
@ -1110,6 +1110,27 @@ describe('Load models with relations', function () {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle hasMany through options', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
var Physician = ds.createModel('Physician', {
|
||||||
|
name: String
|
||||||
|
}, {relations: {patients: {model: 'Patient', type: 'hasMany', foreignKey: 'leftId', through: 'Appointment'}}});
|
||||||
|
|
||||||
|
var Patient = ds.createModel('Patient', {
|
||||||
|
name: String
|
||||||
|
}, {relations: {physicians: {model: 'Physician', type: 'hasMany', foreignKey: 'rightId', through: 'Appointment'}}});
|
||||||
|
|
||||||
|
var Appointment = ds.createModel('Appointment', {
|
||||||
|
physicianId: Number,
|
||||||
|
patientId: Number,
|
||||||
|
appointmentDate: Date
|
||||||
|
}, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}});
|
||||||
|
|
||||||
|
assert(Physician.relations['patients'].keyTo === 'leftId');
|
||||||
|
assert(Patient.relations['physicians'].keyTo === 'rightId');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set up relations after attach', function (done) {
|
it('should set up relations after attach', function (done) {
|
||||||
var ds = new DataSource('memory');
|
var ds = new DataSource('memory');
|
||||||
var modelBuilder = new ModelBuilder();
|
var modelBuilder = new ModelBuilder();
|
||||||
|
|
|
@ -102,7 +102,7 @@ describe('manipulation', function () {
|
||||||
|
|
||||||
Person.validatesPresenceOf('name');
|
Person.validatesPresenceOf('name');
|
||||||
Person.create(batch,function (errors, persons) {
|
Person.create(batch,function (errors, persons) {
|
||||||
delete Person._validations;
|
delete Person.validations;
|
||||||
should.exist(errors);
|
should.exist(errors);
|
||||||
errors.should.have.lengthOf(batch.length);
|
errors.should.have.lengthOf(batch.length);
|
||||||
should.not.exist(errors[0]);
|
should.not.exist(errors[0]);
|
||||||
|
|
|
@ -199,6 +199,41 @@ describe('Memory connector', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support neq operator for number', function (done) {
|
||||||
|
User.find({where: {order: {neq: 6}}}, function (err, users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.be.equal(5);
|
||||||
|
for (var i = 0; i < users.length; i++) {
|
||||||
|
users[i].order.should.not.be.equal(6);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support neq operator for string', function (done) {
|
||||||
|
User.find({where: {role: {neq: 'lead'}}}, function (err, users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.be.equal(4);
|
||||||
|
for (var i = 0; i < users.length; i++) {
|
||||||
|
if (users[i].role) {
|
||||||
|
users[i].role.not.be.equal('lead');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support neq operator for null', function (done) {
|
||||||
|
User.find({where: {role: {neq: null}}}, function (err, users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.be.equal(2);
|
||||||
|
for (var i = 0; i < users.length; i++) {
|
||||||
|
should.exist(users[i].role);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function seed(done) {
|
function seed(done) {
|
||||||
var beatles = [
|
var beatles = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,6 +55,8 @@ describe('ModelDefinition class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
User.build();
|
User.build();
|
||||||
|
|
||||||
|
var json = User.toJSON();
|
||||||
|
|
||||||
User.defineProperty("id", {type: "number", id: true});
|
User.defineProperty("id", {type: "number", id: true});
|
||||||
assert.equal(User.properties.name.type, String);
|
assert.equal(User.properties.name.type, String);
|
||||||
|
@ -62,8 +64,12 @@ describe('ModelDefinition class', function () {
|
||||||
assert.equal(User.properties.approved.type, Boolean);
|
assert.equal(User.properties.approved.type, Boolean);
|
||||||
assert.equal(User.properties.joinedAt.type, Date);
|
assert.equal(User.properties.joinedAt.type, Date);
|
||||||
assert.equal(User.properties.age.type, Number);
|
assert.equal(User.properties.age.type, Number);
|
||||||
|
|
||||||
assert.equal(User.properties.id.type, Number);
|
assert.equal(User.properties.id.type, Number);
|
||||||
|
|
||||||
|
json = User.toJSON();
|
||||||
|
assert.deepEqual(json.properties.id, {type: 'Number', id: true});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
// This test written in mocha+should.js
|
// This test written in mocha+should.js
|
||||||
var should = require('./init.js');
|
var should = require('./init.js');
|
||||||
|
var jdb = require('../');
|
||||||
|
var DataSource = jdb.DataSource;
|
||||||
|
|
||||||
var db, Book, Chapter, Author, Reader;
|
var db, tmp, Book, Chapter, Author, Reader;
|
||||||
var Category, Job;
|
var Category, Job;
|
||||||
var Picture, PictureLink;
|
var Picture, PictureLink;
|
||||||
var Person, Address;
|
var Person, Address;
|
||||||
var Link;
|
var Link;
|
||||||
|
|
||||||
|
var getTransientDataSource = function(settings) {
|
||||||
|
return new DataSource('transient', settings, db.modelBuilder);
|
||||||
|
};
|
||||||
|
|
||||||
describe('relations', function () {
|
describe('relations', function () {
|
||||||
|
|
||||||
describe('hasMany', function () {
|
describe('hasMany', function () {
|
||||||
|
@ -433,6 +439,24 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow to add connection with through data', function (done) {
|
||||||
|
Physician.create({name: 'ph1'}, function (e, physician) {
|
||||||
|
Patient.create({name: 'pa1'}, function (e, patient) {
|
||||||
|
var now = Date.now();
|
||||||
|
physician.patients.add(patient, { date: new Date(now) }, function (e, app) {
|
||||||
|
should.not.exist(e);
|
||||||
|
should.exist(app);
|
||||||
|
app.should.be.an.instanceOf(Appointment);
|
||||||
|
app.physicianId.should.equal(physician.id);
|
||||||
|
app.patientId.should.equal(patient.id);
|
||||||
|
app.patientId.should.equal(patient.id);
|
||||||
|
app.date.getTime().should.equal(now);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should allow to remove connection with instance', function (done) {
|
it('should allow to remove connection with instance', function (done) {
|
||||||
var id;
|
var id;
|
||||||
Physician.create(function (err, physician) {
|
Physician.create(function (err, physician) {
|
||||||
|
@ -462,7 +486,159 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('hasMany through - collect', function () {
|
||||||
|
var Physician, Patient, Appointment, Address;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
db = getSchema();
|
||||||
|
Physician = db.define('Physician', {name: String});
|
||||||
|
Patient = db.define('Patient', {name: String});
|
||||||
|
Appointment = db.define('Appointment', {date: {type: Date,
|
||||||
|
default: function () {
|
||||||
|
return new Date();
|
||||||
|
}}});
|
||||||
|
Address = db.define('Address', {name: String});
|
||||||
|
|
||||||
|
db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], function (err) {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with default options', function () {
|
||||||
|
it('can determine the collect by modelTo\'s name as default', function () {
|
||||||
|
Physician.hasMany(Patient, {through: Appointment});
|
||||||
|
Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
||||||
|
Patient.belongsTo(Address);
|
||||||
|
Appointment.belongsTo(Physician);
|
||||||
|
Appointment.belongsTo(Patient);
|
||||||
|
var physician = new Physician({id: 1});
|
||||||
|
var scope1 = physician.patients._scope;
|
||||||
|
scope1.should.have.property('collect', 'patient');
|
||||||
|
scope1.should.have.property('include', 'patient');
|
||||||
|
var patient = new Patient({id: 1});
|
||||||
|
var scope2 = patient.yyy._scope;
|
||||||
|
scope2.should.have.property('collect', 'physician');
|
||||||
|
scope2.should.have.property('include', 'physician');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when custom reverse belongsTo names for both sides', function () {
|
||||||
|
it('can determine the collect via keyThrough', function () {
|
||||||
|
Physician.hasMany(Patient, {through: Appointment, foreignKey: 'fooId', keyThrough: 'barId'});
|
||||||
|
Patient.hasMany(Physician, {through: Appointment, foreignKey: 'barId', keyThrough: 'fooId', as: 'yyy'});
|
||||||
|
Appointment.belongsTo(Physician, {as: 'foo'});
|
||||||
|
Appointment.belongsTo(Patient, {as: 'bar'});
|
||||||
|
Patient.belongsTo(Address); // jam.
|
||||||
|
Appointment.belongsTo(Patient, {as: 'car'}); // jam. Should we complain in this case???
|
||||||
|
|
||||||
|
var physician = new Physician({id: 1});
|
||||||
|
var scope1 = physician.patients._scope;
|
||||||
|
scope1.should.have.property('collect', 'bar');
|
||||||
|
scope1.should.have.property('include', 'bar');
|
||||||
|
var patient = new Patient({id: 1});
|
||||||
|
var scope2 = patient.yyy._scope;
|
||||||
|
scope2.should.have.property('collect', 'foo');
|
||||||
|
scope2.should.have.property('include', 'foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via modelTo name', function () {
|
||||||
|
Physician.hasMany(Patient, {through: Appointment});
|
||||||
|
Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
||||||
|
Appointment.belongsTo(Physician, {as: 'foo', foreignKey: 'physicianId'});
|
||||||
|
Appointment.belongsTo(Patient, {as: 'bar', foreignKey: 'patientId'});
|
||||||
|
Patient.belongsTo(Address); // jam.
|
||||||
|
|
||||||
|
var physician = new Physician({id: 1});
|
||||||
|
var scope1 = physician.patients._scope;
|
||||||
|
scope1.should.have.property('collect', 'bar');
|
||||||
|
scope1.should.have.property('include', 'bar');
|
||||||
|
var patient = new Patient({id: 1});
|
||||||
|
var scope2 = patient.yyy._scope;
|
||||||
|
scope2.should.have.property('collect', 'foo');
|
||||||
|
scope2.should.have.property('include', 'foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via modelTo name (with jams)', function () {
|
||||||
|
Physician.hasMany(Patient, {through: Appointment});
|
||||||
|
Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
||||||
|
Appointment.belongsTo(Physician, {as: 'foo', foreignKey: 'physicianId'});
|
||||||
|
Appointment.belongsTo(Patient, {as: 'bar', foreignKey: 'patientId'});
|
||||||
|
Patient.belongsTo(Address); // jam.
|
||||||
|
Appointment.belongsTo(Physician, {as: 'goo', foreignKey: 'physicianId'}); // jam. Should we complain in this case???
|
||||||
|
Appointment.belongsTo(Patient, {as: 'car', foreignKey: 'patientId'}); // jam. Should we complain in this case???
|
||||||
|
|
||||||
|
var physician = new Physician({id: 1});
|
||||||
|
var scope1 = physician.patients._scope;
|
||||||
|
scope1.should.have.property('collect', 'bar');
|
||||||
|
scope1.should.have.property('include', 'bar');
|
||||||
|
var patient = new Patient({id: 1});
|
||||||
|
var scope2 = patient.yyy._scope;
|
||||||
|
scope2.should.have.property('collect', 'foo'); // first matched relation
|
||||||
|
scope2.should.have.property('include', 'foo'); // first matched relation
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when custom reverse belongsTo name for one side only', function () {
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Physician.hasMany(Patient, {as: 'xxx', through: Appointment, foreignKey: 'fooId'});
|
||||||
|
Patient.hasMany(Physician, {as: 'yyy', through: Appointment, keyThrough: 'fooId'});
|
||||||
|
Appointment.belongsTo(Physician, {as: 'foo'});
|
||||||
|
Appointment.belongsTo(Patient);
|
||||||
|
Patient.belongsTo(Address); // jam.
|
||||||
|
Appointment.belongsTo(Physician, {as: 'bar'}); // jam. Should we complain in this case???
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via model name', function () {
|
||||||
|
var physician = new Physician({id: 1});
|
||||||
|
var scope1 = physician.xxx._scope;
|
||||||
|
scope1.should.have.property('collect', 'patient');
|
||||||
|
scope1.should.have.property('include', 'patient');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via keyThrough', function () {
|
||||||
|
var patient = new Patient({id: 1});
|
||||||
|
var scope2 = patient.yyy._scope;
|
||||||
|
scope2.should.have.property('collect', 'foo');
|
||||||
|
scope2.should.have.property('include', 'foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasMany through - between same model', function () {
|
||||||
|
var User, Follow, Address;
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
db = getSchema();
|
||||||
|
User = db.define('User', {name: String});
|
||||||
|
Follow = db.define('Follow', {date: {type: Date,
|
||||||
|
default: function () {
|
||||||
|
return new Date();
|
||||||
|
}}});
|
||||||
|
Address = db.define('Address', {name: String});
|
||||||
|
|
||||||
|
User.hasMany(User, {as: 'followers', foreignKey: 'followeeId', keyThrough: 'followerId', through: Follow});
|
||||||
|
User.hasMany(User, {as: 'following', foreignKey: 'followerId', keyThrough: 'followeeId', through: Follow});
|
||||||
|
User.belongsTo(Address);
|
||||||
|
Follow.belongsTo(User, {as: 'follower'});
|
||||||
|
Follow.belongsTo(User, {as: 'followee'});
|
||||||
|
db.automigrate(['User', 'Follow', 'Address'], function (err) {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via keyThrough for each side', function () {
|
||||||
|
var user = new User({id: 1});
|
||||||
|
var scope1 = user.followers._scope;
|
||||||
|
scope1.should.have.property('collect', 'follower');
|
||||||
|
scope1.should.have.property('include', 'follower');
|
||||||
|
var scope2 = user.following._scope;
|
||||||
|
scope2.should.have.property('collect', 'followee');
|
||||||
|
scope2.should.have.property('include', 'followee');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('hasMany with properties', function () {
|
describe('hasMany with properties', function () {
|
||||||
it('can be declared with properties', function (done) {
|
it('can be declared with properties', function (done) {
|
||||||
Book.hasMany(Chapter, { properties: { type: 'bookType' } });
|
Book.hasMany(Chapter, { properties: { type: 'bookType' } });
|
||||||
|
@ -915,6 +1091,29 @@ describe('relations', function () {
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can determine the collect via modelTo name', function () {
|
||||||
|
Author.hasAndBelongsToMany(Picture, { through: PictureLink, polymorphic: 'imageable' });
|
||||||
|
Reader.hasAndBelongsToMany(Picture, { through: PictureLink, polymorphic: 'imageable' });
|
||||||
|
// Optionally, define inverse relations:
|
||||||
|
Picture.hasMany(Author, { through: PictureLink, polymorphic: 'imageable', invert: true });
|
||||||
|
Picture.hasMany(Reader, { through: PictureLink, polymorphic: 'imageable', invert: true });
|
||||||
|
var author = new Author({id: 1});
|
||||||
|
var scope1 = author.pictures._scope;
|
||||||
|
scope1.should.have.property('collect', 'picture');
|
||||||
|
scope1.should.have.property('include', 'picture');
|
||||||
|
var reader = new Reader({id: 1});
|
||||||
|
var scope2 = reader.pictures._scope;
|
||||||
|
scope2.should.have.property('collect', 'picture');
|
||||||
|
scope2.should.have.property('include', 'picture');
|
||||||
|
var picture = new Picture({id: 1});
|
||||||
|
var scope3 = picture.authors._scope;
|
||||||
|
scope3.should.have.property('collect', 'imageable');
|
||||||
|
scope3.should.have.property('include', 'imageable');
|
||||||
|
var scope4 = picture.readers._scope;
|
||||||
|
scope4.should.have.property('collect', 'imageable');
|
||||||
|
scope4.should.have.property('include', 'imageable');
|
||||||
|
});
|
||||||
|
|
||||||
var author, reader, pictures = [];
|
var author, reader, pictures = [];
|
||||||
it('should create polymorphic relation - author', function (done) {
|
it('should create polymorphic relation - author', function (done) {
|
||||||
Author.create({ name: 'Author 1' }, function (err, a) {
|
Author.create({ name: 'Author 1' }, function (err, a) {
|
||||||
|
@ -1486,9 +1685,10 @@ describe('relations', function () {
|
||||||
var Other;
|
var Other;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
|
tmp = getTransientDataSource();
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Passport = db.define('Passport',
|
Passport = tmp.define('Passport',
|
||||||
{name:{type:'string', required: true}},
|
{name:{type:'string', required: true}},
|
||||||
{idInjection: false}
|
{idInjection: false}
|
||||||
);
|
);
|
||||||
|
@ -1634,9 +1834,10 @@ describe('relations', function () {
|
||||||
var address1, address2;
|
var address1, address2;
|
||||||
|
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
|
tmp = getTransientDataSource({defaultIdType: Number});
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Address = db.define('Address', {street: String});
|
Address = tmp.define('Address', {street: String});
|
||||||
Address.validatesPresenceOf('street');
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
db.automigrate(function () {
|
db.automigrate(function () {
|
||||||
|
@ -1813,9 +2014,10 @@ describe('relations', function () {
|
||||||
|
|
||||||
describe('embedsMany - explicit ids', function () {
|
describe('embedsMany - explicit ids', function () {
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
|
tmp = getTransientDataSource();
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Address = db.define('Address', {id: { type: String, id: true }, street: String});
|
Address = tmp.define('Address', {street: String});
|
||||||
Address.validatesPresenceOf('street');
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
db.automigrate(function () {
|
db.automigrate(function () {
|
||||||
|
@ -1824,13 +2026,13 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be declared', function (done) {
|
it('can be declared', function (done) {
|
||||||
Person.embedsMany(Address, { options: { autoId: false } });
|
Person.embedsMany(Address);
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create embedded items on scope', function(done) {
|
it('should create embedded items on scope', function(done) {
|
||||||
Person.create({ name: 'Fred' }, function(err, p) {
|
Person.create({ name: 'Fred' }, function(err, p) {
|
||||||
p.addressList.create({ id: 'home', street: 'Street 1' }, function(err, addresses) {
|
p.addressList.create({ id: 'home', street: 'Street 1' }, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, address) {
|
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -1968,6 +2170,17 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create embedded items with auto-generated id', function(done) {
|
||||||
|
Person.create({ name: 'Wilma' }, function(err, p) {
|
||||||
|
p.addressList.create({ street: 'Home Street 1' }, function(err, address) {
|
||||||
|
should.not.exist(err);
|
||||||
|
address.id.should.match(/^[0-9a-fA-F]{24}$/);
|
||||||
|
address.street.should.equal('Home Street 1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('embedsMany - relations, scope and properties', function () {
|
describe('embedsMany - relations, scope and properties', function () {
|
||||||
|
@ -2208,11 +2421,16 @@ describe('relations', function () {
|
||||||
|
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
|
tmp = getTransientDataSource();
|
||||||
|
|
||||||
Book = db.define('Book', {name: String});
|
Book = db.define('Book', {name: String});
|
||||||
Author = db.define('Author', {name: String});
|
Author = db.define('Author', {name: String});
|
||||||
Reader = db.define('Reader', {name: String});
|
Reader = db.define('Reader', {name: String});
|
||||||
|
|
||||||
Link = db.define('Link', {name: String, notes: String}); // generic model
|
Link = tmp.define('Link', {
|
||||||
|
id: {type: Number, id: true},
|
||||||
|
name: String, notes: String
|
||||||
|
}); // generic model
|
||||||
Link.validatesPresenceOf('linkedId');
|
Link.validatesPresenceOf('linkedId');
|
||||||
Link.validatesPresenceOf('linkedType');
|
Link.validatesPresenceOf('linkedType');
|
||||||
|
|
||||||
|
@ -2226,13 +2444,15 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be declared', function (done) {
|
it('can be declared', function (done) {
|
||||||
|
var idType = db.connector.getDefaultIdType();
|
||||||
|
|
||||||
Book.embedsMany(Link, { as: 'people',
|
Book.embedsMany(Link, { as: 'people',
|
||||||
polymorphic: 'linked',
|
polymorphic: 'linked',
|
||||||
scope: { include: 'linked' }
|
scope: { include: 'linked' }
|
||||||
});
|
});
|
||||||
Link.belongsTo('linked', {
|
Link.belongsTo('linked', {
|
||||||
polymorphic: true, // needs unique auto-id
|
polymorphic: { idType: idType }, // native type
|
||||||
properties: { name: 'name' }, // denormalized
|
properties: { name: 'name' }, // denormalized
|
||||||
options: { invertProperties: true }
|
options: { invertProperties: true }
|
||||||
});
|
});
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
|
@ -2411,7 +2631,7 @@ describe('relations', function () {
|
||||||
err.name.should.equal('ValidationError');
|
err.name.should.equal('ValidationError');
|
||||||
err.details.codes.jobs.should.eql(['uniqueness']);
|
err.details.codes.jobs.should.eql(['uniqueness']);
|
||||||
var expected = 'The `Category` instance is not valid. ';
|
var expected = 'The `Category` instance is not valid. ';
|
||||||
expected += 'Details: `jobs` Contains duplicate `Job` instance.';
|
expected += 'Details: `jobs` contains duplicate `Job` instance.';
|
||||||
err.message.should.equal(expected);
|
err.message.should.equal(expected);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
|
@ -229,3 +229,90 @@ describe('scope - filtered count and destroyAll', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('scope - dynamic target class', function () {
|
||||||
|
|
||||||
|
var Collection, Media, Image, Video;
|
||||||
|
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
db = getSchema();
|
||||||
|
Image = db.define('Image', {name: String});
|
||||||
|
Video = db.define('Video', {name: String});
|
||||||
|
|
||||||
|
Collection = db.define('Collection', {name: String, modelName: String});
|
||||||
|
Collection.scope('items', function() {
|
||||||
|
return {}; // could return a scope based on `this` (receiver)
|
||||||
|
}, null, {}, { isStatic: false, modelTo: function(receiver) {
|
||||||
|
return db.models[receiver.modelName];
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Collection.destroyAll(function() {
|
||||||
|
Image.destroyAll(function() {
|
||||||
|
Video.destroyAll(done);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Collection.create({ name: 'Images', modelName: 'Image' }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Collection.create({ name: 'Videos', modelName: 'Video' }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Collection.create({ name: 'Things', modelName: 'Unknown' }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Image.create({ name: 'Image A' }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Video.create({ name: 'Video A' }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deduce modelTo at runtime - Image', function(done) {
|
||||||
|
Collection.findOne({ where: { modelName: 'Image' } }, function(err, coll) {
|
||||||
|
should.not.exist(err);
|
||||||
|
coll.name.should.equal('Images');
|
||||||
|
coll.items(function(err, items) {
|
||||||
|
should.not.exist(err);
|
||||||
|
items.length.should.equal(1);
|
||||||
|
items[0].name.should.equal('Image A');
|
||||||
|
items[0].should.be.instanceof(Image);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deduce modelTo at runtime - Video', function(done) {
|
||||||
|
Collection.findOne({ where: { modelName: 'Video' } }, function(err, coll) {
|
||||||
|
should.not.exist(err);
|
||||||
|
coll.name.should.equal('Videos');
|
||||||
|
coll.items(function(err, items) {
|
||||||
|
should.not.exist(err);
|
||||||
|
items.length.should.equal(1);
|
||||||
|
items[0].name.should.equal('Video A');
|
||||||
|
items[0].should.be.instanceof(Video);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if modelTo is invalid', function(done) {
|
||||||
|
Collection.findOne({ where: { name: 'Things' } }, function(err, coll) {
|
||||||
|
should.not.exist(err);
|
||||||
|
coll.modelName.should.equal('Unknown');
|
||||||
|
(function () {
|
||||||
|
coll.items(function(err, items) {});
|
||||||
|
}).should.throw();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
var jdb = require('../');
|
||||||
|
var DataSource = jdb.DataSource;
|
||||||
|
var assert = require('assert');
|
||||||
|
var async = require('async');
|
||||||
|
var should = require('./init.js');
|
||||||
|
|
||||||
|
var db, TransientModel, Person, Widget, Item;
|
||||||
|
|
||||||
|
var getTransientDataSource = function(settings) {
|
||||||
|
return new DataSource('transient', settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Transient connector', function () {
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
db = getTransientDataSource();
|
||||||
|
TransientModel = db.define('TransientModel', {}, { idInjection: false });
|
||||||
|
|
||||||
|
Person = TransientModel.extend('Person', {name: String});
|
||||||
|
Person.attachTo(db);
|
||||||
|
|
||||||
|
Widget = db.define('Widget', {name: String});
|
||||||
|
Item = db.define('Item', {
|
||||||
|
id: {type: Number, id: true}, name: String
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect idInjection being false', function(done) {
|
||||||
|
should.not.exist(Person.definition.properties.id);
|
||||||
|
should.exist(Person.definition.properties.name);
|
||||||
|
|
||||||
|
Person.create({ name: 'Wilma' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.toObject().should.eql({ name: 'Wilma' });
|
||||||
|
|
||||||
|
Person.count(function(err, count) {
|
||||||
|
should.not.exist(err);
|
||||||
|
count.should.equal(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a random string id', function(done) {
|
||||||
|
should.exist(Widget.definition.properties.id);
|
||||||
|
should.exist(Widget.definition.properties.name);
|
||||||
|
|
||||||
|
Widget.definition.properties.id.type.should.equal(String);
|
||||||
|
|
||||||
|
Widget.create({ name: 'Thing' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.id.should.match(/^[0-9a-fA-F]{24}$/);
|
||||||
|
inst.name.should.equal('Thing');
|
||||||
|
|
||||||
|
Widget.findById(inst.id, function(err, widget) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.not.exist(widget);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a random number id', function(done) {
|
||||||
|
should.exist(Item.definition.properties.id);
|
||||||
|
should.exist(Item.definition.properties.name);
|
||||||
|
|
||||||
|
Item.definition.properties.id.type.should.equal(Number);
|
||||||
|
|
||||||
|
Item.create({ name: 'Example' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.name.should.equal('Example');
|
||||||
|
|
||||||
|
Item.count(function(err, count) {
|
||||||
|
should.not.exist(err);
|
||||||
|
count.should.equal(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -39,7 +39,7 @@ describe('validations', function () {
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
User.destroyAll(function () {
|
User.destroyAll(function () {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -67,7 +67,7 @@ describe('validations', function () {
|
||||||
describe('lifecycle', function () {
|
describe('lifecycle', function () {
|
||||||
|
|
||||||
it('should work on create', function (done) {
|
it('should work on create', function (done) {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
User.validatesPresenceOf('name');
|
User.validatesPresenceOf('name');
|
||||||
User.create(function (e, u) {
|
User.create(function (e, u) {
|
||||||
should.exist(e);
|
should.exist(e);
|
||||||
|
@ -79,7 +79,7 @@ describe('validations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work on update', function (done) {
|
it('should work on update', function (done) {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
User.validatesPresenceOf('name');
|
User.validatesPresenceOf('name');
|
||||||
User.create({name: 'Valid'}, function (e, d) {
|
User.create({name: 'Valid'}, function (e, d) {
|
||||||
d.updateAttribute('name', null, function (e) {
|
d.updateAttribute('name', null, function (e) {
|
||||||
|
@ -95,7 +95,7 @@ describe('validations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error code', function (done) {
|
it('should return error code', function (done) {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
User.validatesPresenceOf('name');
|
User.validatesPresenceOf('name');
|
||||||
User.create(function (e, u) {
|
User.create(function (e, u) {
|
||||||
should.exist(e);
|
should.exist(e);
|
||||||
|
@ -112,7 +112,7 @@ describe('validations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include validation messages in err.message', function(done) {
|
it('should include validation messages in err.message', function(done) {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
User.validatesPresenceOf('name');
|
User.validatesPresenceOf('name');
|
||||||
User.create(function (e, u) {
|
User.create(function (e, u) {
|
||||||
should.exist(e);
|
should.exist(e);
|
||||||
|
@ -122,7 +122,7 @@ describe('validations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include model name in err.message', function(done) {
|
it('should include model name in err.message', function(done) {
|
||||||
delete User._validations;
|
delete User.validations;
|
||||||
User.validatesPresenceOf('name');
|
User.validatesPresenceOf('name');
|
||||||
User.create(function (e, u) {
|
User.create(function (e, u) {
|
||||||
should.exist(e);
|
should.exist(e);
|
||||||
|
@ -130,6 +130,14 @@ describe('validations', function () {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return validation metadata', function() {
|
||||||
|
var expected = {name:[{validation: 'presence', options: {}}]};
|
||||||
|
delete User.validations;
|
||||||
|
User.validatesPresenceOf('name');
|
||||||
|
var validations = User.validations;
|
||||||
|
validations.should.eql(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue