Merge branch 'release/2.4.0' into production
This commit is contained in:
commit
50007e14ec
27
lib/dao.js
27
lib/dao.js
|
@ -341,31 +341,10 @@ DataAccessObject.findByIds = function(ids, cond, cb) {
|
||||||
filter.where[pk] = { inq: ids };
|
filter.where[pk] = { inq: ids };
|
||||||
mergeQuery(filter, cond || {});
|
mergeQuery(filter, cond || {});
|
||||||
this.find(filter, function(err, results) {
|
this.find(filter, function(err, results) {
|
||||||
cb(err, err ? results : this.sortByIds(ids, results));
|
cb(err, err ? results : utils.sortObjectsByIds(pk, ids, results));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
DataAccessObject.sortByIds = function(ids, results) {
|
|
||||||
var pk = this.dataSource.idName(this.modelName) || 'id';
|
|
||||||
ids = ids.map(function(id) {
|
|
||||||
return (typeof id === 'object') ? id.toString() : id;
|
|
||||||
});
|
|
||||||
|
|
||||||
results.sort(function(x, y) {
|
|
||||||
var idA = (typeof x[pk] === 'object') ? x[pk].toString() : x[pk];
|
|
||||||
var idB = (typeof y[pk] === 'object') ? y[pk].toString() : y[pk];
|
|
||||||
var a = ids.indexOf(idA);
|
|
||||||
var b = ids.indexOf(idB);
|
|
||||||
if (a === -1 || b === -1) return 1; // last
|
|
||||||
if (a !== b) {
|
|
||||||
if (a > b) return 1;
|
|
||||||
if (a < b) return -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
function convertNullToNotFoundError(ctx, cb) {
|
function convertNullToNotFoundError(ctx, cb) {
|
||||||
if (ctx.result !== null) return cb();
|
if (ctx.result !== null) return cb();
|
||||||
|
|
||||||
|
@ -1175,6 +1154,10 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
|
||||||
// Convert the properties by type
|
// Convert the properties by type
|
||||||
inst[key] = data[key];
|
inst[key] = data[key];
|
||||||
typedData[key] = inst[key];
|
typedData[key] = inst[key];
|
||||||
|
if (typeof typedData[key] === 'object'
|
||||||
|
&& typeof typedData[key].toObject === 'function') {
|
||||||
|
typedData[key] = typedData[key].toObject();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inst._adapter().updateAttributes(model, getIdValue(inst.constructor, inst),
|
inst._adapter().updateAttributes(model, getIdValue(inst.constructor, inst),
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
var ModelBuilder = require('./model-builder.js').ModelBuilder;
|
var ModelBuilder = require('./model-builder.js').ModelBuilder;
|
||||||
var ModelDefinition = require('./model-definition.js');
|
var ModelDefinition = require('./model-definition.js');
|
||||||
|
var RelationDefinition = require('./relation-definition.js');
|
||||||
var jutil = require('./jutil');
|
var jutil = require('./jutil');
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
var ModelBaseClass = require('./model.js');
|
var ModelBaseClass = require('./model.js');
|
||||||
|
@ -364,7 +365,7 @@ function isModelClass(cls) {
|
||||||
return cls.prototype instanceof ModelBaseClass;
|
return cls.prototype instanceof ModelBaseClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany', 'hasOne'];
|
DataSource.relationTypes = Object.keys(RelationDefinition.RelationTypes);
|
||||||
|
|
||||||
function isModelDataSourceAttached(model) {
|
function isModelDataSourceAttached(model) {
|
||||||
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);
|
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);
|
||||||
|
|
|
@ -501,9 +501,9 @@ ModelBuilder.prototype.defineValueType = function(type, aliases) {
|
||||||
*
|
*
|
||||||
* @param {String} model Name of model
|
* @param {String} model Name of model
|
||||||
* @options {Object} properties JSON object specifying properties. Each property is a key whos value is
|
* @options {Object} properties JSON object specifying properties. Each property is a key whos value is
|
||||||
* either the [type](http://docs.strongloop.com/display/DOC/LDL+data+types) or `propertyName: {options}`
|
* either the [type](http://docs.strongloop.com/display/LB/LoopBack+types) or `propertyName: {options}`
|
||||||
* where the options are described below.
|
* where the options are described below.
|
||||||
* @property {String} type Datatype of property: Must be an [LDL type](http://docs.strongloop.com/display/DOC/LDL+data+types).
|
* @property {String} type Datatype of property: Must be an [LDL type](http://docs.strongloop.com/display/LB/LoopBack+types).
|
||||||
* @property {Boolean} index True if the property is an index; false otherwise.
|
* @property {Boolean} index True if the property is an index; false otherwise.
|
||||||
*/
|
*/
|
||||||
ModelBuilder.prototype.extendModel = function (model, props) {
|
ModelBuilder.prototype.extendModel = function (model, props) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ var mergeQuery = require('./scope.js').mergeQuery;
|
||||||
var ModelBaseClass = require('./model.js');
|
var ModelBaseClass = require('./model.js');
|
||||||
var applyFilter = require('./connectors/memory').applyFilter;
|
var applyFilter = require('./connectors/memory').applyFilter;
|
||||||
var ValidationError = require('./validations.js').ValidationError;
|
var ValidationError = require('./validations.js').ValidationError;
|
||||||
|
var debug = require('debug')('loopback:relations');
|
||||||
|
|
||||||
exports.Relation = Relation;
|
exports.Relation = Relation;
|
||||||
exports.RelationDefinition = RelationDefinition;
|
exports.RelationDefinition = RelationDefinition;
|
||||||
|
@ -63,12 +64,15 @@ function extendScopeMethods(definition, scopeMethods, ext) {
|
||||||
if (typeof ext === 'function') {
|
if (typeof ext === 'function') {
|
||||||
customMethods = ext.call(definition, scopeMethods, relationClass);
|
customMethods = ext.call(definition, scopeMethods, relationClass);
|
||||||
} else if (typeof ext === 'object') {
|
} else if (typeof ext === 'object') {
|
||||||
for (var key in ext) {
|
function createFunc(definition, relationMethod) {
|
||||||
var relationMethod = ext[key];
|
return function() {
|
||||||
var method = scopeMethods[key] = function () {
|
|
||||||
var relation = new relationClass(definition, this);
|
var relation = new relationClass(definition, this);
|
||||||
return relationMethod.apply(relation, arguments);
|
return relationMethod.apply(relation, arguments);
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
for (var key in ext) {
|
||||||
|
var relationMethod = ext[key];
|
||||||
|
var method = scopeMethods[key] = createFunc(definition, relationMethod);
|
||||||
if (relationMethod.shared) {
|
if (relationMethod.shared) {
|
||||||
sharedMethod(definition, key, method, relationMethod);
|
sharedMethod(definition, key, method, relationMethod);
|
||||||
}
|
}
|
||||||
|
@ -89,7 +93,6 @@ function RelationDefinition(definition) {
|
||||||
}
|
}
|
||||||
definition = definition || {};
|
definition = definition || {};
|
||||||
this.name = definition.name;
|
this.name = definition.name;
|
||||||
this.accessor = definition.accessor || this.name;
|
|
||||||
assert(this.name, 'Relation name is missing');
|
assert(this.name, 'Relation name is missing');
|
||||||
this.type = normalizeType(definition.type);
|
this.type = normalizeType(definition.type);
|
||||||
assert(this.type, 'Invalid relation type: ' + definition.type);
|
assert(this.type, 'Invalid relation type: ' + definition.type);
|
||||||
|
@ -98,8 +101,8 @@ function RelationDefinition(definition) {
|
||||||
this.keyFrom = definition.keyFrom;
|
this.keyFrom = definition.keyFrom;
|
||||||
this.modelTo = definition.modelTo;
|
this.modelTo = definition.modelTo;
|
||||||
this.keyTo = definition.keyTo;
|
this.keyTo = definition.keyTo;
|
||||||
this.discriminator = definition.discriminator;
|
this.polymorphic = definition.polymorphic;
|
||||||
if (!this.discriminator) {
|
if (typeof this.polymorphic !== 'object') {
|
||||||
assert(this.modelTo, 'Target model is required');
|
assert(this.modelTo, 'Target model is required');
|
||||||
}
|
}
|
||||||
this.modelThrough = definition.modelThrough;
|
this.modelThrough = definition.modelThrough;
|
||||||
|
@ -128,6 +131,29 @@ RelationDefinition.prototype.toJSON = function () {
|
||||||
return json;
|
return json;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a relation scope method
|
||||||
|
* @param {String} name of the method
|
||||||
|
* @param {Function} function to define
|
||||||
|
*/
|
||||||
|
RelationDefinition.prototype.defineMethod = function(name, fn) {
|
||||||
|
var relationClass = RelationClasses[this.type];
|
||||||
|
var relationName = this.name;
|
||||||
|
var modelFrom = this.modelFrom;
|
||||||
|
var definition = this;
|
||||||
|
var scope = this.modelFrom.scopes[this.name];
|
||||||
|
if (!scope) throw new Error('Unknown relation scope: ' + this.name);
|
||||||
|
var method = scope.defineMethod(name, function() {
|
||||||
|
var relation = new relationClass(definition, this);
|
||||||
|
return fn.apply(relation, arguments);
|
||||||
|
});
|
||||||
|
if (fn.shared) {
|
||||||
|
sharedMethod(definition, name, method, fn);
|
||||||
|
modelFrom.prototype['__' + name + '__' + relationName] = method;
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the configured scope to the filter/query object.
|
* Apply the configured scope to the filter/query object.
|
||||||
* @param {Object} modelInstance
|
* @param {Object} modelInstance
|
||||||
|
@ -137,8 +163,13 @@ RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
|
||||||
filter = filter || {};
|
filter = filter || {};
|
||||||
filter.where = filter.where || {};
|
filter.where = filter.where || {};
|
||||||
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
|
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
|
||||||
&& typeof this.discriminator === 'string') { // polymorphic
|
&& typeof this.polymorphic === 'object') { // polymorphic
|
||||||
filter.where[this.discriminator] = this.modelFrom.modelName;
|
var discriminator = this.polymorphic.discriminator;
|
||||||
|
if (this.polymorphic.invert) {
|
||||||
|
filter.where[discriminator] = this.modelTo.modelName;
|
||||||
|
} else {
|
||||||
|
filter.where[discriminator] = this.modelFrom.modelName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (typeof this.scope === 'function') {
|
if (typeof this.scope === 'function') {
|
||||||
var scope = this.scope.call(this, modelInstance, filter);
|
var scope = this.scope.call(this, modelInstance, filter);
|
||||||
|
@ -155,21 +186,30 @@ RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
|
||||||
* @param {Object} modelInstance
|
* @param {Object} modelInstance
|
||||||
* @param {Object} target
|
* @param {Object} target
|
||||||
*/
|
*/
|
||||||
RelationDefinition.prototype.applyProperties = function(modelInstance, target) {
|
RelationDefinition.prototype.applyProperties = function(modelInstance, obj) {
|
||||||
|
var source = modelInstance, target = obj;
|
||||||
|
if (this.options.invertProperties) {
|
||||||
|
source = obj, target = modelInstance;
|
||||||
|
}
|
||||||
if (typeof this.properties === 'function') {
|
if (typeof this.properties === 'function') {
|
||||||
var data = this.properties.call(this, modelInstance);
|
var data = this.properties.call(this, source);
|
||||||
for(var k in data) {
|
for(var k in data) {
|
||||||
target[k] = data[k];
|
target[k] = data[k];
|
||||||
}
|
}
|
||||||
} else if (typeof this.properties === 'object') {
|
} else if (typeof this.properties === 'object') {
|
||||||
for(var k in this.properties) {
|
for(var k in this.properties) {
|
||||||
var key = this.properties[k];
|
var key = this.properties[k];
|
||||||
target[key] = modelInstance[k];
|
target[key] = source[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
|
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
|
||||||
&& typeof this.discriminator === 'string') { // polymorphic
|
&& typeof this.polymorphic === 'object') { // polymorphic
|
||||||
target[this.discriminator] = this.modelFrom.modelName;
|
var discriminator = this.polymorphic.discriminator;
|
||||||
|
if (this.polymorphic.invert) {
|
||||||
|
target[discriminator] = this.modelTo.modelName;
|
||||||
|
} else {
|
||||||
|
target[discriminator] = this.modelFrom.modelName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,6 +241,15 @@ Relation.prototype.getCache = function () {
|
||||||
return this.modelInstance.__cachedRelations[this.definition.name];
|
return this.modelInstance.__cachedRelations[this.definition.name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the related model(s) - this is a helper method to unify access.
|
||||||
|
* @param (Boolean|Object} condOrRefresh refresh or conditions object
|
||||||
|
* @param {Function} cb callback
|
||||||
|
*/
|
||||||
|
Relation.prototype.fetch = function(condOrRefresh, cb) {
|
||||||
|
this.modelInstance[this.definition.name].apply(this.modelInstance, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HasMany subclass
|
* HasMany subclass
|
||||||
* @param {RelationDefinition|Object} definition
|
* @param {RelationDefinition|Object} definition
|
||||||
|
@ -398,6 +447,21 @@ function lookupModel(models, modelName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
||||||
|
if ('string' === typeof modelTo) {
|
||||||
|
params.as = params.as || modelTo;
|
||||||
|
modelTo = params.model || modelTo;
|
||||||
|
if (typeof modelTo === 'string') {
|
||||||
|
var modelToName = (singularize ? i8n.singularize(modelTo) : modelTo).toLowerCase();
|
||||||
|
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
||||||
|
}
|
||||||
|
if (typeof modelTo !== 'function') {
|
||||||
|
throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelTo;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Normalize polymorphic parameters
|
* Normalize polymorphic parameters
|
||||||
* @param {Object|String} params Name of the polymorphic relation or params
|
* @param {Object|String} params Name of the polymorphic relation or params
|
||||||
|
@ -436,24 +500,17 @@ function polymorphicParams(params) {
|
||||||
RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
var thisClassName = modelFrom.modelName;
|
var thisClassName = modelFrom.modelName;
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if (typeof modelTo === 'string') {
|
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
|
||||||
params.as = modelTo;
|
|
||||||
if (params.model) {
|
|
||||||
modelTo = params.model;
|
|
||||||
} else {
|
|
||||||
var modelToName = i8n.singularize(modelTo).toLowerCase();
|
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
||||||
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
||||||
|
|
||||||
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
||||||
var discriminator;
|
var discriminator, polymorphic;
|
||||||
|
|
||||||
if (params.polymorphic) {
|
if (params.polymorphic) {
|
||||||
var polymorphic = polymorphicParams(params.polymorphic);
|
polymorphic = polymorphicParams(params.polymorphic);
|
||||||
|
polymorphic.invert = !!params.invert;
|
||||||
discriminator = polymorphic.discriminator;
|
discriminator = polymorphic.discriminator;
|
||||||
if (!params.invert) {
|
if (!params.invert) {
|
||||||
fk = polymorphic.foreignKey;
|
fk = polymorphic.foreignKey;
|
||||||
|
@ -469,12 +526,12 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
modelFrom: modelFrom,
|
modelFrom: modelFrom,
|
||||||
keyFrom: idName,
|
keyFrom: idName,
|
||||||
keyTo: fk,
|
keyTo: fk,
|
||||||
discriminator: discriminator,
|
|
||||||
modelTo: modelTo,
|
modelTo: modelTo,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
properties: params.properties,
|
properties: params.properties,
|
||||||
scope: params.scope,
|
scope: params.scope,
|
||||||
options: params.options
|
options: params.options,
|
||||||
|
polymorphic: polymorphic
|
||||||
});
|
});
|
||||||
|
|
||||||
definition.modelThrough = params.through;
|
definition.modelThrough = params.through;
|
||||||
|
@ -554,7 +611,8 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}, scopeMethods, definition.options);
|
}, scopeMethods, definition.options);
|
||||||
|
|
||||||
|
return definition;
|
||||||
};
|
};
|
||||||
|
|
||||||
function scopeMethod(definition, methodName) {
|
function scopeMethod(definition, methodName) {
|
||||||
|
@ -689,9 +747,13 @@ var throughKeys = function(definition) {
|
||||||
var modelThrough = definition.modelThrough;
|
var modelThrough = definition.modelThrough;
|
||||||
var pk2 = definition.modelTo.definition.idName();
|
var pk2 = definition.modelTo.definition.idName();
|
||||||
|
|
||||||
if (definition.discriminator) { // polymorphic
|
if (typeof definition.polymorphic === 'object') { // polymorphic
|
||||||
var fk1 = definition.keyTo;
|
var fk1 = definition.keyTo;
|
||||||
var fk2 = definition.keyThrough;
|
if (definition.polymorphic.invert) {
|
||||||
|
var fk2 = definition.polymorphic.foreignKey;
|
||||||
|
} else {
|
||||||
|
var fk2 = definition.keyThrough;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
|
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
|
||||||
definition.keyFrom);
|
definition.keyFrom);
|
||||||
|
@ -953,13 +1015,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
var discriminator, polymorphic;
|
var discriminator, polymorphic;
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if ('string' === typeof modelTo && !params.polymorphic) {
|
if ('string' === typeof modelTo && !params.polymorphic) {
|
||||||
params.as = modelTo;
|
modelTo = lookupModelTo(modelFrom, modelTo, params);
|
||||||
if (params.model) {
|
|
||||||
modelTo = params.model;
|
|
||||||
} else {
|
|
||||||
var modelToName = modelTo.toLowerCase();
|
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var idName, relationName, fk;
|
var idName, relationName, fk;
|
||||||
|
@ -993,17 +1049,17 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName);
|
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
|
var definition = modelFrom.relations[relationName] = new RelationDefinition({
|
||||||
name: relationName,
|
name: relationName,
|
||||||
type: RelationTypes.belongsTo,
|
type: RelationTypes.belongsTo,
|
||||||
modelFrom: modelFrom,
|
modelFrom: modelFrom,
|
||||||
keyFrom: fk,
|
keyFrom: fk,
|
||||||
keyTo: idName,
|
keyTo: idName,
|
||||||
discriminator: discriminator,
|
|
||||||
modelTo: modelTo,
|
modelTo: modelTo,
|
||||||
properties: params.properties,
|
properties: params.properties,
|
||||||
scope: params.scope,
|
scope: params.scope,
|
||||||
options: params.options
|
options: params.options,
|
||||||
|
polymorphic: polymorphic
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define a property for the scope so that we have 'this' for the scoped methods
|
// Define a property for the scope so that we have 'this' for the scoped methods
|
||||||
|
@ -1011,12 +1067,12 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: function() {
|
get: function() {
|
||||||
var relation = new BelongsTo(relationDef, this);
|
var relation = new BelongsTo(definition, this);
|
||||||
var relationMethod = relation.related.bind(relation);
|
var relationMethod = relation.related.bind(relation);
|
||||||
relationMethod.create = relation.create.bind(relation);
|
relationMethod.create = relation.create.bind(relation);
|
||||||
relationMethod.build = relation.build.bind(relation);
|
relationMethod.build = relation.build.bind(relation);
|
||||||
if (relationDef.modelTo) {
|
if (definition.modelTo) {
|
||||||
relationMethod._targetClass = relationDef.modelTo.modelName;
|
relationMethod._targetClass = definition.modelTo.modelName;
|
||||||
}
|
}
|
||||||
return relationMethod;
|
return relationMethod;
|
||||||
}
|
}
|
||||||
|
@ -1030,6 +1086,8 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
f.apply(this, arguments);
|
f.apply(this, arguments);
|
||||||
};
|
};
|
||||||
modelFrom.prototype['__get__' + relationName] = fn;
|
modelFrom.prototype['__get__' + relationName] = fn;
|
||||||
|
|
||||||
|
return definition;
|
||||||
};
|
};
|
||||||
|
|
||||||
BelongsTo.prototype.create = function(targetModelData, cb) {
|
BelongsTo.prototype.create = function(targetModelData, cb) {
|
||||||
|
@ -1043,14 +1101,17 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
|
||||||
cb = targetModelData;
|
cb = targetModelData;
|
||||||
targetModelData = {};
|
targetModelData = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.definition.applyProperties(modelInstance, targetModelData || {});
|
this.definition.applyProperties(modelInstance, targetModelData || {});
|
||||||
|
|
||||||
modelTo.create(targetModelData, function(err, targetModel) {
|
modelTo.create(targetModelData, function(err, targetModel) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
modelInstance[fk] = targetModel[pk];
|
modelInstance[fk] = targetModel[pk];
|
||||||
self.resetCache(targetModel);
|
modelInstance.save(function(err, inst) {
|
||||||
cb && cb(err, targetModel);
|
if (cb && err) return cb && cb(err);
|
||||||
|
self.resetCache(targetModel);
|
||||||
|
cb && cb(err, targetModel);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
cb && cb(err);
|
cb && cb(err);
|
||||||
}
|
}
|
||||||
|
@ -1078,10 +1139,10 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var modelFrom = this.definition.modelFrom;
|
var modelFrom = this.definition.modelFrom;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var discriminator = this.definition.discriminator;
|
|
||||||
var pk = this.definition.keyTo;
|
var pk = this.definition.keyTo;
|
||||||
var fk = this.definition.keyFrom;
|
var fk = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
var discriminator;
|
||||||
|
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
params = refresh;
|
params = refresh;
|
||||||
|
@ -1090,6 +1151,10 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
throw new Error('Method can\'t be called with more than two arguments');
|
throw new Error('Method can\'t be called with more than two arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof this.definition.polymorphic === 'object') {
|
||||||
|
discriminator = this.definition.polymorphic.discriminator;
|
||||||
|
}
|
||||||
|
|
||||||
var cachedValue;
|
var cachedValue;
|
||||||
if (!refresh) {
|
if (!refresh) {
|
||||||
cachedValue = self.getCache();
|
cachedValue = self.getCache();
|
||||||
|
@ -1097,11 +1162,12 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
if (params instanceof ModelBaseClass) { // acts as setter
|
if (params instanceof ModelBaseClass) { // acts as setter
|
||||||
modelTo = params.constructor;
|
modelTo = params.constructor;
|
||||||
modelInstance[fk] = params[pk];
|
modelInstance[fk] = params[pk];
|
||||||
if (discriminator) modelInstance[discriminator] = params.constructor.modelName;
|
|
||||||
|
|
||||||
var data = {};
|
if (discriminator) {
|
||||||
this.definition.applyProperties(params, data);
|
modelInstance[discriminator] = params.constructor.modelName;
|
||||||
modelInstance.setAttributes(data);
|
}
|
||||||
|
|
||||||
|
this.definition.applyProperties(modelInstance, params);
|
||||||
|
|
||||||
self.resetCache(params);
|
self.resetCache(params);
|
||||||
} else if (typeof params === 'function') { // acts as async getter
|
} else if (typeof params === 'function') { // acts as async getter
|
||||||
|
@ -1182,19 +1248,9 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
*/
|
*/
|
||||||
RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelTo, params) {
|
RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelTo, params) {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
|
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
|
||||||
|
|
||||||
var models = modelFrom.dataSource.modelBuilder.models;
|
var models = modelFrom.dataSource.modelBuilder.models;
|
||||||
|
|
||||||
if ('string' === typeof modelTo) {
|
|
||||||
params.as = modelTo;
|
|
||||||
if (params.model) {
|
|
||||||
modelTo = params.model;
|
|
||||||
} else {
|
|
||||||
modelTo = lookupModel(models, i8n.singularize(modelTo)) || modelTo;
|
|
||||||
}
|
|
||||||
if (typeof modelTo === 'string') {
|
|
||||||
throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.through) {
|
if (!params.through) {
|
||||||
if (params.polymorphic) throw new Error('Polymorphic relations need a through model');
|
if (params.polymorphic) throw new Error('Polymorphic relations need a through model');
|
||||||
|
@ -1222,8 +1278,7 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
|
||||||
|
|
||||||
params.through.belongsTo(modelTo);
|
params.through.belongsTo(modelTo);
|
||||||
|
|
||||||
this.hasMany(modelFrom, modelTo, options);
|
return this.hasMany(modelFrom, modelTo, options);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1243,24 +1298,16 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
|
||||||
*/
|
*/
|
||||||
RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if ('string' === typeof modelTo) {
|
modelTo = lookupModelTo(modelFrom, modelTo, params);
|
||||||
params.as = modelTo;
|
|
||||||
if (params.model) {
|
|
||||||
modelTo = params.model;
|
|
||||||
} else {
|
|
||||||
var modelToName = modelTo.toLowerCase();
|
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pk = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
|
var pk = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
|
||||||
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
|
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
|
||||||
|
|
||||||
var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', true);
|
var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', true);
|
||||||
var discriminator;
|
var discriminator, polymorphic;
|
||||||
|
|
||||||
if (params.polymorphic) {
|
if (params.polymorphic) {
|
||||||
var polymorphic = polymorphicParams(params.polymorphic);
|
polymorphic = polymorphicParams(params.polymorphic);
|
||||||
fk = polymorphic.foreignKey;
|
fk = polymorphic.foreignKey;
|
||||||
discriminator = polymorphic.discriminator;
|
discriminator = polymorphic.discriminator;
|
||||||
if (!params.through) {
|
if (!params.through) {
|
||||||
|
@ -1268,16 +1315,16 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
|
var definition = modelFrom.relations[relationName] = new RelationDefinition({
|
||||||
name: relationName,
|
name: relationName,
|
||||||
type: RelationTypes.hasOne,
|
type: RelationTypes.hasOne,
|
||||||
modelFrom: modelFrom,
|
modelFrom: modelFrom,
|
||||||
keyFrom: pk,
|
keyFrom: pk,
|
||||||
keyTo: fk,
|
keyTo: fk,
|
||||||
discriminator: discriminator,
|
|
||||||
modelTo: modelTo,
|
modelTo: modelTo,
|
||||||
properties: params.properties,
|
properties: params.properties,
|
||||||
options: params.options
|
options: params.options,
|
||||||
|
polymorphic: polymorphic
|
||||||
});
|
});
|
||||||
|
|
||||||
modelFrom.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
|
modelFrom.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
|
||||||
|
@ -1287,11 +1334,11 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: function() {
|
get: function() {
|
||||||
var relation = new HasOne(relationDef, this);
|
var relation = new HasOne(definition, this);
|
||||||
var relationMethod = relation.related.bind(relation)
|
var relationMethod = relation.related.bind(relation)
|
||||||
relationMethod.create = relation.create.bind(relation);
|
relationMethod.create = relation.create.bind(relation);
|
||||||
relationMethod.build = relation.build.bind(relation);
|
relationMethod.build = relation.build.bind(relation);
|
||||||
relationMethod._targetClass = relationDef.modelTo.modelName;
|
relationMethod._targetClass = definition.modelTo.modelName;
|
||||||
return relationMethod;
|
return relationMethod;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1304,6 +1351,8 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
||||||
f.apply(this, arguments);
|
f.apply(this, arguments);
|
||||||
};
|
};
|
||||||
modelFrom.prototype['__get__' + relationName] = fn;
|
modelFrom.prototype['__get__' + relationName] = fn;
|
||||||
|
|
||||||
|
return definition;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1476,30 +1525,26 @@ HasOne.prototype.related = function (refresh, params) {
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) {
|
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) {
|
||||||
var thisClassName = modelFrom.modelName;
|
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if (typeof modelTo === 'string') {
|
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
|
||||||
params.as = modelTo;
|
|
||||||
if (params.model) {
|
var thisClassName = modelFrom.modelName;
|
||||||
modelTo = params.model;
|
var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List');
|
||||||
} else {
|
var propertyName = params.property || i8n.camelize(modelTo.pluralModelName, true);
|
||||||
var modelToName = i8n.singularize(modelTo).toLowerCase();
|
var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
||||||
}
|
if (relationName === propertyName) {
|
||||||
|
propertyName = '_' + propertyName;
|
||||||
|
debug('EmbedsMany property cannot be equal to relation name: ' +
|
||||||
|
'forcing property %s for relation %s', propertyName, relationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessorName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List');
|
var definition = modelFrom.relations[relationName] = new RelationDefinition({
|
||||||
var relationName = params.property || i8n.camelize(modelTo.pluralModelName, true);
|
|
||||||
var fk = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
|
||||||
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
|
||||||
|
|
||||||
var definition = modelFrom.relations[accessorName] = new RelationDefinition({
|
|
||||||
accessor: accessorName,
|
|
||||||
name: relationName,
|
name: relationName,
|
||||||
type: RelationTypes.embedsMany,
|
type: RelationTypes.embedsMany,
|
||||||
modelFrom: modelFrom,
|
modelFrom: modelFrom,
|
||||||
keyFrom: idName,
|
keyFrom: propertyName,
|
||||||
keyTo: fk,
|
keyTo: idName,
|
||||||
modelTo: modelTo,
|
modelTo: modelTo,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
properties: params.properties,
|
properties: params.properties,
|
||||||
|
@ -1508,7 +1553,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
embed: true
|
embed: true
|
||||||
});
|
});
|
||||||
|
|
||||||
modelFrom.dataSource.defineProperty(modelFrom.modelName, relationName, {
|
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, {
|
||||||
type: [modelTo], default: function() { return []; }
|
type: [modelTo], default: function() { return []; }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1516,14 +1561,14 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
modelTo.validatesPresenceOf(idName);
|
modelTo.validatesPresenceOf(idName);
|
||||||
|
|
||||||
if (!params.polymorphic) {
|
if (!params.polymorphic) {
|
||||||
modelFrom.validate(relationName, function(err) {
|
modelFrom.validate(propertyName, function(err) {
|
||||||
var embeddedList = this[relationName] || [];
|
var embeddedList = this[propertyName] || [];
|
||||||
var ids = embeddedList.map(function(m) { return m[idName]; });
|
var ids = embeddedList.map(function(m) { return m[idName]; });
|
||||||
var uniqueIds = ids.filter(function(id, pos) {
|
var uniqueIds = ids.filter(function(id, pos) {
|
||||||
return ids.indexOf(id) === pos;
|
return ids.indexOf(id) === pos;
|
||||||
});
|
});
|
||||||
if (ids.length !== uniqueIds.length) {
|
if (ids.length !== uniqueIds.length) {
|
||||||
this.errors.add(relationName, 'Contains duplicate `' + idName + '`', 'uniqueness');
|
this.errors.add(propertyName, 'Contains duplicate `' + idName + '`', 'uniqueness');
|
||||||
err(false);
|
err(false);
|
||||||
}
|
}
|
||||||
}, { code: 'uniqueness' })
|
}, { code: 'uniqueness' })
|
||||||
|
@ -1531,9 +1576,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
|
|
||||||
// validate all embedded items
|
// validate all embedded items
|
||||||
if (definition.options.validate) {
|
if (definition.options.validate) {
|
||||||
modelFrom.validate(relationName, function(err) {
|
modelFrom.validate(propertyName, function(err) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var embeddedList = this[relationName] || [];
|
var embeddedList = this[propertyName] || [];
|
||||||
var hasErrors = false;
|
var hasErrors = false;
|
||||||
embeddedList.forEach(function(item) {
|
embeddedList.forEach(function(item) {
|
||||||
if (item instanceof modelTo) {
|
if (item instanceof modelTo) {
|
||||||
|
@ -1543,11 +1588,11 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
var first = Object.keys(item.errors)[0];
|
var first = Object.keys(item.errors)[0];
|
||||||
var msg = 'contains invalid item: `' + id + '`';
|
var msg = 'contains invalid item: `' + id + '`';
|
||||||
msg += ' (' + first + ' ' + item.errors[first] + ')';
|
msg += ' (' + first + ' ' + item.errors[first] + ')';
|
||||||
self.errors.add(relationName, msg, 'invalid');
|
self.errors.add(propertyName, msg, 'invalid');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
self.errors.add(relationName, 'Contains invalid item', 'invalid');
|
self.errors.add(propertyName, 'Contains invalid item', 'invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasErrors) err(false);
|
if (hasErrors) err(false);
|
||||||
|
@ -1568,19 +1613,19 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
};
|
};
|
||||||
|
|
||||||
var findByIdFunc = scopeMethods.findById;
|
var findByIdFunc = scopeMethods.findById;
|
||||||
modelFrom.prototype['__findById__' + accessorName] = findByIdFunc;
|
modelFrom.prototype['__findById__' + relationName] = findByIdFunc;
|
||||||
|
|
||||||
var destroyByIdFunc = scopeMethods.destroy;
|
var destroyByIdFunc = scopeMethods.destroy;
|
||||||
modelFrom.prototype['__destroyById__' + accessorName] = destroyByIdFunc;
|
modelFrom.prototype['__destroyById__' + relationName] = destroyByIdFunc;
|
||||||
|
|
||||||
var updateByIdFunc = scopeMethods.updateById;
|
var updateByIdFunc = scopeMethods.updateById;
|
||||||
modelFrom.prototype['__updateById__' + accessorName] = updateByIdFunc;
|
modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc;
|
||||||
|
|
||||||
var addFunc = scopeMethods.add;
|
var addFunc = scopeMethods.add;
|
||||||
modelFrom.prototype['__link__' + accessorName] = addFunc;
|
modelFrom.prototype['__link__' + relationName] = addFunc;
|
||||||
|
|
||||||
var removeFunc = scopeMethods.remove;
|
var removeFunc = scopeMethods.remove;
|
||||||
modelFrom.prototype['__unlink__' + accessorName] = removeFunc;
|
modelFrom.prototype['__unlink__' + relationName] = removeFunc;
|
||||||
|
|
||||||
scopeMethods.create = scopeMethod(definition, 'create');
|
scopeMethods.create = scopeMethod(definition, 'create');
|
||||||
scopeMethods.build = scopeMethod(definition, 'build');
|
scopeMethods.build = scopeMethod(definition, 'build');
|
||||||
|
@ -1593,23 +1638,67 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
var methodName = customMethods[i];
|
var methodName = customMethods[i];
|
||||||
var method = scopeMethods[methodName];
|
var method = scopeMethods[methodName];
|
||||||
if (typeof method === 'function' && method.shared === true) {
|
if (typeof method === 'function' && method.shared === true) {
|
||||||
modelFrom.prototype['__' + methodName + '__' + accessorName] = method;
|
modelFrom.prototype['__' + methodName + '__' + relationName] = method;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mix the property and scoped methods into the prototype class
|
// Mix the property and scoped methods into the prototype class
|
||||||
var scopeDefinition = defineScope(modelFrom.prototype, modelTo, accessorName, function () {
|
var scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function () {
|
||||||
return {};
|
return {};
|
||||||
}, scopeMethods, definition.options);
|
}, scopeMethods, definition.options);
|
||||||
|
|
||||||
scopeDefinition.related = scopeMethods.related;
|
scopeDefinition.related = scopeMethods.related;
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
|
||||||
|
if (inst && inst.triggerParent !== 'function') {
|
||||||
|
var self = this;
|
||||||
|
var propertyName = this.definition.keyFrom;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
inst.triggerParent = function(actionName, callback) {
|
||||||
|
if (actionName === 'save' || actionName === 'destroy') {
|
||||||
|
var embeddedList = self.embeddedList();
|
||||||
|
if (actionName === 'destroy') {
|
||||||
|
var index = embeddedList.indexOf(inst);
|
||||||
|
if (index > -1) embeddedList.splice(index, 1);
|
||||||
|
}
|
||||||
|
modelInstance.updateAttribute(propertyName,
|
||||||
|
embeddedList, function(err, modelInst) {
|
||||||
|
callback(err, err ? null : modelInst);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process.nextTick(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var originalTrigger = inst.trigger;
|
||||||
|
inst.trigger = function(actionName, work, data, callback) {
|
||||||
|
if (typeof work === 'function') {
|
||||||
|
var originalWork = work;
|
||||||
|
work = function(next) {
|
||||||
|
originalWork.call(this, function(done) {
|
||||||
|
inst.triggerParent(actionName, function(err, inst) {
|
||||||
|
next(done); // TODO [fabien] - error handling?
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
originalTrigger.call(this, actionName, work, data, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.embeddedList = function(modelInstance) {
|
||||||
|
modelInstance = modelInstance || this.modelInstance;
|
||||||
|
var embeddedList = modelInstance[this.definition.keyFrom] || [];
|
||||||
|
embeddedList.forEach(this.prepareEmbeddedInstance.bind(this));
|
||||||
|
return embeddedList;
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var self = receiver;
|
|
||||||
|
|
||||||
var actualCond = {};
|
var actualCond = {};
|
||||||
var actualRefresh = false;
|
var actualRefresh = false;
|
||||||
|
@ -1626,13 +1715,13 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
|
||||||
throw new Error('Method can be only called with one or two arguments');
|
throw new Error('Method can be only called with one or two arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
var embeddedList = self[relationName] || [];
|
var embeddedList = this.embeddedList(receiver);
|
||||||
|
|
||||||
this.definition.applyScope(modelInstance, actualCond);
|
this.definition.applyScope(receiver, actualCond);
|
||||||
|
|
||||||
var params = mergeQuery(actualCond, scopeParams);
|
var params = mergeQuery(actualCond, scopeParams);
|
||||||
|
|
||||||
if (params.where) {
|
if (params.where) { // TODO [fabien] Support order/sorting
|
||||||
embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
|
embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1648,12 +1737,11 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.findById = function (fkId, cb) {
|
EmbedsMany.prototype.findById = function (fkId, cb) {
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyTo;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
var find = function(id) {
|
var find = function(id) {
|
||||||
for (var i = 0; i < embeddedList.length; i++) {
|
for (var i = 0; i < embeddedList.length; i++) {
|
||||||
|
@ -1690,18 +1778,16 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
var propertyName = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
var inst = this.findById(fkId);
|
var inst = this.findById(fkId);
|
||||||
|
|
||||||
if (inst instanceof modelTo) {
|
if (inst instanceof modelTo) {
|
||||||
if (typeof data === 'object') {
|
if (typeof data === 'object') {
|
||||||
for (var key in data) {
|
inst.setAttributes(data);
|
||||||
inst[key] = data[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var err = inst.isValid() ? null : new ValidationError(inst);
|
var err = inst.isValid() ? null : new ValidationError(inst);
|
||||||
if (err && typeof cb === 'function') {
|
if (err && typeof cb === 'function') {
|
||||||
|
@ -1711,7 +1797,7 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof cb === 'function') {
|
if (typeof cb === 'function') {
|
||||||
modelInstance.updateAttribute(relationName,
|
modelInstance.updateAttribute(propertyName,
|
||||||
embeddedList, function(err) {
|
embeddedList, function(err) {
|
||||||
cb(err, inst);
|
cb(err, inst);
|
||||||
});
|
});
|
||||||
|
@ -1726,10 +1812,10 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
|
||||||
|
|
||||||
EmbedsMany.prototype.destroyById = function (fkId, cb) {
|
EmbedsMany.prototype.destroyById = function (fkId, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
var propertyName = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId);
|
var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId);
|
||||||
|
|
||||||
|
@ -1737,7 +1823,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) {
|
||||||
var index = embeddedList.indexOf(inst);
|
var index = embeddedList.indexOf(inst);
|
||||||
if (index > -1) embeddedList.splice(index, 1);
|
if (index > -1) embeddedList.splice(index, 1);
|
||||||
if (typeof cb === 'function') {
|
if (typeof cb === 'function') {
|
||||||
modelInstance.updateAttribute(relationName,
|
modelInstance.updateAttribute(propertyName,
|
||||||
embeddedList, function(err) {
|
embeddedList, function(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
|
@ -1754,10 +1840,9 @@ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById;
|
||||||
|
|
||||||
EmbedsMany.prototype.at = function (index, cb) {
|
EmbedsMany.prototype.at = function (index, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
var item = embeddedList[parseInt(index)];
|
var item = embeddedList[parseInt(index)];
|
||||||
item = (item instanceof modelTo) ? item : null;
|
item = (item instanceof modelTo) ? item : null;
|
||||||
|
@ -1772,9 +1857,9 @@ EmbedsMany.prototype.at = function (index, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.create = function (targetModelData, cb) {
|
EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyTo;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
var propertyName = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
var autoId = this.definition.options.autoId !== false;
|
||||||
|
|
||||||
|
@ -1784,7 +1869,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
}
|
}
|
||||||
targetModelData = targetModelData || {};
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
var inst = this.build(targetModelData);
|
var inst = this.build(targetModelData);
|
||||||
|
|
||||||
|
@ -1796,20 +1881,19 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
modelInstance.updateAttribute(relationName,
|
modelInstance.updateAttribute(propertyName,
|
||||||
embeddedList, function(err, modelInst) {
|
embeddedList, function(err, modelInst) {
|
||||||
cb(err, err ? null : inst);
|
cb(err, err ? null : inst);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.build = function(targetModelData) {
|
EmbedsMany.prototype.build = function(targetModelData) {
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyTo;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var relationName = this.definition.name;
|
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
var autoId = this.definition.options.autoId !== false;
|
||||||
|
|
||||||
var embeddedList = modelInstance[relationName] || [];
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
targetModelData = targetModelData || {};
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
|
@ -1834,6 +1918,8 @@ EmbedsMany.prototype.build = function(targetModelData) {
|
||||||
embeddedList.push(inst);
|
embeddedList.push(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.prepareEmbeddedInstance(inst);
|
||||||
|
|
||||||
return inst;
|
return inst;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1911,7 +1997,7 @@ EmbedsMany.prototype.remove = function (acInst, cb) {
|
||||||
|
|
||||||
belongsTo.applyScope(modelInstance, filter);
|
belongsTo.applyScope(modelInstance, filter);
|
||||||
|
|
||||||
modelInstance[definition.accessor](filter, function(err, items) {
|
modelInstance[definition.name](filter, function(err, items) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
items.forEach(function(item) {
|
items.forEach(function(item) {
|
||||||
|
@ -1925,18 +2011,10 @@ EmbedsMany.prototype.remove = function (acInst, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) {
|
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) {
|
||||||
var thisClassName = modelFrom.modelName;
|
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if (typeof modelTo === 'string') {
|
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
|
||||||
params.as = modelTo;
|
|
||||||
if (params.model) {
|
|
||||||
modelTo = params.model;
|
|
||||||
} else {
|
|
||||||
var modelToName = i8n.singularize(modelTo).toLowerCase();
|
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var thisClassName = modelFrom.modelName;
|
||||||
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
||||||
var fk = params.foreignKey || i8n.camelize(modelTo.modelName + '_ids', true);
|
var fk = params.foreignKey || i8n.camelize(modelTo.modelName + '_ids', true);
|
||||||
var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
||||||
|
@ -2017,6 +2095,8 @@ RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo,
|
||||||
}, scopeMethods, definition.options);
|
}, scopeMethods, definition.options);
|
||||||
|
|
||||||
scopeDefinition.related = scopeMethods.related; // bound to definition
|
scopeDefinition.related = scopeMethods.related; // bound to definition
|
||||||
|
|
||||||
|
return definition;
|
||||||
};
|
};
|
||||||
|
|
||||||
ReferencesMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
ReferencesMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
||||||
|
|
|
@ -65,7 +65,7 @@ function RelationMixin() {
|
||||||
* @property {Object} model Model object
|
* @property {Object} model Model object
|
||||||
*/
|
*/
|
||||||
RelationMixin.hasMany = function hasMany(modelTo, params) {
|
RelationMixin.hasMany = function hasMany(modelTo, params) {
|
||||||
RelationDefinition.hasMany(this, modelTo, params);
|
return RelationDefinition.hasMany(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,7 +120,7 @@ RelationMixin.hasMany = function hasMany(modelTo, params) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
RelationMixin.belongsTo = function (modelTo, params) {
|
RelationMixin.belongsTo = function (modelTo, params) {
|
||||||
RelationDefinition.belongsTo(this, modelTo, params);
|
return RelationDefinition.belongsTo(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,17 +155,17 @@ RelationMixin.belongsTo = function (modelTo, params) {
|
||||||
* @property {Object} model Model object
|
* @property {Object} model Model object
|
||||||
*/
|
*/
|
||||||
RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) {
|
RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) {
|
||||||
RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
return RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.hasOne = function hasMany(modelTo, params) {
|
RelationMixin.hasOne = function hasMany(modelTo, params) {
|
||||||
RelationDefinition.hasOne(this, modelTo, params);
|
return RelationDefinition.hasOne(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.referencesMany = function hasMany(modelTo, params) {
|
RelationMixin.referencesMany = function hasMany(modelTo, params) {
|
||||||
RelationDefinition.referencesMany(this, modelTo, params);
|
return RelationDefinition.referencesMany(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.embedsMany = function hasMany(modelTo, params) {
|
RelationMixin.embedsMany = function hasMany(modelTo, params) {
|
||||||
RelationDefinition.embedsMany(this, modelTo, params);
|
return RelationDefinition.embedsMany(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
28
lib/scope.js
28
lib/scope.js
|
@ -55,6 +55,15 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a scope method
|
||||||
|
* @param {String} name of the method
|
||||||
|
* @param {Function} function to define
|
||||||
|
*/
|
||||||
|
ScopeDefinition.prototype.defineMethod = function(name, fn) {
|
||||||
|
return this.methods[name] = fn;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a scope to the class
|
* Define a scope to the class
|
||||||
* @param {Model} cls The class where the scope method is added
|
* @param {Model} cls The class where the scope method is added
|
||||||
|
@ -138,6 +147,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
f.build = build;
|
f.build = build;
|
||||||
f.create = create;
|
f.create = create;
|
||||||
f.destroyAll = destroyAll;
|
f.destroyAll = destroyAll;
|
||||||
|
f.count = count;
|
||||||
for (var i in definition.methods) {
|
for (var i in definition.methods) {
|
||||||
f[i] = definition.methods[i].bind(self);
|
f[i] = definition.methods[i].bind(self);
|
||||||
}
|
}
|
||||||
|
@ -183,6 +193,13 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
|
|
||||||
cls['__delete__' + name] = fn_delete;
|
cls['__delete__' + name] = fn_delete;
|
||||||
|
|
||||||
|
var fn_count = function (cb) {
|
||||||
|
var f = this[name].count;
|
||||||
|
f.apply(this[name], arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
cls['__count__' + name] = fn_count;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extracting fixed property values for the scope from the where clause into
|
* Extracting fixed property values for the scope from the where clause into
|
||||||
* the data object
|
* the data object
|
||||||
|
@ -239,6 +256,11 @@ function defineScope(cls, targetClass, name, params, methods, options) {
|
||||||
var where = (this._scope && this._scope.where) || {};
|
var where = (this._scope && this._scope.where) || {};
|
||||||
targetClass.destroyAll(where, cb);
|
targetClass.destroyAll(where, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function count(cb) {
|
||||||
|
var where = (this._scope && this._scope.where) || {};
|
||||||
|
targetClass.count(where, cb);
|
||||||
|
}
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
@ -277,11 +299,11 @@ function mergeQuery(base, update) {
|
||||||
base.collect = update.collect;
|
base.collect = update.collect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite order
|
// set order
|
||||||
if (update.order) {
|
if (!base.order && update.order) {
|
||||||
base.order = update.order;
|
base.order = update.order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite pagination
|
// overwrite pagination
|
||||||
if (update.limit !== undefined) {
|
if (update.limit !== undefined) {
|
||||||
base.limit = update.limit;
|
base.limit = update.limit;
|
||||||
|
|
39
lib/utils.js
39
lib/utils.js
|
@ -6,6 +6,7 @@ exports.parseSettings = parseSettings;
|
||||||
exports.mergeSettings = mergeSettings;
|
exports.mergeSettings = mergeSettings;
|
||||||
exports.isPlainObject = isPlainObject;
|
exports.isPlainObject = isPlainObject;
|
||||||
exports.defineCachedRelations = defineCachedRelations;
|
exports.defineCachedRelations = defineCachedRelations;
|
||||||
|
exports.sortObjectsByIds = sortObjectsByIds;
|
||||||
|
|
||||||
var traverse = require('traverse');
|
var traverse = require('traverse');
|
||||||
|
|
||||||
|
@ -203,4 +204,40 @@ function defineCachedRelations(obj) {
|
||||||
function isPlainObject(obj) {
|
function isPlainObject(obj) {
|
||||||
return (typeof obj === 'object') && (obj !== null)
|
return (typeof obj === 'object') && (obj !== null)
|
||||||
&& (obj.constructor === Object);
|
&& (obj.constructor === Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function sortObjectsByIds(idName, ids, objects, strict) {
|
||||||
|
ids = ids.map(function(id) {
|
||||||
|
return (typeof id === 'object') ? id.toString() : id;
|
||||||
|
});
|
||||||
|
|
||||||
|
var indexOf = function(x) {
|
||||||
|
var isObj = (typeof x[idName] === 'object'); // ObjectID
|
||||||
|
var id = isObj ? x[idName].toString() : x[idName];
|
||||||
|
return ids.indexOf(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
var heading = [];
|
||||||
|
var tailing = [];
|
||||||
|
|
||||||
|
objects.forEach(function(x) {
|
||||||
|
if (typeof x === 'object') {
|
||||||
|
var idx = indexOf(x);
|
||||||
|
if (strict && idx === -1) return;
|
||||||
|
idx === -1 ? tailing.push(x) : heading.push(x);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
heading.sort(function(x, y) {
|
||||||
|
var a = indexOf(x);
|
||||||
|
var b = indexOf(y);
|
||||||
|
if (a === -1 || b === -1) return 1; // last
|
||||||
|
if (a === b) return 0;
|
||||||
|
if (a > b) return 1;
|
||||||
|
if (a < b) return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return heading.concat(tailing);
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-datasource-juggler",
|
"name": "loopback-datasource-juggler",
|
||||||
"version": "2.3.1",
|
"version": "2.4.0",
|
||||||
"description": "LoopBack DataSoure Juggler",
|
"description": "LoopBack DataSoure Juggler",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
|
|
@ -903,6 +903,26 @@ describe('Load models with relations', function () {
|
||||||
assert(Post.relations['user']);
|
assert(Post.relations['user']);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set up referencesMany relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String});
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'referencesMany', model: 'Post'}}});
|
||||||
|
|
||||||
|
assert(User.relations['posts']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up embedsMany relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String});
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'embedsMany', model: 'Post' }}});
|
||||||
|
|
||||||
|
assert(User.relations['posts']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set up foreign key with the correct type', function (done) {
|
it('should set up foreign key with the correct type', function (done) {
|
||||||
var ds = new DataSource('memory');
|
var ds = new DataSource('memory');
|
||||||
|
|
|
@ -42,12 +42,15 @@ mixins.define('TimeStamp', timestamps);
|
||||||
|
|
||||||
describe('Model class', function () {
|
describe('Model class', function () {
|
||||||
|
|
||||||
it('should define a mixin', function() {
|
it('should define mixins', function() {
|
||||||
mixins.define('Example', function(Model, options) {
|
mixins.define('Example', function(Model, options) {
|
||||||
Model.prototype.example = function() {
|
Model.prototype.example = function() {
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
mixins.define('Demo', function(Model, options) {
|
||||||
|
Model.demoMixin = options.ok;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply a mixin class', function() {
|
it('should apply a mixin class', function() {
|
||||||
|
@ -58,7 +61,7 @@ describe('Model class', function () {
|
||||||
|
|
||||||
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
||||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||||
mixins: { TimeStamp: true, demo: true, Address: true }
|
mixins: { Address: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
var properties = Item.definition.properties;
|
var properties = Item.definition.properties;
|
||||||
|
@ -70,11 +73,12 @@ describe('Model class', function () {
|
||||||
it('should apply mixins', function(done) {
|
it('should apply mixins', function(done) {
|
||||||
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
||||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||||
mixins: { TimeStamp: true, demo: { ok: true } }
|
mixins: { TimeStamp: true, Demo: { ok: true } }
|
||||||
});
|
});
|
||||||
|
|
||||||
Item.mixin('Example', { foo: 'bar' });
|
Item.mixin('Example', { foo: 'bar' });
|
||||||
Item.mixin('other');
|
|
||||||
|
Item.demoMixin.should.be.true;
|
||||||
|
|
||||||
var properties = Item.definition.properties;
|
var properties = Item.definition.properties;
|
||||||
properties.createdAt.should.eql({ type: Date });
|
properties.createdAt.should.eql({ type: Date });
|
||||||
|
|
|
@ -537,6 +537,16 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find count of records on scope - scoped', function (done) {
|
||||||
|
Category.findOne(function (err, c) {
|
||||||
|
c.productType = 'tool'; // temporary, for scoping
|
||||||
|
c.products.count(function(err, count) {
|
||||||
|
count.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should delete records on scope - scoped', function (done) {
|
it('should delete records on scope - scoped', function (done) {
|
||||||
Category.findOne(function (err, c) {
|
Category.findOne(function (err, c) {
|
||||||
|
@ -1030,6 +1040,40 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create polymorphic item through relation scope', function (done) {
|
||||||
|
Picture.findById(anotherPicture.id, function(err, p) {
|
||||||
|
p.authors.create({ name: 'Author 3' }, function(err, a) {
|
||||||
|
should.not.exist(err);
|
||||||
|
author = a;
|
||||||
|
author.name.should.equal('Author 3');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create polymorphic through model - new author', function (done) {
|
||||||
|
PictureLink.findOne({ where: {
|
||||||
|
pictureId: anotherPicture.id, imageableId: author.id, imageableType: 'Author'
|
||||||
|
} }, function(err, link) {
|
||||||
|
should.not.exist(err);
|
||||||
|
link.pictureId.should.eql(anotherPicture.id);
|
||||||
|
link.imageableId.should.eql(author.id);
|
||||||
|
link.imageableType.should.equal('Author');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find polymorphic items - new author', function (done) {
|
||||||
|
Author.findById(author.id, function(err, author) {
|
||||||
|
author.pictures(function(err, pics) {
|
||||||
|
pics.should.have.length(1);
|
||||||
|
pics[0].id.should.eql(anotherPicture.id);
|
||||||
|
pics[0].name.should.equal('Example');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1109,11 +1153,9 @@ describe('relations', function () {
|
||||||
p.person.create({name: 'Fred', age: 36 }, function(err, person) {
|
p.person.create({name: 'Fred', age: 36 }, function(err, person) {
|
||||||
personCreated = person;
|
personCreated = person;
|
||||||
p.personId.should.equal(person.id);
|
p.personId.should.equal(person.id);
|
||||||
p.save(function (err, p) {
|
person.name.should.equal('Fred');
|
||||||
person.name.should.equal('Fred');
|
person.passportNotes.should.equal('Some notes...');
|
||||||
person.passportNotes.should.equal('Some notes...');
|
done();
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1191,7 +1233,6 @@ describe('relations', function () {
|
||||||
Article.create(function (e, article) {
|
Article.create(function (e, article) {
|
||||||
article.tags.create({name: 'popular'}, function (e, t) {
|
article.tags.create({name: 'popular'}, function (e, t) {
|
||||||
t.should.be.an.instanceOf(Tag);
|
t.should.be.an.instanceOf(Tag);
|
||||||
// console.log(t);
|
|
||||||
ArticleTag.findOne(function (e, at) {
|
ArticleTag.findOne(function (e, at) {
|
||||||
should.exist(at);
|
should.exist(at);
|
||||||
at.tagId.toString().should.equal(t.id.toString());
|
at.tagId.toString().should.equal(t.id.toString());
|
||||||
|
@ -1566,19 +1607,15 @@ describe('relations', function () {
|
||||||
|
|
||||||
describe('embedsMany - relations, scope and properties', function () {
|
describe('embedsMany - relations, scope and properties', function () {
|
||||||
|
|
||||||
var product1, product2, product3;
|
var category, product1, product2, product3;
|
||||||
|
|
||||||
before(function (done) {
|
before(function () {
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Category = db.define('Category', {name: String});
|
Category = db.define('Category', {name: String});
|
||||||
Product = db.define('Product', {name: String});
|
Product = db.define('Product', {name: String});
|
||||||
Link = db.define('Link', {name: String});
|
Link = db.define('Link', {name: String});
|
||||||
|
|
||||||
db.automigrate(function () {
|
|
||||||
Person.destroyAll(done);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be declared', function (done) {
|
it('can be declared', function (done) {
|
||||||
Category.embedsMany(Link, {
|
Category.embedsMany(Link, {
|
||||||
as: 'items', // rename
|
as: 'items', // rename
|
||||||
|
@ -1588,6 +1625,7 @@ describe('relations', function () {
|
||||||
Link.belongsTo(Product, {
|
Link.belongsTo(Product, {
|
||||||
foreignKey: 'id', // re-use the actual product id
|
foreignKey: 'id', // re-use the actual product id
|
||||||
properties: { id: 'id', name: 'name' }, // denormalize, transfer id
|
properties: { id: 'id', name: 'name' }, // denormalize, transfer id
|
||||||
|
options: { invertProperties: true }
|
||||||
});
|
});
|
||||||
db.automigrate(function() {
|
db.automigrate(function() {
|
||||||
Product.create({ name: 'Product 0' }, done); // offset ids for tests
|
Product.create({ name: 'Product 0' }, done); // offset ids for tests
|
||||||
|
@ -1607,7 +1645,7 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create items on scope', function(done) {
|
it('should associate items on scope', function(done) {
|
||||||
Category.create({ name: 'Category A' }, function(err, cat) {
|
Category.create({ name: 'Category A' }, function(err, cat) {
|
||||||
var link = cat.items.build();
|
var link = cat.items.build();
|
||||||
link.product(product1);
|
link.product(product1);
|
||||||
|
@ -1718,12 +1756,84 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove embedded items by reference id', function(done) {
|
it('should have removed embedded items by reference id', function(done) {
|
||||||
Category.findOne(function(err, cat) {
|
Category.findOne(function(err, cat) {
|
||||||
cat.links.should.have.length(1);
|
cat.links.should.have.length(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var productId;
|
||||||
|
|
||||||
|
it('should create items on scope', function(done) {
|
||||||
|
Category.create({ name: 'Category B' }, function(err, cat) {
|
||||||
|
category = cat;
|
||||||
|
var link = cat.items.build({ notes: 'Some notes...' });
|
||||||
|
link.product.create({ name: 'Product 1' }, function(err, p) {
|
||||||
|
productId = p.id;
|
||||||
|
cat.links[0].id.should.eql(p.id);
|
||||||
|
cat.links[0].name.should.equal('Product 1'); // denormalized
|
||||||
|
cat.links[0].notes.should.equal('Some notes...');
|
||||||
|
cat.items.at(0).should.equal(cat.links[0]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find items on scope', function(done) {
|
||||||
|
Category.findById(category.id, function(err, cat) {
|
||||||
|
cat.name.should.equal('Category B');
|
||||||
|
cat.links.toObject().should.eql([
|
||||||
|
{id: productId, name: 'Product 1', notes: 'Some notes...'}
|
||||||
|
]);
|
||||||
|
cat.items.at(0).should.equal(cat.links[0]);
|
||||||
|
cat.items(function(err, items) { // alternative access
|
||||||
|
items.should.be.an.array;
|
||||||
|
items.should.have.length(1);
|
||||||
|
items[0].product(function(err, p) {
|
||||||
|
p.name.should.equal('Product 1'); // actual value
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update items on scope - and save parent', function(done) {
|
||||||
|
Category.findById(category.id, function(err, cat) {
|
||||||
|
var link = cat.items.at(0);
|
||||||
|
link.updateAttributes({notes: 'Updated notes...'}, function(err, link) {
|
||||||
|
link.notes.should.equal('Updated notes...');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find items on scope - verify update', function(done) {
|
||||||
|
Category.findById(category.id, function(err, cat) {
|
||||||
|
cat.name.should.equal('Category B');
|
||||||
|
cat.links.toObject().should.eql([
|
||||||
|
{id: productId, name: 'Product 1', notes: 'Updated notes...'}
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove items from scope - and save parent', function(done) {
|
||||||
|
Category.findById(category.id, function(err, cat) {
|
||||||
|
cat.items.at(0).destroy(function(err, link) {
|
||||||
|
cat.links.should.eql([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find items on scope - verify destroy', function(done) {
|
||||||
|
Category.findById(category.id, function(err, cat) {
|
||||||
|
cat.name.should.equal('Category B');
|
||||||
|
cat.links.should.eql([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1757,7 +1867,8 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
Link.belongsTo('linked', {
|
Link.belongsTo('linked', {
|
||||||
polymorphic: true, // needs unique auto-id
|
polymorphic: true, // needs unique auto-id
|
||||||
properties: { name: 'name' } // denormalized
|
properties: { name: 'name' }, // denormalized
|
||||||
|
options: { invertProperties: true }
|
||||||
});
|
});
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
});
|
});
|
||||||
|
@ -2129,5 +2240,80 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('custom relation/scope methods', function () {
|
||||||
|
var categoryId;
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
db = getSchema();
|
||||||
|
Category = db.define('Category', {name: String});
|
||||||
|
Product = db.define('Product', {name: String});
|
||||||
|
|
||||||
|
db.automigrate(function () {
|
||||||
|
Category.destroyAll(function() {
|
||||||
|
Product.destroyAll(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be declared', function (done) {
|
||||||
|
var relation = Category.hasMany(Product);
|
||||||
|
|
||||||
|
var summarize = function(cb) {
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
this.fetch(function(err, items) {
|
||||||
|
if (err) return cb(err, []);
|
||||||
|
var summary = items.map(function(item) {
|
||||||
|
var obj = item.toObject();
|
||||||
|
obj.categoryName = modelInstance.name;
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
cb(null, summary);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
summarize.shared = true; // remoting
|
||||||
|
summarize.http = { verb: 'get', path: '/products/summary' };
|
||||||
|
|
||||||
|
relation.defineMethod('summarize', summarize);
|
||||||
|
|
||||||
|
Category.prototype['__summarize__products'].should.be.a.function;
|
||||||
|
should.exist(Category.prototype['__summarize__products'].shared);
|
||||||
|
Category.prototype['__summarize__products'].http.should.eql(summarize.http);
|
||||||
|
|
||||||
|
db.automigrate(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup test records', function (done) {
|
||||||
|
Category.create({ name: 'Category A' }, function(err, cat) {
|
||||||
|
categoryId = cat.id;
|
||||||
|
cat.products.create({ name: 'Product 1' }, function(err, p) {
|
||||||
|
cat.products.create({ name: 'Product 2' }, function(err, p) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow custom scope methods - summarize', function(done) {
|
||||||
|
var expected = [
|
||||||
|
{ name: 'Product 1', categoryId: categoryId, categoryName: 'Category A' },
|
||||||
|
{ name: 'Product 2', categoryId: categoryId, categoryName: 'Category A' }
|
||||||
|
];
|
||||||
|
|
||||||
|
Category.findOne(function(err, cat) {
|
||||||
|
cat.products.summarize(function(err, summary) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var result = summary.map(function(item) {
|
||||||
|
delete item.id;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
result.should.eql(expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,3 +76,57 @@ describe('scope', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('scope - order', function () {
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
db = getSchema();
|
||||||
|
Station = db.define('Station', {
|
||||||
|
name: {type: String, index: true},
|
||||||
|
order: {type: Number, index: true}
|
||||||
|
});
|
||||||
|
Station.scope('reverse', {order: 'order DESC'});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Station.destroyAll(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Station.create({ name: 'a', order: 1 }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Station.create({ name: 'b', order: 2 }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
Station.create({ name: 'c', order: 3 }, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should define scope with default order', function (done) {
|
||||||
|
Station.reverse(function(err, stations) {
|
||||||
|
stations[0].name.should.equal('c');
|
||||||
|
stations[0].order.should.equal(3);
|
||||||
|
stations[1].name.should.equal('b');
|
||||||
|
stations[1].order.should.equal(2);
|
||||||
|
stations[2].name.should.equal('a');
|
||||||
|
stations[2].order.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override default scope order', function (done) {
|
||||||
|
Station.reverse({order: 'order ASC'}, function(err, stations) {
|
||||||
|
stations[0].name.should.equal('a');
|
||||||
|
stations[0].order.should.equal(1);
|
||||||
|
stations[1].name.should.equal('b');
|
||||||
|
stations[1].order.should.equal(2);
|
||||||
|
stations[2].name.should.equal('c');
|
||||||
|
stations[2].order.should.equal(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ var utils = require('../lib/utils');
|
||||||
var fieldsToArray = utils.fieldsToArray;
|
var fieldsToArray = utils.fieldsToArray;
|
||||||
var removeUndefined = utils.removeUndefined;
|
var removeUndefined = utils.removeUndefined;
|
||||||
var mergeSettings = utils.mergeSettings;
|
var mergeSettings = utils.mergeSettings;
|
||||||
|
var sortObjectsByIds = utils.sortObjectsByIds;
|
||||||
|
|
||||||
describe('util.fieldsToArray', function () {
|
describe('util.fieldsToArray', function () {
|
||||||
it('Turn objects and strings into an array of fields to include when finding models', function () {
|
it('Turn objects and strings into an array of fields to include when finding models', function () {
|
||||||
|
@ -185,4 +186,35 @@ describe('mergeSettings', function () {
|
||||||
|
|
||||||
should.deepEqual(dst.acls, expected.acls, 'Merged settings should match the expectation');
|
should.deepEqual(dst.acls, expected.acls, 'Merged settings should match the expectation');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sortObjectsByIds', function () {
|
||||||
|
|
||||||
|
var items = [
|
||||||
|
{ id: 1, name: 'a' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 5, name: 'e' },
|
||||||
|
{ id: 6, name: 'f' }
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should sort', function() {
|
||||||
|
var sorted = sortObjectsByIds('id', [6, 5, 4, 3, 2, 1], items);
|
||||||
|
var names = sorted.map(function(u) { return u.name; });
|
||||||
|
should.deepEqual(names, ['f', 'e', 'd', 'c', 'b', 'a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort - partial ids', function() {
|
||||||
|
var sorted = sortObjectsByIds('id', [5, 3, 2], items);
|
||||||
|
var names = sorted.map(function(u) { return u.name; });
|
||||||
|
should.deepEqual(names, ['e', 'c', 'b', 'a', 'd', 'f']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort - strict', function() {
|
||||||
|
var sorted = sortObjectsByIds('id', [5, 3, 2], items, true);
|
||||||
|
var names = sorted.map(function(u) { return u.name; });
|
||||||
|
should.deepEqual(names, ['e', 'c', 'b']);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue