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');
|
||||
|
||||
/**
|
||||
* 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 {Function} [callback] The callback function
|
||||
|
@ -406,9 +406,10 @@ function applyFilter(filter) {
|
|||
if (typeof value === 'string' && (example instanceof RegExp)) {
|
||||
return value.match(example);
|
||||
}
|
||||
if (example === undefined || value === undefined) {
|
||||
if (example === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof example === 'object') {
|
||||
// ignore geo near filter
|
||||
if (example.near) {
|
||||
|
@ -425,6 +426,10 @@ function applyFilter(filter) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if ('neq' in example) {
|
||||
return compare(example.neq, value) !== 0;
|
||||
}
|
||||
|
||||
if (example.like || example.nlike) {
|
||||
|
||||
var like = example.like || example.nlike;
|
||||
|
@ -445,7 +450,8 @@ function applyFilter(filter) {
|
|||
}
|
||||
}
|
||||
// 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
|
||||
*/
|
||||
var jutil = require('./jutil');
|
||||
var validations = require('./validations.js');
|
||||
var ValidationError = validations.ValidationError;
|
||||
var ValidationError = require('./validations').ValidationError;
|
||||
var Relation = require('./relations.js');
|
||||
var Inclusion = require('./include.js');
|
||||
var List = require('./list.js');
|
||||
|
@ -40,15 +39,12 @@ function DataAccessObject() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function idName(m) {
|
||||
return m.getDataSource().idName
|
||||
? m.getDataSource().idName(m.modelName) : 'id';
|
||||
return m.definition.idName() || 'id';
|
||||
}
|
||||
|
||||
function getIdValue(m, data) {
|
||||
return data && data[m.getDataSource().idName(m.modelName)];
|
||||
return data && data[idName(m)];
|
||||
}
|
||||
|
||||
function setIdValue(m, data, value) {
|
||||
|
@ -331,7 +327,7 @@ DataAccessObject.findByIds = function(ids, cond, cb) {
|
|||
cond = {};
|
||||
}
|
||||
|
||||
var pk = this.dataSource.idName(this.modelName) || 'id';
|
||||
var pk = idName(this);
|
||||
if (ids.length === 0) {
|
||||
process.nextTick(function() { cb(null, []); });
|
||||
return;
|
||||
|
|
|
@ -392,6 +392,7 @@ DataSource.prototype.defineScopes = function (modelClass, scopes) {
|
|||
* @param relations
|
||||
*/
|
||||
DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||
var self = this;
|
||||
|
||||
// Create a function for the closure in the loop
|
||||
var createListener = function (name, relation, targetModel, throughModel) {
|
||||
|
@ -416,7 +417,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
|||
throughModel.once('dataAccessConfigured', function (model) {
|
||||
if (isModelDataSourceAttached(targetModel)) {
|
||||
// The target model is resolved
|
||||
var params = traverse(relations).clone();
|
||||
var params = traverse(relation).clone();
|
||||
params.as = name;
|
||||
params.model = targetModel;
|
||||
params.through = model;
|
||||
|
@ -428,7 +429,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
|||
|
||||
// Set up the relations
|
||||
if (relations) {
|
||||
for (var rn in relations) {
|
||||
Object.keys(relations).forEach(function(rn) {
|
||||
var r = relations[rn];
|
||||
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
||||
var targetModel, polymorphicName;
|
||||
|
@ -447,12 +448,12 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
|||
}
|
||||
|
||||
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;
|
||||
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))
|
||||
|
@ -469,7 +470,7 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
|||
}
|
||||
modelClass[r.type].call(modelClass, rn, params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -210,6 +210,7 @@ ModelDefinition.prototype.build = function (forceRebuild) {
|
|||
this.properties = null;
|
||||
this.relations = [];
|
||||
this._ids = null;
|
||||
this.json = null;
|
||||
}
|
||||
if (this.properties) {
|
||||
return this.properties;
|
||||
|
|
|
@ -11,7 +11,7 @@ var util = require('util');
|
|||
var jutil = require('./jutil');
|
||||
var List = require('./list');
|
||||
var Hookable = require('./hooks');
|
||||
var validations = require('./validations.js');
|
||||
var validations = require('./validations');
|
||||
var _extend = util._extend;
|
||||
|
||||
// Set up an object for quick lookup
|
||||
|
|
|
@ -391,13 +391,13 @@ util.inherits(HasOne, Relation);
|
|||
* EmbedsOne subclass
|
||||
* @param {RelationDefinition|Object} definition
|
||||
* @param {Object} modelInstance
|
||||
* @returns {EmbedsMany}
|
||||
* @returns {EmbedsOne}
|
||||
* @constructor
|
||||
* @class EmbedsOne
|
||||
*/
|
||||
function EmbedsOne(definition, modelInstance) {
|
||||
if (!(this instanceof EmbedsOne)) {
|
||||
return new EmbedsMany(definition, modelInstance);
|
||||
return new EmbedsOne(definition, modelInstance);
|
||||
}
|
||||
assert(definition.type === RelationTypes.embedsOne);
|
||||
Relation.apply(this, arguments);
|
||||
|
@ -489,7 +489,6 @@ function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
|||
}
|
||||
if (typeof modelTo === 'string') {
|
||||
modelToName = (singularize ? i8n.singularize(params.as) : params.as).toLowerCase();
|
||||
console.log(modelToName)
|
||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
||||
}
|
||||
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
|
||||
* @returns {Object} The normalized parameters
|
||||
*/
|
||||
function polymorphicParams(params) {
|
||||
function polymorphicParams(params, as) {
|
||||
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.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true);
|
||||
return params;
|
||||
|
@ -541,13 +540,17 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
|||
|
||||
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, 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 discriminator, polymorphic;
|
||||
|
||||
if (params.polymorphic) {
|
||||
polymorphic = polymorphicParams(params.polymorphic);
|
||||
if (params.invert) polymorphic.invert = true;
|
||||
if (params.invert) {
|
||||
polymorphic.invert = true;
|
||||
keyThrough = polymorphic.foreignKey;
|
||||
}
|
||||
discriminator = polymorphic.discriminator;
|
||||
if (!params.invert) {
|
||||
fk = polymorphic.foreignKey;
|
||||
|
@ -568,14 +571,12 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
|||
properties: params.properties,
|
||||
scope: params.scope,
|
||||
options: params.options,
|
||||
keyThrough: keyThrough,
|
||||
polymorphic: polymorphic
|
||||
});
|
||||
|
||||
definition.modelThrough = params.through;
|
||||
|
||||
var keyThrough = definition.throughKey || i8n.camelize(modelTo.modelName + '_id', true);
|
||||
definition.keyThrough = keyThrough;
|
||||
|
||||
modelFrom.relations[relationName] = definition;
|
||||
|
||||
if (!params.through) {
|
||||
|
@ -636,16 +637,34 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
|||
filter.where[fk] = this[idName];
|
||||
|
||||
definition.applyScope(this, filter);
|
||||
|
||||
if (params.through && params.polymorphic && params.invert) {
|
||||
filter.where[discriminator] = modelTo.modelName; // overwrite
|
||||
filter.collect = params.polymorphic;
|
||||
filter.include = filter.collect;
|
||||
} else if (params.through) {
|
||||
filter.collect = i8n.camelize(modelTo.modelName, true);
|
||||
filter.include = filter.collect;
|
||||
|
||||
if (definition.modelThrough) {
|
||||
var throughRelationName;
|
||||
|
||||
// find corresponding belongsTo relations from through model as collect
|
||||
for(var r in definition.modelThrough.relations) {
|
||||
var relation = definition.modelThrough.relations[r];
|
||||
|
||||
// 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;
|
||||
}, scopeMethods, definition.options);
|
||||
|
||||
|
@ -918,14 +937,18 @@ HasManyThrough.prototype.create = function create(data, done) {
|
|||
/**
|
||||
* Add the target model instance to the 'hasMany' relation
|
||||
* @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 definition = this.definition;
|
||||
var modelThrough = definition.modelThrough;
|
||||
var pk1 = definition.keyFrom;
|
||||
|
||||
var data = {};
|
||||
if (typeof data === 'function') {
|
||||
done = data;
|
||||
data = {};
|
||||
}
|
||||
var query = {};
|
||||
|
||||
// The primary key for the target model
|
||||
|
@ -1056,21 +1079,23 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
|||
|
||||
var idName, relationName, fk;
|
||||
if (params.polymorphic) {
|
||||
relationName = params.as || (typeof modelTo === 'string' ? modelTo : null); // initially
|
||||
|
||||
if (params.polymorphic === true) {
|
||||
// modelTo arg will be the name of the polymorphic relation (string)
|
||||
polymorphic = polymorphicParams(modelTo);
|
||||
polymorphic = polymorphicParams(modelTo, relationName);
|
||||
} else {
|
||||
polymorphic = polymorphicParams(params.polymorphic);
|
||||
polymorphic = polymorphicParams(params.polymorphic, relationName);
|
||||
}
|
||||
|
||||
modelTo = null; // will lookup dynamically
|
||||
|
||||
idName = params.idName || 'id';
|
||||
relationName = params.as || polymorphic.as;
|
||||
relationName = params.as || polymorphic.as; // finally
|
||||
fk = polymorphic.foreignKey;
|
||||
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 });
|
||||
} else { // try to use the same foreign key type as modelFrom
|
||||
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName);
|
||||
|
@ -1842,8 +1867,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
|||
type: [modelTo], default: function() { return []; }
|
||||
});
|
||||
|
||||
// unique id is required
|
||||
modelTo.validatesPresenceOf(idName);
|
||||
if (typeof modelTo.dataSource.connector.generateId !== 'function') {
|
||||
modelTo.validatesPresenceOf(idName); // unique id is required
|
||||
}
|
||||
|
||||
if (!params.polymorphic) {
|
||||
modelFrom.validate(propertyName, function(err) {
|
||||
|
@ -1853,7 +1879,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
|||
return ids.indexOf(id) === pos;
|
||||
});
|
||||
if (ids.length !== uniqueIds.length) {
|
||||
this.errors.add(propertyName, 'Contains duplicate `' + idName + '`', 'uniqueness');
|
||||
this.errors.add(propertyName, 'contains duplicate `' + idName + '`', 'uniqueness');
|
||||
err(false);
|
||||
}
|
||||
}, { code: 'uniqueness' })
|
||||
|
@ -1877,7 +1903,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
|||
}
|
||||
} else {
|
||||
hasErrors = true;
|
||||
self.errors.add(propertyName, 'Contains invalid item', 'invalid');
|
||||
self.errors.add(propertyName, 'contains invalid item', 'invalid');
|
||||
}
|
||||
});
|
||||
if (hasErrors) err(false);
|
||||
|
@ -2143,7 +2169,6 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
|||
var modelTo = this.definition.modelTo;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
var modelInstance = this.modelInstance;
|
||||
var autoId = this.definition.options.autoId !== false;
|
||||
|
||||
if (typeof targetModelData === 'function' && !cb) {
|
||||
cb = targetModelData;
|
||||
|
@ -2170,16 +2195,22 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
|||
};
|
||||
|
||||
EmbedsMany.prototype.build = function(targetModelData) {
|
||||
var pk = this.definition.keyTo;
|
||||
var modelTo = this.definition.modelTo;
|
||||
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();
|
||||
|
||||
targetModelData = targetModelData || {};
|
||||
|
||||
if (typeof targetModelData[pk] !== 'number' && autoId) {
|
||||
var assignId = (forceId || targetModelData[pk] === undefined);
|
||||
|
||||
if (assignId && pkType === Number) {
|
||||
var ids = embeddedList.map(function(m) {
|
||||
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
||||
});
|
||||
|
@ -2188,6 +2219,9 @@ EmbedsMany.prototype.build = function(targetModelData) {
|
|||
} else {
|
||||
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);
|
||||
|
@ -2325,7 +2359,7 @@ RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo,
|
|||
return ids.indexOf(id) === pos;
|
||||
});
|
||||
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');
|
||||
err(false);
|
||||
}
|
||||
|
|
62
lib/scope.js
62
lib/scope.js
|
@ -1,6 +1,8 @@
|
|||
var i8n = require('inflection');
|
||||
var utils = require('./utils');
|
||||
var defineCachedRelations = utils.defineCachedRelations;
|
||||
var DefaultModelBaseClass = require('./model.js');
|
||||
|
||||
/**
|
||||
* Module exports
|
||||
*/
|
||||
|
@ -13,10 +15,26 @@ function ScopeDefinition(definition) {
|
|||
this.modelTo = definition.modelTo || definition.modelFrom;
|
||||
this.name = definition.name;
|
||||
this.params = definition.params;
|
||||
this.methods = definition.methods;
|
||||
this.options = definition.options;
|
||||
this.methods = definition.methods || {};
|
||||
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) {
|
||||
var name = this.name;
|
||||
var self = receiver;
|
||||
|
@ -42,7 +60,8 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
|
|||
|| actualRefresh) {
|
||||
// It either doesn't hit the cache or refresh is required
|
||||
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) {
|
||||
defineCachedRelations(self);
|
||||
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
|
||||
*/
|
||||
function defineScope(cls, targetClass, name, params, methods, options) {
|
||||
|
||||
// collect meta info about scope
|
||||
if (!cls._scopeMeta) {
|
||||
cls._scopeMeta = {};
|
||||
|
@ -84,7 +102,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
// are same
|
||||
if (cls === targetClass) {
|
||||
cls._scopeMeta[name] = params;
|
||||
} else {
|
||||
} else if (targetClass) {
|
||||
if (!targetClass._scopeMeta) {
|
||||
targetClass._scopeMeta = {};
|
||||
}
|
||||
|
@ -100,7 +118,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
name: name,
|
||||
params: params,
|
||||
methods: methods,
|
||||
options: options || {}
|
||||
options: options
|
||||
});
|
||||
|
||||
if(isStatic) {
|
||||
|
@ -127,7 +145,9 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
*
|
||||
*/
|
||||
get: function () {
|
||||
var targetModel = definition.targetModel(this);
|
||||
var self = this;
|
||||
|
||||
var f = function(condOrRefresh, cb) {
|
||||
if(arguments.length === 1) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
f._receiver = this;
|
||||
f._scope = typeof definition.params === 'function' ?
|
||||
definition.params.call(self) : definition.params;
|
||||
|
||||
f._targetClass = definition.modelTo.modelName;
|
||||
|
||||
f._targetClass = targetModel.modelName;
|
||||
if (f._scope.collect) {
|
||||
f._targetClass = i8n.capitalize(f._scope.collect);
|
||||
}
|
||||
|
||||
|
||||
f.build = build;
|
||||
f.create = create;
|
||||
f.destroyAll = destroyAll;
|
||||
|
@ -151,6 +172,8 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
for (var i in definition.methods) {
|
||||
f[i] = definition.methods[i].bind(self);
|
||||
}
|
||||
|
||||
if (!targetClass) return f;
|
||||
|
||||
// Define scope-chaining, such as
|
||||
// Station.scope('active', {where: {isActive: true}});
|
||||
|
@ -160,7 +183,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
Object.defineProperty(f, name, {
|
||||
enumerable: false,
|
||||
get: function () {
|
||||
mergeQuery(f._scope, targetClass._scopeMeta[name]);
|
||||
mergeQuery(f._scope, targetModel._scopeMeta[name]);
|
||||
return f;
|
||||
}
|
||||
});
|
||||
|
@ -207,16 +230,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
* @param {Object} The data object
|
||||
* @param {Object} The where clause
|
||||
*/
|
||||
function setScopeValuesFromWhere(data, where) {
|
||||
function setScopeValuesFromWhere(data, where, targetModel) {
|
||||
for (var i in where) {
|
||||
if (i === 'and') {
|
||||
// Find fixed property values from each subclauses
|
||||
for (var w = 0, n = where[i].length; w < n; w++) {
|
||||
setScopeValuesFromWhere(data, where[i][w]);
|
||||
setScopeValuesFromWhere(data, where[i][w], targetModel);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var prop = targetClass.definition.properties[i];
|
||||
var prop = targetModel.definition.properties[i];
|
||||
if (prop) {
|
||||
var val = where[i];
|
||||
if (typeof val !== 'object' || val instanceof prop.type
|
||||
|
@ -233,9 +256,10 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
function build(data) {
|
||||
data = data || {};
|
||||
// Find all fixed property values for the scope
|
||||
var targetModel = definition.targetModel(this._receiver);
|
||||
var where = (this._scope && this._scope.where) || {};
|
||||
setScopeValuesFromWhere(data, where);
|
||||
return new targetClass(data);
|
||||
setScopeValuesFromWhere(data, where, targetModel);
|
||||
return new targetModel(data);
|
||||
}
|
||||
|
||||
function create(data, cb) {
|
||||
|
@ -256,14 +280,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
|||
if (typeof where === 'function') cb = where, where = {};
|
||||
var scoped = (this._scope && this._scope.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) {
|
||||
if (typeof where === 'function') cb = where, where = {};
|
||||
var scoped = (this._scope && this._scope.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;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
var util = require('util');
|
||||
var extend = util._extend;
|
||||
|
||||
/*!
|
||||
* Module exports
|
||||
*/
|
||||
|
@ -369,7 +371,9 @@ var validators = {
|
|||
|
||||
function getConfigurator(name, opts) {
|
||||
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) {
|
||||
var valid = true, inst = this, wait = 0, async = false;
|
||||
var validations = this.constructor.validations;
|
||||
|
||||
// exit with success when no errors
|
||||
if (!this.constructor._validations) {
|
||||
if (typeof validations !== 'object') {
|
||||
cleanErrors(this);
|
||||
if (callback) {
|
||||
this.trigger('validate', function (validationsDone) {
|
||||
|
@ -431,21 +436,25 @@ Validatable.prototype.isValid = function (callback, data) {
|
|||
var inst = this,
|
||||
asyncFail = false;
|
||||
|
||||
this.constructor._validations.forEach(function (v) {
|
||||
if (v[2] && v[2].async) {
|
||||
async = true;
|
||||
wait += 1;
|
||||
process.nextTick(function () {
|
||||
validationFailed(inst, v, done);
|
||||
});
|
||||
} else {
|
||||
if (validationFailed(inst, v)) {
|
||||
valid = false;
|
||||
var attrs = Object.keys(validations || {});
|
||||
|
||||
attrs.forEach(function(attr) {
|
||||
var attrValidations = validations[attr] || [];
|
||||
attrValidations.forEach(function(v) {
|
||||
if (v.options && v.options.async) {
|
||||
async = true;
|
||||
wait += 1;
|
||||
process.nextTick(function () {
|
||||
validationFailed(inst, attr, v, done);
|
||||
});
|
||||
} else {
|
||||
if (validationFailed(inst, attr, v)) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (!async) {
|
||||
validationsDone.call(inst, function () {
|
||||
if (valid) cleanErrors(inst);
|
||||
|
@ -487,11 +496,9 @@ function cleanErrors(inst) {
|
|||
});
|
||||
}
|
||||
|
||||
function validationFailed(inst, v, cb) {
|
||||
var attr = v[0];
|
||||
var conf = v[1];
|
||||
var opts = v[2] || {};
|
||||
|
||||
function validationFailed(inst, attr, conf, cb) {
|
||||
var opts = conf.options || {};
|
||||
|
||||
if (typeof attr !== 'string') return false;
|
||||
|
||||
// here we should check skip validation conditions (if, unless)
|
||||
|
@ -615,12 +622,12 @@ function blank(v) {
|
|||
}
|
||||
|
||||
function configure(cls, validation, args, opts) {
|
||||
if (!cls._validations) {
|
||||
Object.defineProperty(cls, '_validations', {
|
||||
if (!cls.validations) {
|
||||
Object.defineProperty(cls, 'validations', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: []
|
||||
value: {}
|
||||
});
|
||||
}
|
||||
args = [].slice.call(args);
|
||||
|
@ -634,9 +641,13 @@ function configure(cls, validation, args, opts) {
|
|||
conf.customValidator = args.pop();
|
||||
}
|
||||
conf.validation = validation;
|
||||
args.forEach(function (attr) {
|
||||
cls._validations.push([attr, conf, opts]);
|
||||
});
|
||||
var attr = args[0];
|
||||
if (typeof attr === 'string') {
|
||||
var validation = extend({}, conf);
|
||||
validation.options = opts || {};
|
||||
cls.validations[attr] = cls.validations[attr] || [];
|
||||
cls.validations[attr].push(validation);
|
||||
}
|
||||
}
|
||||
|
||||
function Errors() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-datasource-juggler",
|
||||
"version": "2.7.0",
|
||||
"version": "2.8.0",
|
||||
"description": "LoopBack DataSoure Juggler",
|
||||
"keywords": [
|
||||
"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;
|
||||
|
||||
if (!('getSchema' in global)) {
|
||||
global.getSchema = function () {
|
||||
return new Schema('memory');
|
||||
global.getSchema = function (connector, settings) {
|
||||
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();
|
||||
});
|
||||
|
||||
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) {
|
||||
var ds = new DataSource('memory');
|
||||
var modelBuilder = new ModelBuilder();
|
||||
|
|
|
@ -102,7 +102,7 @@ describe('manipulation', function () {
|
|||
|
||||
Person.validatesPresenceOf('name');
|
||||
Person.create(batch,function (errors, persons) {
|
||||
delete Person._validations;
|
||||
delete Person.validations;
|
||||
should.exist(errors);
|
||||
errors.should.have.lengthOf(batch.length);
|
||||
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) {
|
||||
var beatles = [
|
||||
{
|
||||
|
|
|
@ -55,6 +55,8 @@ describe('ModelDefinition class', function () {
|
|||
});
|
||||
|
||||
User.build();
|
||||
|
||||
var json = User.toJSON();
|
||||
|
||||
User.defineProperty("id", {type: "number", id: true});
|
||||
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.joinedAt.type, Date);
|
||||
assert.equal(User.properties.age.type, Number);
|
||||
|
||||
|
||||
assert.equal(User.properties.id.type, Number);
|
||||
|
||||
json = User.toJSON();
|
||||
assert.deepEqual(json.properties.id, {type: 'Number', id: true});
|
||||
|
||||
done();
|
||||
|
||||
});
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
// This test written in mocha+should.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 Picture, PictureLink;
|
||||
var Person, Address;
|
||||
var Link;
|
||||
|
||||
var getTransientDataSource = function(settings) {
|
||||
return new DataSource('transient', settings, db.modelBuilder);
|
||||
};
|
||||
|
||||
describe('relations', 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) {
|
||||
var id;
|
||||
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 () {
|
||||
it('can be declared with properties', function (done) {
|
||||
Book.hasMany(Chapter, { properties: { type: 'bookType' } });
|
||||
|
@ -915,6 +1091,29 @@ describe('relations', function () {
|
|||
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 = [];
|
||||
it('should create polymorphic relation - author', function (done) {
|
||||
Author.create({ name: 'Author 1' }, function (err, a) {
|
||||
|
@ -1486,9 +1685,10 @@ describe('relations', function () {
|
|||
var Other;
|
||||
|
||||
before(function () {
|
||||
tmp = getTransientDataSource();
|
||||
db = getSchema();
|
||||
Person = db.define('Person', {name: String});
|
||||
Passport = db.define('Passport',
|
||||
Passport = tmp.define('Passport',
|
||||
{name:{type:'string', required: true}},
|
||||
{idInjection: false}
|
||||
);
|
||||
|
@ -1634,9 +1834,10 @@ describe('relations', function () {
|
|||
var address1, address2;
|
||||
|
||||
before(function (done) {
|
||||
tmp = getTransientDataSource({defaultIdType: Number});
|
||||
db = getSchema();
|
||||
Person = db.define('Person', {name: String});
|
||||
Address = db.define('Address', {street: String});
|
||||
Address = tmp.define('Address', {street: String});
|
||||
Address.validatesPresenceOf('street');
|
||||
|
||||
db.automigrate(function () {
|
||||
|
@ -1813,9 +2014,10 @@ describe('relations', function () {
|
|||
|
||||
describe('embedsMany - explicit ids', function () {
|
||||
before(function (done) {
|
||||
tmp = getTransientDataSource();
|
||||
db = getSchema();
|
||||
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');
|
||||
|
||||
db.automigrate(function () {
|
||||
|
@ -1824,13 +2026,13 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('can be declared', function (done) {
|
||||
Person.embedsMany(Address, { options: { autoId: false } });
|
||||
Person.embedsMany(Address);
|
||||
db.automigrate(done);
|
||||
});
|
||||
|
||||
it('should create embedded items on scope', function(done) {
|
||||
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);
|
||||
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, address) {
|
||||
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 () {
|
||||
|
@ -2208,11 +2421,16 @@ describe('relations', function () {
|
|||
|
||||
before(function (done) {
|
||||
db = getSchema();
|
||||
tmp = getTransientDataSource();
|
||||
|
||||
Book = db.define('Book', {name: String});
|
||||
Author = db.define('Author', {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('linkedType');
|
||||
|
||||
|
@ -2226,13 +2444,15 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('can be declared', function (done) {
|
||||
var idType = db.connector.getDefaultIdType();
|
||||
|
||||
Book.embedsMany(Link, { as: 'people',
|
||||
polymorphic: 'linked',
|
||||
scope: { include: 'linked' }
|
||||
});
|
||||
Link.belongsTo('linked', {
|
||||
polymorphic: true, // needs unique auto-id
|
||||
properties: { name: 'name' }, // denormalized
|
||||
polymorphic: { idType: idType }, // native type
|
||||
properties: { name: 'name' }, // denormalized
|
||||
options: { invertProperties: true }
|
||||
});
|
||||
db.automigrate(done);
|
||||
|
@ -2411,7 +2631,7 @@ describe('relations', function () {
|
|||
err.name.should.equal('ValidationError');
|
||||
err.details.codes.jobs.should.eql(['uniqueness']);
|
||||
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);
|
||||
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) {
|
||||
User.destroyAll(function () {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -67,7 +67,7 @@ describe('validations', function () {
|
|||
describe('lifecycle', function () {
|
||||
|
||||
it('should work on create', function (done) {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
User.validatesPresenceOf('name');
|
||||
User.create(function (e, u) {
|
||||
should.exist(e);
|
||||
|
@ -79,7 +79,7 @@ describe('validations', function () {
|
|||
});
|
||||
|
||||
it('should work on update', function (done) {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
User.validatesPresenceOf('name');
|
||||
User.create({name: 'Valid'}, function (e, d) {
|
||||
d.updateAttribute('name', null, function (e) {
|
||||
|
@ -95,7 +95,7 @@ describe('validations', function () {
|
|||
});
|
||||
|
||||
it('should return error code', function (done) {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
User.validatesPresenceOf('name');
|
||||
User.create(function (e, u) {
|
||||
should.exist(e);
|
||||
|
@ -112,7 +112,7 @@ describe('validations', function () {
|
|||
});
|
||||
|
||||
it('should include validation messages in err.message', function(done) {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
User.validatesPresenceOf('name');
|
||||
User.create(function (e, u) {
|
||||
should.exist(e);
|
||||
|
@ -122,7 +122,7 @@ describe('validations', function () {
|
|||
});
|
||||
|
||||
it('should include model name in err.message', function(done) {
|
||||
delete User._validations;
|
||||
delete User.validations;
|
||||
User.validatesPresenceOf('name');
|
||||
User.create(function (e, u) {
|
||||
should.exist(e);
|
||||
|
@ -130,6 +130,14 @@ describe('validations', function () {
|
|||
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