Merge branch 'release/2.5.0' into production
This commit is contained in:
commit
7b00635d82
11
lib/dao.js
11
lib/dao.js
|
@ -338,7 +338,7 @@ DataAccessObject.findByIds = function(ids, cond, cb) {
|
|||
}
|
||||
|
||||
var filter = { where: {} };
|
||||
filter.where[pk] = { inq: ids };
|
||||
filter.where[pk] = { inq: [].concat(ids) };
|
||||
mergeQuery(filter, cond || {});
|
||||
this.find(filter, function(err, results) {
|
||||
cb(err, err ? results : utils.sortObjectsByIds(pk, ids, results));
|
||||
|
@ -1113,6 +1113,15 @@ DataAccessObject.prototype.setAttributes = function setAttributes(data) {
|
|||
Model.emit('set', inst);
|
||||
};
|
||||
|
||||
DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullify) {
|
||||
if (nullify) {
|
||||
this[name] = this.__data[name] = null;
|
||||
} else {
|
||||
delete this[name];
|
||||
delete this.__data[name];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update set of attributes.
|
||||
* Performs validation before updating.
|
||||
|
|
|
@ -42,7 +42,7 @@ function Inclusion() {
|
|||
*/
|
||||
Inclusion.include = function (objects, include, cb) {
|
||||
var self = this;
|
||||
|
||||
|
||||
if (!include || (Array.isArray(include) && include.length === 0) ||
|
||||
(isPlainObject(include) && Object.keys(include).length === 0)) {
|
||||
// The objects are empty
|
||||
|
@ -123,6 +123,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
return callback();
|
||||
}
|
||||
}
|
||||
|
||||
var inst = (obj instanceof self) ? obj : new self(obj);
|
||||
// Calling the relation method on the instance
|
||||
inst[relationName](function (err, result) {
|
||||
|
@ -131,6 +132,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
} else {
|
||||
defineCachedRelations(obj);
|
||||
obj.__cachedRelations[relationName] = result;
|
||||
|
||||
if(obj === inst) {
|
||||
obj.__data[relationName] = result;
|
||||
obj.setStrict(false);
|
||||
|
|
35
lib/model.js
35
lib/model.js
|
@ -12,6 +12,7 @@ var jutil = require('./jutil');
|
|||
var List = require('./list');
|
||||
var Hookable = require('./hooks');
|
||||
var validations = require('./validations.js');
|
||||
var _extend = util._extend;
|
||||
|
||||
// Set up an object for quick lookup
|
||||
var BASE_TYPES = {
|
||||
|
@ -56,7 +57,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
// Convert the data to be plain object to avoid pollutions
|
||||
data = data.toObject(false);
|
||||
}
|
||||
var properties = ctor.definition.properties;
|
||||
var properties = _extend({}, ctor.definition.properties);
|
||||
data = data || {};
|
||||
|
||||
options = options || {};
|
||||
|
@ -130,27 +131,39 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
self.__data[p] = propVal;
|
||||
}
|
||||
} else if (ctor.relations[p]) {
|
||||
if (!properties[p]) {
|
||||
var modelTo = ctor.relations[p].modelTo || ModelBaseClass;
|
||||
var multiple = ctor.relations[p].multiple;
|
||||
var typeName = multiple ? 'Array' : modelTo.modelName;
|
||||
var propType = multiple ? [modelTo] : modelTo;
|
||||
properties[p] = { name: typeName, type: propType };
|
||||
this.setStrict(false);
|
||||
}
|
||||
|
||||
// Relation
|
||||
if (ctor.relations[p].type === 'belongsTo' && propVal != null) {
|
||||
// If the related model is populated
|
||||
self.__data[ctor.relations[p].keyFrom] = propVal[ctor.relations[p].keyTo];
|
||||
} else if (!self.__data[p] && propVal != null) {
|
||||
self.__data[p] = propVal;
|
||||
}
|
||||
self.__cachedRelations[p] = propVal;
|
||||
} else {
|
||||
// Un-managed property
|
||||
if (strict === false) {
|
||||
self[p] = self.__data[p] = propVal;
|
||||
if (strict === false || self.__cachedRelations[p]) {
|
||||
self[p] = self.__data[p] = propVal || self.__cachedRelations[p];
|
||||
} else if (strict === 'throw') {
|
||||
throw new Error('Unknown property: ' + p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
keys = Object.keys(properties);
|
||||
size = keys.length;
|
||||
|
||||
for (k = 0; k < size; k++) {
|
||||
p = keys[k];
|
||||
// var prop
|
||||
propVal = self.__data[p];
|
||||
|
||||
// Set default values
|
||||
|
@ -172,10 +185,11 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
self.__data[p] = def;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle complex types (JSON/Object)
|
||||
var type = properties[p].type;
|
||||
if (! BASE_TYPES[type.name]) {
|
||||
if (!BASE_TYPES[type.name]) {
|
||||
|
||||
if (typeof self.__data[p] !== 'object' && self.__data[p]) {
|
||||
try {
|
||||
self.__data[p] = JSON.parse(self.__data[p] + '');
|
||||
|
@ -183,7 +197,14 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
self.__data[p] = String(self.__data[p]);
|
||||
}
|
||||
}
|
||||
if (type.name === 'Array' || Array.isArray(type)) {
|
||||
|
||||
if (type.prototype instanceof ModelBaseClass) {
|
||||
if (!(self.__data[p] instanceof type)
|
||||
&& typeof self.__data[p] === 'object'
|
||||
&& self.__data[p] !== null ) {
|
||||
self.__data[p] = new type(self.__data[p]);
|
||||
}
|
||||
} else if (type.name === 'Array' || Array.isArray(type)) {
|
||||
if (!(self.__data[p] instanceof List)
|
||||
&& self.__data[p] !== undefined
|
||||
&& self.__data[p] !== null ) {
|
||||
|
|
|
@ -20,6 +20,7 @@ var RelationTypes = {
|
|||
hasOne: 'hasOne',
|
||||
hasAndBelongsToMany: 'hasAndBelongsToMany',
|
||||
referencesMany: 'referencesMany',
|
||||
embedsOne: 'embedsOne',
|
||||
embedsMany: 'embedsMany'
|
||||
};
|
||||
|
||||
|
@ -30,6 +31,7 @@ exports.HasOne = HasOne;
|
|||
exports.HasAndBelongsToMany = HasAndBelongsToMany;
|
||||
exports.BelongsTo = BelongsTo;
|
||||
exports.ReferencesMany = ReferencesMany;
|
||||
exports.EmbedsOne = EmbedsOne;
|
||||
exports.EmbedsMany = EmbedsMany;
|
||||
|
||||
var RelationClasses = {
|
||||
|
@ -39,6 +41,7 @@ var RelationClasses = {
|
|||
hasOne: HasOne,
|
||||
hasAndBelongsToMany: HasAndBelongsToMany,
|
||||
referencesMany: ReferencesMany,
|
||||
embedsOne: EmbedsOne,
|
||||
embedsMany: EmbedsMany
|
||||
};
|
||||
|
||||
|
@ -384,6 +387,24 @@ function HasOne(definition, modelInstance) {
|
|||
|
||||
util.inherits(HasOne, Relation);
|
||||
|
||||
/**
|
||||
* EmbedsOne subclass
|
||||
* @param {RelationDefinition|Object} definition
|
||||
* @param {Object} modelInstance
|
||||
* @returns {EmbedsMany}
|
||||
* @constructor
|
||||
* @class EmbedsOne
|
||||
*/
|
||||
function EmbedsOne(definition, modelInstance) {
|
||||
if (!(this instanceof EmbedsOne)) {
|
||||
return new EmbedsMany(definition, modelInstance);
|
||||
}
|
||||
assert(definition.type === RelationTypes.embedsOne);
|
||||
Relation.apply(this, arguments);
|
||||
}
|
||||
|
||||
util.inherits(EmbedsOne, Relation);
|
||||
|
||||
/**
|
||||
* EmbedsMany subclass
|
||||
* @param {RelationDefinition|Object} definition
|
||||
|
@ -954,7 +975,7 @@ HasManyThrough.prototype.exists = function (acInst, done) {
|
|||
var keys = throughKeys(definition);
|
||||
var fk1 = keys[0];
|
||||
var fk2 = keys[1];
|
||||
|
||||
|
||||
query[fk1] = this.modelInstance[pk1];
|
||||
query[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst;
|
||||
|
||||
|
@ -1084,9 +1105,11 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
|||
get: function() {
|
||||
var relation = new BelongsTo(definition, this);
|
||||
var relationMethod = relation.related.bind(relation);
|
||||
relationMethod.create = relation.create.bind(relation);
|
||||
relationMethod.build = relation.build.bind(relation);
|
||||
if (definition.modelTo) {
|
||||
relationMethod.update = relation.update.bind(relation);
|
||||
relationMethod.destroy = relation.destroy.bind(relation);
|
||||
if (!polymorphic) {
|
||||
relationMethod.create = relation.create.bind(relation);
|
||||
relationMethod.build = relation.build.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
}
|
||||
return relationMethod;
|
||||
|
@ -1139,6 +1162,36 @@ BelongsTo.prototype.build = function(targetModelData) {
|
|||
return new modelTo(targetModelData);
|
||||
};
|
||||
|
||||
BelongsTo.prototype.update = function (targetModelData, cb) {
|
||||
var definition = this.definition;
|
||||
this.fetch(function(err, inst) {
|
||||
if (inst instanceof ModelBaseClass) {
|
||||
inst.updateAttributes(targetModelData, cb);
|
||||
} else {
|
||||
cb(new Error('BelongsTo relation ' + definition.name
|
||||
+ ' is empty'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
BelongsTo.prototype.destroy = function (cb) {
|
||||
var modelTo = this.definition.modelTo;
|
||||
var modelInstance = this.modelInstance;
|
||||
var fk = this.definition.keyFrom;
|
||||
this.fetch(function(err, targetModel) {
|
||||
if (targetModel instanceof ModelBaseClass) {
|
||||
modelInstance[fk] = null;
|
||||
modelInstance.save(function(err, targetModel) {
|
||||
if (cb && err) return cb && cb(err);
|
||||
cb && cb(err, targetModel);
|
||||
});
|
||||
} else {
|
||||
cb(new Error('BelongsTo relation ' + definition.name
|
||||
+ ' is empty'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the method for the belongsTo relation itself
|
||||
* It will support one of the following styles:
|
||||
|
@ -1175,7 +1228,6 @@ BelongsTo.prototype.related = function (refresh, params) {
|
|||
cachedValue = self.getCache();
|
||||
}
|
||||
if (params instanceof ModelBaseClass) { // acts as setter
|
||||
modelTo = params.constructor;
|
||||
modelInstance[fk] = params[pk];
|
||||
|
||||
if (discriminator) {
|
||||
|
@ -1204,7 +1256,8 @@ BelongsTo.prototype.related = function (refresh, params) {
|
|||
var query = {where: {}};
|
||||
query.where[pk] = modelInstance[fk];
|
||||
|
||||
if (query.where[pk] === undefined) {
|
||||
if (query.where[pk] === undefined
|
||||
|| query.where[pk] === null) {
|
||||
// Foreign key is undefined
|
||||
return process.nextTick(cb);
|
||||
}
|
||||
|
@ -1353,6 +1406,8 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
|||
var relationMethod = relation.related.bind(relation)
|
||||
relationMethod.create = relation.create.bind(relation);
|
||||
relationMethod.build = relation.build.bind(relation);
|
||||
relationMethod.update = relation.update.bind(relation);
|
||||
relationMethod.destroy = relation.destroy.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
return relationMethod;
|
||||
}
|
||||
|
@ -1416,6 +1471,29 @@ HasOne.prototype.create = function (targetModelData, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
HasOne.prototype.update = function (targetModelData, cb) {
|
||||
var definition = this.definition;
|
||||
this.fetch(function(err, targetModel) {
|
||||
if (targetModel instanceof ModelBaseClass) {
|
||||
targetModel.updateAttributes(targetModelData, cb);
|
||||
} else {
|
||||
cb(new Error('HasOne relation ' + definition.name
|
||||
+ ' is empty'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
HasOne.prototype.destroy = function (cb) {
|
||||
this.fetch(function(err, targetModel) {
|
||||
if (targetModel instanceof ModelBaseClass) {
|
||||
targetModel.destroy(cb);
|
||||
} else {
|
||||
cb(new Error('HasOne relation ' + definition.name
|
||||
+ ' is empty'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a target model instance
|
||||
* @param {Object} targetModelData The target model data
|
||||
|
@ -1539,6 +1617,198 @@ HasOne.prototype.related = function (refresh, params) {
|
|||
}
|
||||
};
|
||||
|
||||
RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
|
||||
params = params || {};
|
||||
modelTo = lookupModelTo(modelFrom, modelTo, params);
|
||||
|
||||
var thisClassName = modelFrom.modelName;
|
||||
var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'Item');
|
||||
var propertyName = params.property || i8n.camelize(modelTo.modelName, true);
|
||||
var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
||||
|
||||
if (relationName === propertyName) {
|
||||
propertyName = '_' + propertyName;
|
||||
debug('EmbedsOne property cannot be equal to relation name: ' +
|
||||
'forcing property %s for relation %s', propertyName, relationName);
|
||||
}
|
||||
|
||||
var definition = modelFrom.relations[relationName] = new RelationDefinition({
|
||||
name: relationName,
|
||||
type: RelationTypes.embedsOne,
|
||||
modelFrom: modelFrom,
|
||||
keyFrom: propertyName,
|
||||
keyTo: idName,
|
||||
modelTo: modelTo,
|
||||
multiple: false,
|
||||
properties: params.properties,
|
||||
scope: params.scope,
|
||||
options: params.options,
|
||||
embed: true
|
||||
});
|
||||
|
||||
var opts = { type: modelTo };
|
||||
|
||||
if (params.default === true) {
|
||||
opts.default = function() { return new modelTo(); };
|
||||
} else if (typeof params.default === 'object') {
|
||||
opts.default = (function(def) {
|
||||
return function() {
|
||||
return new modelTo(def);
|
||||
};
|
||||
}(params.default));
|
||||
}
|
||||
|
||||
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts);
|
||||
|
||||
// validate the embedded instance
|
||||
if (definition.options.validate !== false) {
|
||||
modelFrom.validate(relationName, function(err) {
|
||||
var inst = this[propertyName];
|
||||
if (inst instanceof modelTo) {
|
||||
if (!inst.isValid()) {
|
||||
var first = Object.keys(inst.errors)[0];
|
||||
var msg = 'is invalid: `' + first + '` ' + inst.errors[first];
|
||||
this.errors.add(relationName, msg, 'invalid');
|
||||
err(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Define a property for the scope so that we have 'this' for the scoped methods
|
||||
Object.defineProperty(modelFrom.prototype, relationName, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
var relation = new EmbedsOne(definition, this);
|
||||
var relationMethod = relation.related.bind(relation)
|
||||
relationMethod.create = relation.create.bind(relation);
|
||||
relationMethod.build = relation.build.bind(relation);
|
||||
relationMethod.update = relation.update.bind(relation);
|
||||
relationMethod.destroy = relation.destroy.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
return relationMethod;
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME: [rfeng] Wrap the property into a function for remoting
|
||||
// so that it can be accessed as /api/<model>/<id>/<embedsOneRelationName>
|
||||
// For example, /api/orders/1/customer
|
||||
var fn = function() {
|
||||
var f = this[relationName];
|
||||
f.apply(this, arguments);
|
||||
};
|
||||
modelFrom.prototype['__get__' + relationName] = fn;
|
||||
|
||||
return definition;
|
||||
};
|
||||
|
||||
EmbedsOne.prototype.related = function (refresh, params) {
|
||||
var modelTo = this.definition.modelTo;
|
||||
var modelInstance = this.modelInstance;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
|
||||
if (arguments.length === 1) {
|
||||
params = refresh;
|
||||
refresh = false;
|
||||
} else if (arguments.length > 2) {
|
||||
throw new Error('Method can\'t be called with more than two arguments');
|
||||
}
|
||||
|
||||
if (params instanceof ModelBaseClass) { // acts as setter
|
||||
if (params instanceof modelTo) {
|
||||
this.definition.applyProperties(modelInstance, params);
|
||||
modelInstance.setAttribute(propertyName, params);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var embeddedInstance = modelInstance[propertyName];
|
||||
if (typeof params === 'function') { // acts as async getter
|
||||
var cb = params;
|
||||
process.nextTick(function() {
|
||||
cb(null, embeddedInstance);
|
||||
});
|
||||
} else if (params === undefined) { // acts as sync getter
|
||||
return embeddedInstance;
|
||||
}
|
||||
};
|
||||
|
||||
EmbedsOne.prototype.create = function (targetModelData, cb) {
|
||||
var modelTo = this.definition.modelTo;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
var modelInstance = this.modelInstance;
|
||||
|
||||
if (typeof targetModelData === 'function' && !cb) {
|
||||
cb = targetModelData;
|
||||
targetModelData = {};
|
||||
}
|
||||
|
||||
targetModelData = targetModelData || {};
|
||||
|
||||
var inst = this.build(targetModelData);
|
||||
|
||||
var err = inst.isValid() ? null : new ValidationError(inst);
|
||||
|
||||
if (err) {
|
||||
return process.nextTick(function() {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
modelInstance.updateAttribute(propertyName,
|
||||
inst, function(err) {
|
||||
cb(err, err ? null : inst);
|
||||
});
|
||||
};
|
||||
|
||||
EmbedsOne.prototype.build = function (targetModelData) {
|
||||
var modelTo = this.definition.modelTo;
|
||||
var modelInstance = this.modelInstance;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
|
||||
targetModelData = targetModelData || {};
|
||||
|
||||
this.definition.applyProperties(modelInstance, targetModelData);
|
||||
|
||||
var embeddedInstance = new modelTo(targetModelData);
|
||||
modelInstance[propertyName] = embeddedInstance;
|
||||
|
||||
return embeddedInstance;
|
||||
};
|
||||
|
||||
EmbedsOne.prototype.update = function (targetModelData, cb) {
|
||||
var modelTo = this.definition.modelTo;
|
||||
var modelInstance = this.modelInstance;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
|
||||
var isInst = targetModelData instanceof ModelBaseClass;
|
||||
var data = isInst ? targetModelData.toObject() : targetModelData;
|
||||
|
||||
var embeddedInstance = modelInstance[propertyName];
|
||||
if (embeddedInstance instanceof modelTo) {
|
||||
embeddedInstance.setAttributes(data);
|
||||
if (typeof cb === 'function') {
|
||||
modelInstance.save(function(err, inst) {
|
||||
cb(err, inst ? inst[propertyName] : embeddedInstance);
|
||||
});
|
||||
}
|
||||
} else if (!embeddedInstance && cb) {
|
||||
this.create(data, db);
|
||||
} else if (!embeddedInstance) {
|
||||
this.build(data);
|
||||
}
|
||||
};
|
||||
|
||||
EmbedsOne.prototype.destroy = function (cb) {
|
||||
var modelInstance = this.modelInstance;
|
||||
var propertyName = this.definition.keyFrom;
|
||||
modelInstance.unsetAttribute(propertyName, true);
|
||||
modelInstance.save(function (err, result) {
|
||||
cb && cb(err, result);
|
||||
});
|
||||
};
|
||||
|
||||
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) {
|
||||
params = params || {};
|
||||
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
|
||||
|
@ -1590,7 +1860,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
|||
}
|
||||
|
||||
// validate all embedded items
|
||||
if (definition.options.validate) {
|
||||
if (definition.options.validate !== false) {
|
||||
modelFrom.validate(propertyName, function(err) {
|
||||
var self = this;
|
||||
var embeddedList = this[propertyName] || [];
|
||||
|
@ -1602,7 +1872,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
|||
var id = item[idName] || '(blank)';
|
||||
var first = Object.keys(item.errors)[0];
|
||||
var msg = 'contains invalid item: `' + id + '`';
|
||||
msg += ' (' + first + ' ' + item.errors[first] + ')';
|
||||
msg += ' (`' + first + '` ' + item.errors[first] + ')';
|
||||
self.errors.add(propertyName, msg, 'invalid');
|
||||
}
|
||||
} else {
|
||||
|
@ -1716,15 +1986,11 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
|
|||
var modelInstance = this.modelInstance;
|
||||
|
||||
var actualCond = {};
|
||||
var actualRefresh = false;
|
||||
if (arguments.length === 3) {
|
||||
cb = condOrRefresh;
|
||||
} else if (arguments.length === 4) {
|
||||
if (typeof condOrRefresh === 'boolean') {
|
||||
actualRefresh = condOrRefresh;
|
||||
} else {
|
||||
if (typeof condOrRefresh === 'object') {
|
||||
actualCond = condOrRefresh;
|
||||
actualRefresh = true;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Method can be only called with one or two arguments');
|
||||
|
@ -1923,7 +2189,7 @@ EmbedsMany.prototype.build = function(targetModelData) {
|
|||
}
|
||||
}
|
||||
|
||||
this.definition.applyProperties(this.modelInstance, targetModelData);
|
||||
this.definition.applyProperties(modelInstance, targetModelData);
|
||||
|
||||
var inst = new modelTo(targetModelData);
|
||||
|
||||
|
|
|
@ -158,14 +158,18 @@ RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params
|
|||
return RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
||||
};
|
||||
|
||||
RelationMixin.hasOne = function hasMany(modelTo, params) {
|
||||
RelationMixin.hasOne = function hasOne(modelTo, params) {
|
||||
return RelationDefinition.hasOne(this, modelTo, params);
|
||||
};
|
||||
|
||||
RelationMixin.referencesMany = function hasMany(modelTo, params) {
|
||||
RelationMixin.referencesMany = function referencesMany(modelTo, params) {
|
||||
return RelationDefinition.referencesMany(this, modelTo, params);
|
||||
};
|
||||
|
||||
RelationMixin.embedsMany = function hasMany(modelTo, params) {
|
||||
RelationMixin.embedsOne = function embedsOne(modelTo, params) {
|
||||
return RelationDefinition.embedsOne(this, modelTo, params);
|
||||
};
|
||||
|
||||
RelationMixin.embedsMany = function embedsMany(modelTo, params) {
|
||||
return RelationDefinition.embedsMany(this, modelTo, params);
|
||||
};
|
||||
|
|
|
@ -210,12 +210,12 @@ function isPlainObject(obj) {
|
|||
|
||||
function sortObjectsByIds(idName, ids, objects, strict) {
|
||||
ids = ids.map(function(id) {
|
||||
return (typeof id === 'object') ? id.toString() : id;
|
||||
return (typeof id === 'object') ? String(id) : id;
|
||||
});
|
||||
|
||||
var indexOf = function(x) {
|
||||
var isObj = (typeof x[idName] === 'object'); // ObjectID
|
||||
var id = isObj ? x[idName].toString() : x[idName];
|
||||
var id = isObj ? String(x[idName]) : x[idName];
|
||||
return ids.indexOf(id);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-datasource-juggler",
|
||||
"version": "2.4.2",
|
||||
"version": "2.5.0",
|
||||
"description": "LoopBack DataSoure Juggler",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
|
|
@ -189,7 +189,7 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
describe('hasMany through', function () {
|
||||
var Physician, Patient, Appointment;
|
||||
var Physician, Patient, Appointment, Address;
|
||||
|
||||
before(function (done) {
|
||||
db = getSchema();
|
||||
|
@ -199,13 +199,15 @@ describe('relations', function () {
|
|||
default: function () {
|
||||
return new Date();
|
||||
}}});
|
||||
|
||||
Address = db.define('Address', {name: String});
|
||||
|
||||
Physician.hasMany(Patient, {through: Appointment});
|
||||
Patient.hasMany(Physician, {through: Appointment});
|
||||
Patient.belongsTo(Address);
|
||||
Appointment.belongsTo(Patient);
|
||||
Appointment.belongsTo(Physician);
|
||||
|
||||
db.automigrate(['Physician', 'Patient', 'Appointment'], function (err) {
|
||||
db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
@ -277,11 +279,10 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('should allow to use include syntax on related data', function (done) {
|
||||
var Address = db.define('Address', {name: String});
|
||||
Patient.belongsTo(Address);
|
||||
Physician.create(function (err, physician) {
|
||||
physician.patients.create({name: 'a'}, function (err, patient) {
|
||||
Address.create({name: 'z'}, function (err, address) {
|
||||
should.not.exist(err);
|
||||
patient.address(address);
|
||||
patient.save(function() {
|
||||
verify(physician, address.id);
|
||||
|
@ -353,6 +354,7 @@ describe('relations', function () {
|
|||
var id;
|
||||
Physician.create(function (err, physician) {
|
||||
physician.patients.create({name: 'a'}, function (err, ch) {
|
||||
should.not.exist(err);
|
||||
id = ch.id;
|
||||
physician.patients.create({name: 'z'}, function () {
|
||||
physician.patients.create({name: 'c'}, function () {
|
||||
|
@ -1110,6 +1112,8 @@ describe('relations', function () {
|
|||
|
||||
describe('belongsTo', function () {
|
||||
var List, Item, Fear, Mind;
|
||||
|
||||
var listId, itemId;
|
||||
|
||||
it('can be declared in different ways', function () {
|
||||
List = db.define('List', {name: String});
|
||||
|
@ -1132,15 +1136,18 @@ describe('relations', function () {
|
|||
it('can be used to query data', function (done) {
|
||||
List.hasMany('todos', {model: Item});
|
||||
db.automigrate(function () {
|
||||
List.create(function (e, list) {
|
||||
List.create({name: 'List 1'}, function (e, list) {
|
||||
listId = list.id;
|
||||
should.not.exist(e);
|
||||
should.exist(list);
|
||||
list.todos.create(function (err, todo) {
|
||||
list.todos.create({name: 'Item 1'},function (err, todo) {
|
||||
itemId = todo.id;
|
||||
todo.list(function (e, l) {
|
||||
should.not.exist(e);
|
||||
should.exist(l);
|
||||
l.should.be.an.instanceOf(List);
|
||||
todo.list().id.should.equal(l.id);
|
||||
todo.list().name.should.equal('List 1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -1162,6 +1169,55 @@ describe('relations', function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update related item on scope', function(done) {
|
||||
Item.findById(itemId, function (e, todo) {
|
||||
todo.list.update({name: 'List A'}, function(err, list) {
|
||||
should.not.exist(err);
|
||||
should.exist(list);
|
||||
list.name.should.equal('List A');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get related item on scope', function(done) {
|
||||
Item.findById(itemId, function (e, todo) {
|
||||
todo.list(function(err, list) {
|
||||
should.not.exist(err);
|
||||
should.exist(list);
|
||||
list.name.should.equal('List A');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy related item on scope', function(done) {
|
||||
Item.findById(itemId, function (e, todo) {
|
||||
todo.list.destroy(function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get related item on scope - verify', function(done) {
|
||||
Item.findById(itemId, function (e, todo) {
|
||||
todo.list(function(err, list) {
|
||||
should.not.exist(err);
|
||||
should.not.exist(list);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have deleted related item', function(done) {
|
||||
List.findById(listId, function (e, list) {
|
||||
should.not.exist(e);
|
||||
should.exist(list);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -1169,7 +1225,7 @@ describe('relations', function () {
|
|||
var Person, Passport;
|
||||
|
||||
it('can be declared with scope and properties', function (done) {
|
||||
Person = db.define('Person', {name: String, age: Number});
|
||||
Person = db.define('Person', {name: String, age: Number, passportNotes: String});
|
||||
Passport = db.define('Passport', {name: String, notes: String});
|
||||
Passport.belongsTo(Person, {
|
||||
properties: { notes: 'passportNotes' },
|
||||
|
@ -1206,6 +1262,7 @@ describe('relations', function () {
|
|||
|
||||
describe('hasOne', function () {
|
||||
var Supplier, Account;
|
||||
var supplierId, accountId;
|
||||
|
||||
before(function () {
|
||||
db = getSchema();
|
||||
|
@ -1220,13 +1277,14 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('can be used to query data', function (done) {
|
||||
// Supplier.hasOne(Account);
|
||||
db.automigrate(function () {
|
||||
Supplier.create({name: 'Supplier 1'}, function (e, supplier) {
|
||||
supplierId = supplier.id;
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
supplier.account.create({accountNo: 'a01'}, function (err, account) {
|
||||
supplier.account(function (e, act) {
|
||||
accountId = act.id;
|
||||
should.not.exist(e);
|
||||
should.exist(act);
|
||||
act.should.be.an.instanceOf(Account);
|
||||
|
@ -1242,6 +1300,63 @@ describe('relations', function () {
|
|||
it('should set targetClass on scope property', function() {
|
||||
should.equal(Supplier.prototype.account._targetClass, 'Account');
|
||||
});
|
||||
|
||||
it('should update the related item on scope', function(done) {
|
||||
Supplier.findById(supplierId, function(e, supplier) {
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
supplier.account.update({supplierName: 'Supplier A'}, function(err, act) {
|
||||
should.not.exist(e);
|
||||
act.supplierName.should.equal('Supplier A');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the related item on scope', function(done) {
|
||||
Supplier.findById(supplierId, function(e, supplier) {
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
supplier.account(function(err, act) {
|
||||
should.not.exist(e);
|
||||
should.exist(act);
|
||||
act.supplierName.should.equal('Supplier A');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy the related item on scope', function(done) {
|
||||
Supplier.findById(supplierId, function(e, supplier) {
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
supplier.account.destroy(function(err) {
|
||||
should.not.exist(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the related item on scope - verify', function(done) {
|
||||
Supplier.findById(supplierId, function(e, supplier) {
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
supplier.account(function(err, act) {
|
||||
should.not.exist(e);
|
||||
should.not.exist(act);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have deleted related item', function(done) {
|
||||
Supplier.findById(supplierId, function (e, supplier) {
|
||||
should.not.exist(e);
|
||||
should.exist(supplier);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('hasAndBelongsToMany', function () {
|
||||
|
@ -1320,6 +1435,155 @@ describe('relations', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('embedsOne', function () {
|
||||
|
||||
var person;
|
||||
var Other;
|
||||
|
||||
before(function () {
|
||||
db = getSchema();
|
||||
Person = db.define('Person', {name: String});
|
||||
Passport = db.define('Passport',
|
||||
{name:{type:'string', required: true}},
|
||||
{idInjection: false}
|
||||
);
|
||||
Other = db.define('Other', {name: String});
|
||||
});
|
||||
|
||||
it('can be declared using embedsOne method', function (done) {
|
||||
Person.embedsOne(Passport, {
|
||||
default: {name: 'Anonymous'} // a bit contrived
|
||||
});
|
||||
db.automigrate(done);
|
||||
});
|
||||
|
||||
it('should have setup a property and accessor', function() {
|
||||
var p = new Person();
|
||||
p.passport.should.be.an.object; // because of default
|
||||
p.passportItem.should.be.a.function;
|
||||
p.passportItem.create.should.be.a.function;
|
||||
p.passportItem.build.should.be.a.function;
|
||||
p.passportItem.destroy.should.be.a.function;
|
||||
});
|
||||
|
||||
it('should return an instance with default values', function() {
|
||||
var p = new Person();
|
||||
p.passport.toObject().should.eql({name: 'Anonymous'});
|
||||
p.passportItem().should.equal(p.passport);
|
||||
p.passportItem(function(err, passport) {
|
||||
should.not.exist(err);
|
||||
passport.should.equal(p.passport);
|
||||
});
|
||||
});
|
||||
|
||||
it('should embed a model instance', function() {
|
||||
var p = new Person();
|
||||
p.passportItem(new Passport({name: 'Fred'}));
|
||||
p.passport.toObject().should.eql({name: 'Fred'});
|
||||
p.passport.should.be.an.instanceOf(Passport);
|
||||
});
|
||||
|
||||
it('should not embed an invalid model type', function() {
|
||||
var p = new Person();
|
||||
p.passportItem(new Other());
|
||||
p.passport.toObject().should.eql({name: 'Anonymous'});
|
||||
p.passport.should.be.an.instanceOf(Passport);
|
||||
});
|
||||
|
||||
var personId;
|
||||
it('should create an embedded item on scope', function(done) {
|
||||
Person.create({name: 'Fred'}, function(err, p) {
|
||||
should.not.exist(err);
|
||||
personId = p.id;
|
||||
p.passportItem.create({name: 'Fredric'}, function(err, passport) {
|
||||
should.not.exist(err);
|
||||
p.passport.toObject().should.eql({name: 'Fredric'});
|
||||
p.passport.should.be.an.instanceOf(Passport);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get an embedded item on scope', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
should.not.exist(err);
|
||||
var passport = p.passportItem();
|
||||
passport.toObject().should.eql({name: 'Fredric'});
|
||||
passport.should.be.an.instanceOf(Passport);
|
||||
passport.should.equal(p.passport);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate an embedded item on scope - on creation', function(done) {
|
||||
var p = new Person({name: 'Fred'});
|
||||
p.passportItem.create({}, function(err, passport) {
|
||||
should.exist(err);
|
||||
err.name.should.equal('ValidationError');
|
||||
var msg = 'The `Passport` instance is not valid.';
|
||||
msg += ' Details: `name` can\'t be blank.';
|
||||
err.message.should.equal(msg);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate an embedded item on scope - on update', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
var passport = p.passportItem();
|
||||
passport.name = null;
|
||||
p.save(function(err) {
|
||||
should.exist(err);
|
||||
err.name.should.equal('ValidationError');
|
||||
var msg = 'The `Person` instance is not valid.';
|
||||
msg += ' Details: `passportItem` is invalid: `name` can\'t be blank.';
|
||||
err.message.should.equal(msg);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update an embedded item on scope', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
p.passportItem.update({name: 'Freddy'}, function(err, passport) {
|
||||
should.not.exist(err);
|
||||
var passport = p.passportItem();
|
||||
passport.toObject().should.eql({name: 'Freddy'});
|
||||
passport.should.be.an.instanceOf(Passport);
|
||||
passport.should.equal(p.passport);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get an embedded item on scope - verify', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
should.not.exist(err);
|
||||
var passport = p.passportItem();
|
||||
passport.toObject().should.eql({name: 'Freddy'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy an embedded item on scope', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
p.passportItem.destroy(function(err) {
|
||||
should.not.exist(err);
|
||||
should.equal(p.passport, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get an embedded item on scope - verify', function(done) {
|
||||
Person.findById(personId, function(err, p) {
|
||||
should.not.exist(err);
|
||||
should.equal(p.passport, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('embedsMany', function () {
|
||||
|
||||
var address1, address2;
|
||||
|
@ -1515,7 +1779,7 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('can be declared', function (done) {
|
||||
Person.embedsMany(Address, { options: { autoId: false, validate: true } });
|
||||
Person.embedsMany(Address, { options: { autoId: false } });
|
||||
db.automigrate(done);
|
||||
});
|
||||
|
||||
|
@ -1591,7 +1855,7 @@ describe('relations', function () {
|
|||
Person.create({ name: 'Wilma', addresses: addresses }, function(err, p) {
|
||||
err.name.should.equal('ValidationError');
|
||||
var expected = 'The `Person` instance is not valid. ';
|
||||
expected += 'Details: `addresses` contains invalid item: `work` (street can\'t be blank).';
|
||||
expected += 'Details: `addresses` contains invalid item: `work` (`street` can\'t be blank).';
|
||||
err.message.should.equal(expected);
|
||||
done();
|
||||
});
|
||||
|
@ -1644,7 +1908,7 @@ describe('relations', function () {
|
|||
db = getSchema();
|
||||
Category = db.define('Category', {name: String});
|
||||
Product = db.define('Product', {name: String});
|
||||
Link = db.define('Link', {name: String});
|
||||
Link = db.define('Link', {name: String, notes: String});
|
||||
});
|
||||
|
||||
it('can be declared', function (done) {
|
||||
|
@ -1878,7 +2142,7 @@ describe('relations', function () {
|
|||
Author = db.define('Author', {name: String});
|
||||
Reader = db.define('Reader', {name: String});
|
||||
|
||||
Link = db.define('Link'); // generic model
|
||||
Link = db.define('Link', {name: String, notes: String}); // generic model
|
||||
Link.validatesPresenceOf('linkedId');
|
||||
Link.validatesPresenceOf('linkedType');
|
||||
|
||||
|
|
Loading…
Reference in New Issue