Merge branch 'release/1.6.2' into production
This commit is contained in:
commit
748e674a15
|
@ -5,8 +5,8 @@ for interacting with databases, REST APIs, and other data sources. It was
|
||||||
initially forked from [JugglingDB](https://github.com/1602/jugglingdb).
|
initially forked from [JugglingDB](https://github.com/1602/jugglingdb).
|
||||||
|
|
||||||
**For full documentation, see the official StrongLoop documentation**:
|
**For full documentation, see the official StrongLoop documentation**:
|
||||||
* [Data sources and connectors](http://docs.strongloop.com/display/DOC/Data+sources+and+connectors)
|
* [Data sources and connectors](http://docs.strongloop.com/display/LB/Data+sources+and+connectors)
|
||||||
* [Data Source Juggler](http://docs.strongloop.com/display/DOC/Data+Source+Juggler).
|
* [Creating data sources and connected models](http://docs.strongloop.com/display/LB/Creating+data+sources+and+connected+models).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,12 @@ Memory.prototype.all = function all(model, filter, callback) {
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
if (!filter.order) {
|
||||||
|
var idNames = this.idNames(model);
|
||||||
|
if (idNames && idNames.length) {
|
||||||
|
filter.order = idNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
// do we need some sorting?
|
// do we need some sorting?
|
||||||
if (filter.order) {
|
if (filter.order) {
|
||||||
var orders = filter.order;
|
var orders = filter.order;
|
||||||
|
|
112
lib/dao.js
112
lib/dao.js
|
@ -434,6 +434,48 @@ DataAccessObject._normalize = function (filter) {
|
||||||
filter.skip = offset;
|
filter.skip = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter.order) {
|
||||||
|
var order = filter.order;
|
||||||
|
if (!Array.isArray(order)) {
|
||||||
|
order = [order];
|
||||||
|
}
|
||||||
|
var fields = [];
|
||||||
|
for (var i = 0, m = order.length; i < m; i++) {
|
||||||
|
if (typeof order[i] === 'string') {
|
||||||
|
// Normalize 'f1 ASC, f2 DESC, f3' to ['f1 ASC', 'f2 DESC', 'f3']
|
||||||
|
var tokens = order[i].split(/(?:\s*,\s*)+/);
|
||||||
|
for (var t = 0, n = tokens.length; t < n; t++) {
|
||||||
|
var token = tokens[t];
|
||||||
|
if (token.length === 0) {
|
||||||
|
// Skip empty token
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var parts = token.split(/\s+/);
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
var dir = parts[1].toUpperCase();
|
||||||
|
if (dir === 'ASC' || dir === 'DESC') {
|
||||||
|
token = parts[0] + ' ' + dir;
|
||||||
|
} else {
|
||||||
|
err = new Error(util.format('The order %j has invalid direction', token));
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields.push(token);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = new Error(util.format('The order %j is not valid', order[i]));
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fields.length === 1 && typeof filter.order === 'string') {
|
||||||
|
filter.order = fields[0];
|
||||||
|
} else {
|
||||||
|
filter.order = fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// normalize fields as array of included property names
|
// normalize fields as array of included property names
|
||||||
if (filter.fields) {
|
if (filter.fields) {
|
||||||
filter.fields = fieldsToArray(filter.fields,
|
filter.fields = fieldsToArray(filter.fields,
|
||||||
|
@ -445,6 +487,25 @@ DataAccessObject._normalize = function (filter) {
|
||||||
return filter;
|
return filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function DateType(arg) {
|
||||||
|
return new Date(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BooleanType(val) {
|
||||||
|
if (val === 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (val === 'false') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return Boolean(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumberType(val) {
|
||||||
|
var num = Number(val);
|
||||||
|
return !isNaN(num) ? num : val;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Coerce values based the property types
|
* Coerce values based the property types
|
||||||
* @param {Object} where The where clause
|
* @param {Object} where The where clause
|
||||||
|
@ -470,11 +531,11 @@ DataAccessObject._coerce = function (where) {
|
||||||
if (p === 'and' || p === 'or' || p === 'nor') {
|
if (p === 'and' || p === 'or' || p === 'nor') {
|
||||||
var clauses = where[p];
|
var clauses = where[p];
|
||||||
if (Array.isArray(clauses)) {
|
if (Array.isArray(clauses)) {
|
||||||
for (var i = 0; i < clauses.length; i++) {
|
for (var k = 0; k < clauses.length; k++) {
|
||||||
self._coerce(clauses[i]);
|
self._coerce(clauses[k]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = new Error(util.format('The %p operator has invalid clauses %j', p, clauses));
|
err = new Error(util.format('The %s operator has invalid clauses %j', p, clauses));
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -488,30 +549,16 @@ DataAccessObject._coerce = function (where) {
|
||||||
DataType = DataType[0];
|
DataType = DataType[0];
|
||||||
}
|
}
|
||||||
if (DataType === Date) {
|
if (DataType === Date) {
|
||||||
var OrigDate = Date;
|
DataType = DateType;
|
||||||
DataType = function Date(arg) {
|
|
||||||
return new OrigDate(arg);
|
|
||||||
};
|
|
||||||
} else if (DataType === Boolean) {
|
} else if (DataType === Boolean) {
|
||||||
DataType = function (val) {
|
DataType = BooleanType;
|
||||||
if (val === 'true') {
|
|
||||||
return true;
|
|
||||||
} else if (val === 'false') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return Boolean(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (DataType === Number) {
|
} else if (DataType === Number) {
|
||||||
// This fixes a regression in mongodb connector
|
// This fixes a regression in mongodb connector
|
||||||
// For numbers, only convert it produces a valid number
|
// For numbers, only convert it produces a valid number
|
||||||
// LoopBack by default injects a number id. We should fix it based
|
// LoopBack by default injects a number id. We should fix it based
|
||||||
// on the connector's input, for example, MongoDB should use string
|
// on the connector's input, for example, MongoDB should use string
|
||||||
// while RDBs typically use number
|
// while RDBs typically use number
|
||||||
DataType = function (val) {
|
DataType = NumberType;
|
||||||
var num = Number(val);
|
|
||||||
return !isNaN(num) ? num : val;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DataType) {
|
if (!DataType) {
|
||||||
|
@ -543,6 +590,31 @@ DataAccessObject._coerce = function (where) {
|
||||||
if (op in val) {
|
if (op in val) {
|
||||||
val = val[op];
|
val = val[op];
|
||||||
operator = op;
|
operator = op;
|
||||||
|
switch(operator) {
|
||||||
|
case 'inq':
|
||||||
|
case 'nin':
|
||||||
|
if (!Array.isArray(val)) {
|
||||||
|
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'between':
|
||||||
|
if (!Array.isArray(val) || val.length !== 2) {
|
||||||
|
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'like':
|
||||||
|
case 'nlike':
|
||||||
|
if (!(typeof val === 'string' || val instanceof RegExp)) {
|
||||||
|
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,7 +364,7 @@ function isModelClass(cls) {
|
||||||
return cls.prototype instanceof ModelBaseClass;
|
return cls.prototype instanceof ModelBaseClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany'];
|
DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany', 'hasOne'];
|
||||||
|
|
||||||
function isModelDataSourceAttached(model) {
|
function isModelDataSourceAttached(model) {
|
||||||
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);
|
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);
|
||||||
|
@ -1579,22 +1579,26 @@ DataSource.prototype.idProperty = function (modelName) {
|
||||||
* @param {String} foreignClassName The foreign model name
|
* @param {String} foreignClassName The foreign model name
|
||||||
*/
|
*/
|
||||||
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) {
|
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) {
|
||||||
// quit if key already defined
|
var pkType = null;
|
||||||
if (this.getModelDefinition(className).rawProperties[key]) return;
|
|
||||||
|
|
||||||
var defaultType = Number;
|
|
||||||
if (foreignClassName) {
|
|
||||||
var foreignModel = this.getModelDefinition(foreignClassName);
|
var foreignModel = this.getModelDefinition(foreignClassName);
|
||||||
var pkName = foreignModel && foreignModel.idName();
|
var pkName = foreignModel && foreignModel.idName();
|
||||||
if (pkName) {
|
if (pkName) {
|
||||||
defaultType = foreignModel.properties[pkName].type;
|
pkType = foreignModel.properties[pkName].type;
|
||||||
}
|
}
|
||||||
|
var model = this.getModelDefinition(className);
|
||||||
|
if (model.properties[key]) {
|
||||||
|
if (pkType) {
|
||||||
|
// Reset the type of the foreign key
|
||||||
|
model.rawProperties[key].type = model.properties[key].type = pkType;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.connector.defineForeignKey) {
|
if (this.connector.defineForeignKey) {
|
||||||
var cb = function (err, keyType) {
|
var cb = function (err, keyType) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
// Add the foreign key property to the data source _models
|
// Add the foreign key property to the data source _models
|
||||||
this.defineProperty(className, key, {type: keyType || defaultType});
|
this.defineProperty(className, key, {type: keyType || pkType});
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
switch (this.connector.defineForeignKey.length) {
|
switch (this.connector.defineForeignKey.length) {
|
||||||
case 4:
|
case 4:
|
||||||
|
@ -1607,7 +1611,7 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add the foreign key property to the data source _models
|
// Add the foreign key property to the data source _models
|
||||||
this.defineProperty(className, key, {type: defaultType});
|
this.defineProperty(className, key, {type: pkType});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
12
lib/model.js
12
lib/model.js
|
@ -158,11 +158,19 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
||||||
var def = properties[p]['default'];
|
var def = properties[p]['default'];
|
||||||
if (def !== undefined) {
|
if (def !== undefined) {
|
||||||
if (typeof def === 'function') {
|
if (typeof def === 'function') {
|
||||||
self.__data[p] = def();
|
if (def === Date) {
|
||||||
|
// FIXME: We should coerce the value in general
|
||||||
|
// This is a work around to {default: Date}
|
||||||
|
// Date() will return a string instead of Date
|
||||||
|
def = new Date();
|
||||||
} else {
|
} else {
|
||||||
self.__data[p] = def;
|
def = def();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// FIXME: We should coerce the value
|
||||||
|
// will implement it after we refactor the PropertyDefinition
|
||||||
|
self.__data[p] = def;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle complex types (JSON/Object)
|
// Handle complex types (JSON/Object)
|
||||||
|
|
|
@ -60,10 +60,10 @@ function RelationDefinition(definition) {
|
||||||
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);
|
||||||
this.modelFrom = definition.modelFrom;
|
this.modelFrom = definition.modelFrom;
|
||||||
assert(this.modelFrom);
|
assert(this.modelFrom, 'Source model is required');
|
||||||
this.keyFrom = definition.keyFrom;
|
this.keyFrom = definition.keyFrom;
|
||||||
this.modelTo = definition.modelTo;
|
this.modelTo = definition.modelTo;
|
||||||
assert(this.modelTo);
|
assert(this.modelTo, 'Target model is required');
|
||||||
this.keyTo = definition.keyTo;
|
this.keyTo = definition.keyTo;
|
||||||
this.modelThrough = definition.modelThrough;
|
this.modelThrough = definition.modelThrough;
|
||||||
this.keyThrough = definition.keyThrough;
|
this.keyThrough = definition.keyThrough;
|
||||||
|
@ -106,6 +106,15 @@ function Relation(definition, modelInstance) {
|
||||||
this.modelInstance = modelInstance;
|
this.modelInstance = modelInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Relation.prototype.resetCache = function (cache) {
|
||||||
|
cache = cache || undefined;
|
||||||
|
this.modelInstance.__cachedRelations[this.definition.name] = cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
Relation.prototype.getCache = function () {
|
||||||
|
return this.modelInstance.__cachedRelations[this.definition.name];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HasMany subclass
|
* HasMany subclass
|
||||||
* @param {RelationDefinition|Object} definition
|
* @param {RelationDefinition|Object} definition
|
||||||
|
@ -124,6 +133,39 @@ function HasMany(definition, modelInstance) {
|
||||||
|
|
||||||
util.inherits(HasMany, Relation);
|
util.inherits(HasMany, Relation);
|
||||||
|
|
||||||
|
HasMany.prototype.removeFromCache = function (id) {
|
||||||
|
var cache = this.modelInstance.__cachedRelations[this.definition.name];
|
||||||
|
var idName = this.definition.modelTo.definition.idName();
|
||||||
|
if (Array.isArray(cache)) {
|
||||||
|
for (var i = 0, n = cache.length; i < n; i++) {
|
||||||
|
if (cache[i][idName] === id) {
|
||||||
|
return cache.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
HasMany.prototype.addToCache = function (inst) {
|
||||||
|
if (!inst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cache = this.modelInstance.__cachedRelations[this.definition.name];
|
||||||
|
if (cache === undefined) {
|
||||||
|
cache = this.modelInstance.__cachedRelations[this.definition.name] = [];
|
||||||
|
}
|
||||||
|
var idName = this.definition.modelTo.definition.idName();
|
||||||
|
if (Array.isArray(cache)) {
|
||||||
|
for (var i = 0, n = cache.length; i < n; i++) {
|
||||||
|
if (cache[i][idName] === inst[idName]) {
|
||||||
|
cache[i] = inst;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.push(inst);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HasManyThrough subclass
|
* HasManyThrough subclass
|
||||||
* @param {RelationDefinition|Object} definition
|
* @param {RelationDefinition|Object} definition
|
||||||
|
@ -210,7 +252,7 @@ function findBelongsTo(modelFrom, modelTo, keyTo) {
|
||||||
var rel = relations[keys[k]];
|
var rel = relations[keys[k]];
|
||||||
if (rel.type === RelationTypes.belongsTo &&
|
if (rel.type === RelationTypes.belongsTo &&
|
||||||
rel.modelTo === modelTo &&
|
rel.modelTo === modelTo &&
|
||||||
rel.keyTo === keyTo) {
|
(keyTo === undefined || rel.keyTo === keyTo)) {
|
||||||
return rel.keyFrom;
|
return rel.keyFrom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,7 +336,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
|
|
||||||
if (!params.through) {
|
if (!params.through) {
|
||||||
// obviously, modelTo should have attribute called `fk`
|
// obviously, modelTo should have attribute called `fk`
|
||||||
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, this.modelName);
|
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var scopeMethods = {
|
var scopeMethods = {
|
||||||
|
@ -306,6 +348,9 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
scopeMethods.create = scopeMethod(definition, 'create');
|
scopeMethods.create = scopeMethod(definition, 'create');
|
||||||
scopeMethods.add = scopeMethod(definition, 'add');
|
scopeMethods.add = scopeMethod(definition, 'add');
|
||||||
scopeMethods.remove = scopeMethod(definition, 'remove');
|
scopeMethods.remove = scopeMethod(definition, 'remove');
|
||||||
|
} else {
|
||||||
|
scopeMethods.create = scopeMethod(definition, 'create');
|
||||||
|
scopeMethods.build = scopeMethod(definition, 'build');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mix the property and scoped methods into the prototype class
|
// Mix the property and scoped methods into the prototype class
|
||||||
|
@ -356,6 +401,7 @@ HasMany.prototype.findById = function (id, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
HasMany.prototype.destroyById = function (id, cb) {
|
HasMany.prototype.destroyById = function (id, cb) {
|
||||||
|
var self = this;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
|
@ -369,6 +415,7 @@ HasMany.prototype.destroyById = function (id, cb) {
|
||||||
}
|
}
|
||||||
// Check if the foreign key matches the primary key
|
// Check if the foreign key matches the primary key
|
||||||
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) {
|
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) {
|
||||||
|
self.removeFromCache(inst[fk]);
|
||||||
inst.destroy(cb);
|
inst.destroy(cb);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied: foreign key does not match the primary key'));
|
cb(new Error('Permission denied: foreign key does not match the primary key'));
|
||||||
|
@ -379,6 +426,7 @@ HasMany.prototype.destroyById = function (id, cb) {
|
||||||
// Create an instance of the target model and connect it to the instance of
|
// Create an instance of the target model and connect it to the instance of
|
||||||
// the source model by creating an instance of the through model
|
// the source model by creating an instance of the through model
|
||||||
HasManyThrough.prototype.create = function create(data, done) {
|
HasManyThrough.prototype.create = function create(data, done) {
|
||||||
|
var self = this;
|
||||||
var definition = this.definition;
|
var definition = this.definition;
|
||||||
var modelTo = definition.modelTo;
|
var modelTo = definition.modelTo;
|
||||||
var modelThrough = definition.modelThrough;
|
var modelThrough = definition.modelThrough;
|
||||||
|
@ -391,27 +439,28 @@ HasManyThrough.prototype.create = function create(data, done) {
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
// First create the target model
|
// First create the target model
|
||||||
modelTo.create(data, function (err, ac) {
|
modelTo.create(data, function (err, to) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done && done(err, ac);
|
return done && done(err, to);
|
||||||
}
|
}
|
||||||
// The primary key for the target model
|
// The primary key for the target model
|
||||||
var pk2 = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
var pk2 = definition.modelTo.definition.idName();
|
||||||
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
|
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
|
||||||
definition.keyFrom);
|
definition.keyFrom);
|
||||||
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
|
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
|
||||||
var d = {};
|
var d = {};
|
||||||
d[fk1] = modelInstance[definition.keyFrom];
|
d[fk1] = modelInstance[definition.keyFrom];
|
||||||
d[fk2] = ac[pk2];
|
d[fk2] = to[pk2];
|
||||||
// Then create the through model
|
// Then create the through model
|
||||||
modelThrough.create(d, function (e) {
|
modelThrough.create(d, function (e, through) {
|
||||||
if (e) {
|
if (e) {
|
||||||
// Undo creation of the target model
|
// Undo creation of the target model
|
||||||
ac.destroy(function () {
|
to.destroy(function () {
|
||||||
done && done(e);
|
done && done(e);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
done && done(err, ac);
|
self.addToCache(to);
|
||||||
|
done && done(err, to);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -422,9 +471,9 @@ HasManyThrough.prototype.create = function create(data, done) {
|
||||||
* @param {Object|ID} acInst The actual instance or id value
|
* @param {Object|ID} acInst The actual instance or id value
|
||||||
*/
|
*/
|
||||||
HasManyThrough.prototype.add = function (acInst, done) {
|
HasManyThrough.prototype.add = function (acInst, done) {
|
||||||
|
var self = this;
|
||||||
var definition = this.definition;
|
var definition = this.definition;
|
||||||
var modelThrough = definition.modelThrough;
|
var modelThrough = definition.modelThrough;
|
||||||
var modelTo = definition.modelTo;
|
|
||||||
var pk1 = definition.keyFrom;
|
var pk1 = definition.keyFrom;
|
||||||
|
|
||||||
var data = {};
|
var data = {};
|
||||||
|
@ -434,7 +483,7 @@ HasManyThrough.prototype.add = function (acInst, done) {
|
||||||
definition.keyFrom);
|
definition.keyFrom);
|
||||||
|
|
||||||
// The primary key for the target model
|
// The primary key for the target model
|
||||||
var pk2 = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
var pk2 = definition.modelTo.definition.idName();
|
||||||
|
|
||||||
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
|
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
|
||||||
|
|
||||||
|
@ -445,7 +494,14 @@ HasManyThrough.prototype.add = function (acInst, done) {
|
||||||
data[fk2] = acInst[pk2] || acInst;
|
data[fk2] = acInst[pk2] || acInst;
|
||||||
|
|
||||||
// Create an instance of the through model
|
// Create an instance of the through model
|
||||||
modelThrough.findOrCreate({where: query}, data, done);
|
modelThrough.findOrCreate({where: query}, data, function(err, ac) {
|
||||||
|
if(!err) {
|
||||||
|
if (acInst instanceof definition.modelTo) {
|
||||||
|
self.addToCache(acInst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done(err, ac);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -453,13 +509,30 @@ HasManyThrough.prototype.add = function (acInst, done) {
|
||||||
* @param {Object|ID) acInst The actual instance or id value
|
* @param {Object|ID) acInst The actual instance or id value
|
||||||
*/
|
*/
|
||||||
HasManyThrough.prototype.remove = function (acInst, done) {
|
HasManyThrough.prototype.remove = function (acInst, done) {
|
||||||
var modelThrough = this.definition.modelThrough;
|
var self = this;
|
||||||
var fk2 = this.definition.keyThrough;
|
var definition = this.definition;
|
||||||
var pk = this.definition.keyFrom;
|
var modelThrough = definition.modelThrough;
|
||||||
|
var pk1 = definition.keyFrom;
|
||||||
|
|
||||||
var q = {};
|
var query = {};
|
||||||
q[fk2] = acInst[pk] || acInst;
|
|
||||||
modelThrough.deleteAll(q, done );
|
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
|
||||||
|
definition.keyFrom);
|
||||||
|
|
||||||
|
// The primary key for the target model
|
||||||
|
var pk2 = definition.modelTo.definition.idName();
|
||||||
|
|
||||||
|
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
|
||||||
|
|
||||||
|
query[fk1] = this.modelInstance[pk1];
|
||||||
|
query[fk2] = acInst[pk2] || acInst;
|
||||||
|
|
||||||
|
modelThrough.deleteAll(query, function (err) {
|
||||||
|
if (!err) {
|
||||||
|
self.removeFromCache(query[fk2]);
|
||||||
|
}
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -547,14 +620,21 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
||||||
};
|
};
|
||||||
|
|
||||||
BelongsTo.prototype.create = function(targetModelData, cb) {
|
BelongsTo.prototype.create = function(targetModelData, cb) {
|
||||||
|
var self = this;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
|
cb = targetModelData;
|
||||||
|
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);
|
||||||
cb && cb(err, targetModel);
|
cb && cb(err, targetModel);
|
||||||
} else {
|
} else {
|
||||||
cb && cb(err);
|
cb && cb(err);
|
||||||
|
@ -579,11 +659,11 @@ BelongsTo.prototype.build = function(targetModelData) {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
BelongsTo.prototype.related = function (refresh, params) {
|
BelongsTo.prototype.related = function (refresh, params) {
|
||||||
|
var self = this;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
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 relationName = this.definition.name;
|
|
||||||
|
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
params = refresh;
|
params = refresh;
|
||||||
|
@ -593,13 +673,12 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachedValue;
|
var cachedValue;
|
||||||
if (!refresh && modelInstance.__cachedRelations
|
if (!refresh) {
|
||||||
&& (modelInstance.__cachedRelations[relationName] !== undefined)) {
|
cachedValue = self.getCache();
|
||||||
cachedValue = modelInstance.__cachedRelations[relationName];
|
|
||||||
}
|
}
|
||||||
if (params instanceof ModelBaseClass) { // acts as setter
|
if (params instanceof ModelBaseClass) { // acts as setter
|
||||||
modelInstance[fk] = params[pk];
|
modelInstance[fk] = params[pk];
|
||||||
modelInstance.__cachedRelations[relationName] = params;
|
self.resetCache(params);
|
||||||
} else if (typeof params === 'function') { // acts as async getter
|
} else if (typeof params === 'function') { // acts as async getter
|
||||||
var cb = params;
|
var cb = params;
|
||||||
if (cachedValue === undefined) {
|
if (cachedValue === undefined) {
|
||||||
|
@ -612,9 +691,10 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
}
|
}
|
||||||
// Check if the foreign key matches the primary key
|
// Check if the foreign key matches the primary key
|
||||||
if (inst[pk] === modelInstance[fk]) {
|
if (inst[pk] === modelInstance[fk]) {
|
||||||
|
self.resetCache(inst);
|
||||||
cb(null, inst);
|
cb(null, inst);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied'));
|
cb(new Error('Permission denied: foreign key does not match the primary key'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return modelInstance[fk];
|
return modelInstance[fk];
|
||||||
|
@ -623,10 +703,10 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
return cachedValue;
|
return cachedValue;
|
||||||
}
|
}
|
||||||
} else if (params === undefined) { // acts as sync getter
|
} else if (params === undefined) { // acts as sync getter
|
||||||
return modelInstance[fk];
|
return cachedValue;
|
||||||
} else { // setter
|
} else { // setter
|
||||||
modelInstance[fk] = params;
|
modelInstance[fk] = params;
|
||||||
delete modelInstance.__cachedRelations[relationName];
|
self.resetCache();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -741,29 +821,77 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
||||||
* @param {String|Object} err Error string or object
|
* @param {String|Object} err Error string or object
|
||||||
* @param {Object} The newly created target model instance
|
* @param {Object} The newly created target model instance
|
||||||
*/
|
*/
|
||||||
HasOne.prototype.create = function(targetModelData, cb) {
|
HasOne.prototype.create = function (targetModelData, cb) {
|
||||||
|
var self = this;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
|
cb = targetModelData;
|
||||||
|
targetModelData = {};
|
||||||
|
}
|
||||||
|
targetModelData = targetModelData || {};
|
||||||
|
targetModelData[fk] = modelInstance[pk];
|
||||||
|
var query = {where: {}};
|
||||||
|
query.where[fk] = targetModelData[fk]
|
||||||
|
modelTo.findOne(query, function(err, result) {
|
||||||
|
if(err) {
|
||||||
|
cb(err);
|
||||||
|
} else if(result) {
|
||||||
|
cb(new Error('HasOne relation cannot create more than one instance of '
|
||||||
|
+ modelTo.modelName));
|
||||||
|
} else {
|
||||||
|
modelTo.create(targetModelData, function (err, targetModel) {
|
||||||
|
if (!err) {
|
||||||
|
// Refresh the cache
|
||||||
|
self.resetCache(targetModel);
|
||||||
|
cb && cb(err, targetModel);
|
||||||
|
} else {
|
||||||
|
cb && cb(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a target model instance
|
||||||
|
* @param {Object} targetModelData The target model data
|
||||||
|
* @callback {Function} [cb] Callback function
|
||||||
|
* @param {String|Object} err Error string or object
|
||||||
|
* @param {Object} The newly created target model instance
|
||||||
|
*/
|
||||||
|
HasMany.prototype.create = function (targetModelData, cb) {
|
||||||
|
var self = this;
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var fk = this.definition.keyTo;
|
||||||
|
var pk = this.definition.keyFrom;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
|
cb = targetModelData;
|
||||||
|
targetModelData = {};
|
||||||
|
}
|
||||||
targetModelData = targetModelData || {};
|
targetModelData = targetModelData || {};
|
||||||
targetModelData[fk] = modelInstance[pk];
|
targetModelData[fk] = modelInstance[pk];
|
||||||
modelTo.create(targetModelData, function(err, targetModel) {
|
modelTo.create(targetModelData, function(err, targetModel) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
|
// Refresh the cache
|
||||||
|
self.addToCache(targetModel);
|
||||||
cb && cb(err, targetModel);
|
cb && cb(err, targetModel);
|
||||||
} else {
|
} else {
|
||||||
cb && cb(err);
|
cb && cb(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a target model instance
|
* Build a target model instance
|
||||||
* @param {Object} targetModelData The target model data
|
* @param {Object} targetModelData The target model data
|
||||||
* @returns {Object} The newly built target model instance
|
* @returns {Object} The newly built target model instance
|
||||||
*/
|
*/
|
||||||
HasOne.prototype.build = function(targetModelData) {
|
HasMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
|
@ -784,11 +912,11 @@ HasOne.prototype.build = function(targetModelData) {
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
HasOne.prototype.related = function (refresh, params) {
|
HasOne.prototype.related = function (refresh, params) {
|
||||||
|
var self = this;
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var relationName = this.definition.name;
|
|
||||||
|
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
params = refresh;
|
params = refresh;
|
||||||
|
@ -798,13 +926,12 @@ HasOne.prototype.related = function (refresh, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachedValue;
|
var cachedValue;
|
||||||
if (!refresh && modelInstance.__cachedRelations
|
if (!refresh) {
|
||||||
&& (modelInstance.__cachedRelations[relationName] !== undefined)) {
|
cachedValue = self.getCache();
|
||||||
cachedValue = modelInstance.__cachedRelations[relationName];
|
|
||||||
}
|
}
|
||||||
if (params instanceof ModelBaseClass) { // acts as setter
|
if (params instanceof ModelBaseClass) { // acts as setter
|
||||||
params[fk] = modelInstance[pk];
|
params[fk] = modelInstance[pk];
|
||||||
modelInstance.__cachedRelations[relationName] = params;
|
self.resetCache(params);
|
||||||
} else if (typeof params === 'function') { // acts as async getter
|
} else if (typeof params === 'function') { // acts as async getter
|
||||||
var cb = params;
|
var cb = params;
|
||||||
if (cachedValue === undefined) {
|
if (cachedValue === undefined) {
|
||||||
|
@ -819,6 +946,7 @@ HasOne.prototype.related = function (refresh, params) {
|
||||||
}
|
}
|
||||||
// Check if the foreign key matches the primary key
|
// Check if the foreign key matches the primary key
|
||||||
if (inst[fk] === modelInstance[pk]) {
|
if (inst[fk] === modelInstance[pk]) {
|
||||||
|
self.resetCache(inst);
|
||||||
cb(null, inst);
|
cb(null, inst);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied'));
|
cb(new Error('Permission denied'));
|
||||||
|
@ -830,9 +958,9 @@ HasOne.prototype.related = function (refresh, params) {
|
||||||
return cachedValue;
|
return cachedValue;
|
||||||
}
|
}
|
||||||
} else if (params === undefined) { // acts as sync getter
|
} else if (params === undefined) { // acts as sync getter
|
||||||
return modelInstance[pk];
|
return cachedValue;
|
||||||
} else { // setter
|
} else { // setter
|
||||||
params[fk] = modelInstance[pk];
|
params[fk] = modelInstance[pk];
|
||||||
delete modelInstance.__cachedRelations[relationName];
|
self.resetCache();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -202,7 +202,9 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
var prop = targetClass.definition.properties[i];
|
var prop = targetClass.definition.properties[i];
|
||||||
if (prop) {
|
if (prop) {
|
||||||
var val = where[i];
|
var val = where[i];
|
||||||
if (typeof val !== 'object' || val instanceof prop.type) {
|
if (typeof val !== 'object' || val instanceof prop.type
|
||||||
|
|| prop.type.name === 'ObjectID') // MongoDB key
|
||||||
|
{
|
||||||
// Only pick the {propertyName: propertyValue}
|
// Only pick the {propertyName: propertyValue}
|
||||||
data[i] = where[i];
|
data[i] = where[i];
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-datasource-juggler",
|
"name": "loopback-datasource-juggler",
|
||||||
"version": "1.6.1",
|
"version": "1.6.2",
|
||||||
"description": "LoopBack DataSoure Juggler",
|
"description": "LoopBack DataSoure Juggler",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
@ -24,14 +24,14 @@
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"should": "~1.2.2",
|
"should": "~1.2.2",
|
||||||
"mocha": "~1.18.2"
|
"mocha": "~1.20.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "~0.9.0",
|
"async": "~0.9.0",
|
||||||
"inflection": "~1.3.5",
|
"debug": "~1.0.2",
|
||||||
"traverse": "~0.6.6",
|
"inflection": "~1.3.7",
|
||||||
"qs": "~0.6.6",
|
"qs": "~0.6.6",
|
||||||
"debug": "~0.8.1"
|
"traverse": "~0.6.6"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Dual MIT/StrongLoop",
|
"name": "Dual MIT/StrongLoop",
|
||||||
|
|
|
@ -264,7 +264,7 @@ describe('DataSource define model', function () {
|
||||||
name: String,
|
name: String,
|
||||||
bio: ModelBuilder.Text,
|
bio: ModelBuilder.Text,
|
||||||
approved: Boolean,
|
approved: Boolean,
|
||||||
joinedAt: Date,
|
joinedAt: {type: Date, default: Date},
|
||||||
age: Number
|
age: Number
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -280,6 +280,8 @@ describe('DataSource define model', function () {
|
||||||
assert.equal(user.name, 'Joe');
|
assert.equal(user.name, 'Joe');
|
||||||
assert.equal(user.group, 'G1');
|
assert.equal(user.group, 'G1');
|
||||||
|
|
||||||
|
assert(user.joinedAt instanceof Date);
|
||||||
|
|
||||||
// setup relationships
|
// setup relationships
|
||||||
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,64 @@ describe('Memory connector', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw if the like value is not string or regexp', function (done) {
|
||||||
|
User.find({where: {name: {like: 123}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the nlike value is not string or regexp', function (done) {
|
||||||
|
User.find({where: {name: {nlike: 123}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the inq value is not an array', function (done) {
|
||||||
|
User.find({where: {name: {inq: '12'}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the nin value is not an array', function (done) {
|
||||||
|
User.find({where: {name: {nin: '12'}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the between value is not an array', function (done) {
|
||||||
|
User.find({where: {name: {between: '12'}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the between value is not an array of length 2', function (done) {
|
||||||
|
User.find({where: {name: {between: ['12']}}}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('support order with multiple fields', function (done) {
|
||||||
|
User.find({order: 'vip ASC, seq DESC'}, function (err, posts) {
|
||||||
|
should.not.exist(err);
|
||||||
|
posts[0].seq.should.be.eql(4);
|
||||||
|
posts[1].seq.should.be.eql(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if order has wrong direction', function (done) {
|
||||||
|
User.find({order: 'seq ABC'}, function (err, posts) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function seed(done) {
|
function seed(done) {
|
||||||
var beatles = [
|
var beatles = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -155,7 +155,7 @@ describe('relations', function () {
|
||||||
should.not.exist(e);
|
should.not.exist(e);
|
||||||
should.exist(l);
|
should.exist(l);
|
||||||
l.should.be.an.instanceOf(List);
|
l.should.be.an.instanceOf(List);
|
||||||
todo.list().should.equal(l.id);
|
todo.list().id.should.equal(l.id);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -206,7 +206,7 @@ describe('relations', function () {
|
||||||
should.not.exist(e);
|
should.not.exist(e);
|
||||||
should.exist(act);
|
should.exist(act);
|
||||||
act.should.be.an.instanceOf(Account);
|
act.should.be.an.instanceOf(Account);
|
||||||
supplier.account().should.equal(act.id);
|
supplier.account().id.should.equal(act.id);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue