Add an optional `options` argument to relation methods

This commit is contained in:
Raymond Feng 2015-05-16 10:11:17 -07:00
parent 9177e07209
commit 93a0342099
5 changed files with 566 additions and 254 deletions

View File

@ -366,7 +366,7 @@ Memory.prototype.all = function all(model, filter, options, callback) {
process.nextTick(function () { process.nextTick(function () {
if (filter && filter.include) { if (filter && filter.include) {
self._models[model].model.include(nodes, filter.include, callback); self._models[model].model.include(nodes, filter.include, options, callback);
} else { } else {
callback(null, nodes); callback(null, nodes);
} }

View File

@ -2068,10 +2068,10 @@ DataAccessObject.prototype.setAttribute = function setAttribute(name, value) {
* @param {Mixed} value Value of property * @param {Mixed} value Value of property
* @param {Function} cb Callback function called with (err, instance) * @param {Function} cb Callback function called with (err, instance)
*/ */
DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, cb) { DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, options, cb) {
var data = {}; var data = {};
data[name] = value; data[name] = value;
return this.updateAttributes(data, cb); return this.updateAttributes(data, options, cb);
}; };
/** /**

View File

@ -2,7 +2,6 @@ var async = require('async');
var utils = require('./utils'); var utils = require('./utils');
var isPlainObject = utils.isPlainObject; var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations; var defineCachedRelations = utils.defineCachedRelations;
var debug = require('debug')('loopback:include');
/*! /*!
* Normalize the include to be an array * Normalize the include to be an array
@ -145,11 +144,15 @@ Inclusion.normalizeInclude = normalizeInclude;
* *
* @param {Array} objects Array of instances * @param {Array} objects Array of instances
* @param {String|Object|Array} include Which relations to load. * @param {String|Object|Array} include Which relations to load.
* @param {Object} [options] Options for CRUD
* @param {Function} cb Callback called when relations are loaded * @param {Function} cb Callback called when relations are loaded
* *
*/ */
Inclusion.include = function (objects, include, cb) { Inclusion.include = function (objects, include, options, cb) {
debug('include', include); if (typeof options === 'function' && cb === undefined) {
cb = options;
options = {};
}
var self = this; var self = this;
if (!include || (Array.isArray(include) && include.length === 0) || if (!include || (Array.isArray(include) && include.length === 0) ||
@ -163,12 +166,12 @@ Inclusion.include = function (objects, include, cb) {
include = normalizeInclude(include); include = normalizeInclude(include);
async.each(include, function(item, callback) { async.each(include, function(item, callback) {
processIncludeItem(objects, item, callback); processIncludeItem(objects, item, options, callback);
}, function(err) { }, function(err) {
cb && cb(err, objects); cb && cb(err, objects);
}); });
function processIncludeItem(objs, include, cb) { function processIncludeItem(objs, include, options, cb) {
var relations = self.relations; var relations = self.relations;
var relationName; var relationName;
@ -214,8 +217,7 @@ Inclusion.include = function (objects, include, cb) {
// Just skip if inclusion is disabled // Just skip if inclusion is disabled
if (relation.options.disableInclude) { if (relation.options.disableInclude) {
cb(); return cb();
return;
} }
//prepare filter and fields for making DB Call //prepare filter and fields for making DB Call
var filter = (scope && scope.conditions()) || {}; var filter = (scope && scope.conditions()) || {};
@ -372,7 +374,7 @@ Inclusion.include = function (objects, include, cb) {
//process. //process.
if (subInclude && targets) { if (subInclude && targets) {
tasks.push(function subIncludesTask(next) { tasks.push(function subIncludesTask(next) {
relation.modelTo.include(targets, subInclude, next); relation.modelTo.include(targets, subInclude, options, next);
}); });
} }
//process & link each target with object //process & link each target with object
@ -450,7 +452,7 @@ Inclusion.include = function (objects, include, cb) {
//simultaneously process subIncludes //simultaneously process subIncludes
if (subInclude && targets) { if (subInclude && targets) {
tasks.push(function subIncludesTask(next) { tasks.push(function subIncludesTask(next) {
relation.modelTo.include(targets, subInclude, next); relation.modelTo.include(targets, subInclude, options, next);
}); });
} }
//process each target object //process each target object
@ -511,7 +513,7 @@ Inclusion.include = function (objects, include, cb) {
//simultaneously process subIncludes //simultaneously process subIncludes
if (subInclude && targets) { if (subInclude && targets) {
tasks.push(function subIncludesTask(next) { tasks.push(function subIncludesTask(next) {
relation.modelTo.include(targets, subInclude, next); relation.modelTo.include(targets, subInclude, options, next);
}); });
} }
//process each target object //process each target object
@ -597,7 +599,7 @@ Inclusion.include = function (objects, include, cb) {
//simultaneously process subIncludes //simultaneously process subIncludes
if (subInclude && targets) { if (subInclude && targets) {
tasks.push(function subIncludesTask(next) { tasks.push(function subIncludesTask(next) {
Model.include(targets, subInclude, next); Model.include(targets, subInclude, options, next);
}); });
} }
//process each target object //process each target object
@ -665,7 +667,7 @@ Inclusion.include = function (objects, include, cb) {
//simultaneously process subIncludes //simultaneously process subIncludes
if (subInclude && targets) { if (subInclude && targets) {
tasks.push(function subIncludesTask(next) { tasks.push(function subIncludesTask(next) {
relation.modelTo.include(targets, subInclude, next); relation.modelTo.include(targets, subInclude, options, next);
}); });
} }
//process each target object //process each target object
@ -769,10 +771,10 @@ Inclusion.include = function (objects, include, cb) {
related = inst[relationName].bind(inst, filter); related = inst[relationName].bind(inst, filter);
} else { } else {
related = inst[relationName].bind(inst); related = inst[relationName].bind(inst, undefined);
} }
related(function (err, result) { related(options, function (err, result) {
if (err) { if (err) {
return callback(err); return callback(err);
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@ -21,10 +21,11 @@ function ScopeDefinition(definition) {
} }
ScopeDefinition.prototype.targetModel = function(receiver) { ScopeDefinition.prototype.targetModel = function(receiver) {
var modelTo;
if (typeof this.options.modelTo === 'function') { if (typeof this.options.modelTo === 'function') {
var modelTo = this.options.modelTo.call(this, receiver) || this.modelTo; modelTo = this.options.modelTo.call(this, receiver) || this.modelTo;
} else { } else {
var modelTo = this.modelTo; modelTo = this.modelTo;
} }
if (!(modelTo.prototype instanceof DefaultModelBaseClass)) { if (!(modelTo.prototype instanceof DefaultModelBaseClass)) {
var msg = 'Invalid target model for scope `'; var msg = 'Invalid target model for scope `';
@ -36,16 +37,34 @@ ScopeDefinition.prototype.targetModel = function(receiver) {
return modelTo; return modelTo;
}; };
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { /*!
* Find related model instances
* @param {*} receiver The target model class/prototype
* @param {Object|Function} scopeParams
* @param {Boolean|Object} [condOrRefresh] true for refresh or object as a filter
* @param {Object} [options]
* @param {Function} cb
* @returns {*}
*/
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, options, cb) {
var name = this.name; var name = this.name;
var self = receiver; var self = receiver;
var actualCond = {}; var actualCond = {};
var actualRefresh = false; var actualRefresh = false;
var saveOnCache = true; var saveOnCache = true;
if (arguments.length === 3) { if (typeof condOrRefresh === 'function' &&
options === undefined && cb === undefined) {
// related(receiver, scopeParams, cb)
cb = condOrRefresh; cb = condOrRefresh;
} else if (arguments.length === 4) { options = {};
condOrRefresh = undefined;
} else if (typeof options === 'function' && cb === undefined) {
cb = options;
options = {};
}
options = options || {};
if (condOrRefresh !== undefined) {
if (typeof condOrRefresh === 'boolean') { if (typeof condOrRefresh === 'boolean') {
actualRefresh = condOrRefresh; actualRefresh = condOrRefresh;
} else { } else {
@ -53,16 +72,15 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
actualRefresh = true; actualRefresh = true;
saveOnCache = false; saveOnCache = false;
} }
} else {
throw new Error('Method can be only called with one or two arguments');
} }
cb = cb || utils.createPromiseCallback();
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) { || actualRefresh) {
// It either doesn't hit the cache or refresh is required // It either doesn't hit the cache or refresh is required
var params = mergeQuery(actualCond, scopeParams, {nestedInclude: true}); var params = mergeQuery(actualCond, scopeParams, {nestedInclude: true});
var targetModel = this.targetModel(receiver); var targetModel = this.targetModel(receiver);
targetModel.find(params, function (err, data) { targetModel.find(params, options, function (err, data) {
if (!err && saveOnCache) { if (!err && saveOnCache) {
defineCachedRelations(self); defineCachedRelations(self);
self.__cachedRelations[name] = data; self.__cachedRelations[name] = data;
@ -73,6 +91,7 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
// Return from cache // Return from cache
cb(null, self.__cachedRelations[name]); cb(null, self.__cachedRelations[name]);
} }
return cb.promise;
} }
/** /**
@ -149,7 +168,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
var targetModel = definition.targetModel(this); var targetModel = definition.targetModel(this);
var self = this; var self = this;
var f = function(condOrRefresh, cb) { var f = function(condOrRefresh, options, cb) {
if (arguments.length === 0) { if (arguments.length === 0) {
if (typeof f.value === 'function') { if (typeof f.value === 'function') {
return f.value(self); return f.value(self);
@ -157,6 +176,18 @@ function defineScope(cls, targetClass, name, params, methods, options) {
return self.__cachedRelations[name]; return self.__cachedRelations[name];
} }
} else { } else {
if (typeof condOrRefresh === 'function'
&& options === undefined && cb === undefined) {
// customer.orders(cb)
cb = condOrRefresh;
options = {};
condOrRefresh = undefined;
} else if (typeof options === 'function' && cb === undefined) {
// customer.orders(condOrRefresh, cb);
cb = options;
options = {};
}
options = options || {}
// Check if there is a through model // Check if there is a through model
// see https://github.com/strongloop/loopback/issues/1076 // see https://github.com/strongloop/loopback/issues/1076
if (f._scope.collect && if (f._scope.collect &&
@ -169,11 +200,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
}; };
condOrRefresh = {}; condOrRefresh = {};
} }
if (arguments.length === 1) { return definition.related(self, f._scope, condOrRefresh, options, cb);
return definition.related(self, f._scope, condOrRefresh);
} else {
return definition.related(self, f._scope, condOrRefresh, cb);
}
} }
}; };
@ -186,23 +213,20 @@ function defineScope(cls, targetClass, name, params, methods, options) {
f._targetClass = i8n.camelize(f._scope.collect); f._targetClass = i8n.camelize(f._scope.collect);
} }
f.getAsync = function (cond, cb) { f.getAsync = function(condOrRefresh, options, cb) {
if (cb === undefined) { if (typeof condOrRefresh === 'function'
if (cond === undefined) { && options === undefined && cb === undefined) {
// getAsync() // customer.orders.getAsync(cb)
cb = utils.createPromiseCallback(); cb = condOrRefresh;
cond = true; options = {};
} else if (typeof cond !== 'function') { condOrRefresh = {};
// getAsync({where:{}}) } else if (typeof options === 'function' && cb === undefined) {
cb = utils.createPromiseCallback(); // customer.orders.getAsync(condOrRefresh, cb);
} else { cb = options;
// getAsync(function(){}) options = {};
cb = cond;
cond = true;
}
} }
definition.related(self, f._scope, cond, cb); options = options || {}
return cb.promise; return definition.related(self, f._scope, condOrRefresh, options, cb);
} }
f.build = build; f.build = build;
@ -298,13 +322,19 @@ function defineScope(cls, targetClass, name, params, methods, options) {
return new targetModel(data); return new targetModel(data);
} }
function create(data, cb) { function create(data, options, cb) {
if (typeof data === 'function') { if (typeof data === 'function' &&
options === undefined && cb === undefined) {
// create(cb)
cb = data; cb = data;
data = {}; data = {};
} else if (typeof options === 'function' && cb === undefined) {
// create(data, cb)
cb = options;
options = {};
} }
cb = cb || utils.createPromiseCallback(); options = options || {};
return this.build(data).save(cb); return this.build(data).save(options, cb);
} }
/* /*
@ -313,53 +343,108 @@ function defineScope(cls, targetClass, name, params, methods, options) {
- For every destroy call which results in an error - For every destroy call which results in an error
- If fetching the Elements on which destroyAll is called results in an error - If fetching the Elements on which destroyAll is called results in an error
*/ */
function destroyAll(where, cb) { function destroyAll(where, options, cb) {
if (typeof where === 'function') cb = where, where = {}; if (typeof where === 'function') {
cb = cb || utils.createPromiseCallback(); // destroyAll(cb)
cb = where;
where = {};
} else if (typeof options === 'function' && cb === undefined) {
// destroyAll(where, cb)
cb = options;
options = {};
}
options = options || {};
var targetModel = definition.targetModel(this._receiver); var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {}; var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, { where: where || {} }); var filter = mergeQuery({ where: scoped }, { where: where || {} });
return targetModel.destroyAll(filter.where, cb); return targetModel.destroyAll(filter.where, options, cb);
} }
function updateAll(where, data, cb) { function updateAll(where, data, options, cb) {
if (arguments.length === 2) { if (typeof data === 'function' &&
// Handle updateAll(data, cb) options === undefined && cb === undefined) {
// updateAll(data, cb)
cb = data; cb = data;
data = where; data = where;
where = {}; where = {};
options = {};
} else if (typeof options === 'function' && cb === undefined) {
// updateAll(where, data, cb)
cb = options;
options = {};
} }
options = options || {};
var targetModel = definition.targetModel(this._receiver); var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {}; var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, { where: where || {} }); var filter = mergeQuery({ where: scoped }, { where: where || {} });
targetModel.updateAll(filter.where, data, cb); return targetModel.updateAll(filter.where, data, options, cb);
} }
function findById(id, cb) { function findById(id, filter, options, cb) {
if (options === undefined && cb === undefined) {
if (typeof filter === 'function') {
// findById(id, cb)
cb = filter;
filter = {};
}
} else if (cb === undefined) {
if (typeof options === 'function') {
// findById(id, query, cb)
cb = options;
options = {};
if (typeof filter === 'object' && !(filter.include || filter.fields)) {
// If filter doesn't have include or fields, assuming it's options
options = filter;
filter = {};
}
}
}
options = options || {};
filter = filter || {};
var targetModel = definition.targetModel(this._receiver); var targetModel = definition.targetModel(this._receiver);
var idName = targetModel.definition.idName(); var idName = targetModel.definition.idName();
var filter = { where: {} }; var query = {where: {}};
filter.where[idName] = id; query.where[idName] = id;
this.findOne(filter, cb); query = mergeQuery(query, filter);
return this.findOne(query, options, cb);
} }
function findOne(filter, cb) { function findOne(filter, options, cb) {
if (typeof filter === 'function') cb = filter, filter = {}; if (typeof filter === 'function') {
// findOne(cb)
cb = filter;
filter = {};
options = {};
} else if (typeof options === 'function' && cb === undefined) {
// findOne(filter, cb)
cb = options;
options = {};
}
options = options || {};
var targetModel = definition.targetModel(this._receiver); var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {}; var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, filter || {}); filter = mergeQuery({ where: scoped }, filter || {});
targetModel.findOne(filter, cb); return targetModel.findOne(filter, options, cb);
} }
function count(where, cb) { function count(where, options, cb) {
if (typeof where === 'function') cb = where, where = {}; if (typeof where === 'function') {
cb = cb || utils.createPromiseCallback(); // count(cb)
cb = where;
where = {};
} else if (typeof options === 'function' && cb === undefined) {
// count(where, cb)
cb = options;
options = {};
}
options = options || {};
var targetModel = definition.targetModel(this._receiver); var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {}; var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, { where: where || {} }); var filter = mergeQuery({ where: scoped }, { where: where || {} });
return targetModel.count(filter.where, cb); return targetModel.count(filter.where, options, cb);
} }
return definition; return definition;