Merge pull request #452 from partap/promises-related

Promisify model relation methods
This commit is contained in:
Miroslav Bajtoš 2015-04-07 11:41:55 +02:00
commit 2bdcce0d96
3 changed files with 1511 additions and 20 deletions

View File

@ -758,9 +758,12 @@ HasMany.prototype.findById = function (fkId, cb) {
filter.where[idName] = fkId;
filter.where[fk] = modelInstance[pk];
cb = cb || utils.createPromiseCallback();
if (filter.where[fk] === undefined) {
// Foreign key is undefined
return process.nextTick(cb);
process.nextTick(cb);
return cb.promise;
}
this.definition.applyScope(modelInstance, filter);
@ -784,6 +787,7 @@ HasMany.prototype.findById = function (fkId, cb) {
cb(err);
}
});
return cb.promise;
};
/**
@ -795,6 +799,8 @@ HasMany.prototype.exists = function (fkId, cb) {
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
cb = cb || utils.createPromiseCallback();
this.findById(fkId, function (err, inst) {
if (err) {
@ -810,6 +816,7 @@ HasMany.prototype.exists = function (fkId, cb) {
cb(null, false);
}
});
return cb.promise;
};
/**
@ -818,12 +825,14 @@ HasMany.prototype.exists = function (fkId, cb) {
* @param {Function} cb The callback function
*/
HasMany.prototype.updateById = function (fkId, data, cb) {
cb = cb || utils.createPromiseCallback();
this.findById(fkId, function (err, inst) {
if (err) {
return cb && cb(err);
}
inst.updateAttributes(data, cb);
});
return cb.promise;
};
/**
@ -832,6 +841,7 @@ HasMany.prototype.updateById = function (fkId, data, cb) {
* @param {Function} cb The callback function
*/
HasMany.prototype.destroyById = function (fkId, cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
this.findById(fkId, function(err, inst) {
if (err) {
@ -840,6 +850,7 @@ HasMany.prototype.destroyById = function (fkId, cb) {
self.removeFromCache(inst[fkId]);
inst.destroy(cb);
});
return cb.promise;
};
var throughKeys = function(definition) {
@ -875,6 +886,8 @@ HasManyThrough.prototype.findById = function (fkId, cb) {
var modelInstance = this.modelInstance;
var modelThrough = this.definition.modelThrough;
cb = cb || utils.createPromiseCallback();
self.exists(fkId, function (err, exists) {
if (err || !exists) {
if (!err) {
@ -897,6 +910,7 @@ HasManyThrough.prototype.findById = function (fkId, cb) {
cb(err, inst);
});
});
return cb.promise;
};
/**
@ -911,6 +925,8 @@ HasManyThrough.prototype.destroyById = function (fkId, cb) {
var modelInstance = this.modelInstance;
var modelThrough = this.definition.modelThrough;
cb = cb || utils.createPromiseCallback();
self.exists(fkId, function (err, exists) {
if (err || !exists) {
if (!err) {
@ -928,6 +944,7 @@ HasManyThrough.prototype.destroyById = function (fkId, cb) {
modelTo.deleteById(fkId, cb);
});
});
return cb.promise;
};
// Create an instance of the target model and connect it to the instance of
@ -942,7 +959,7 @@ HasManyThrough.prototype.create = function create(data, done) {
done = data;
data = {};
}
done = done || function(){};
done = done || utils.createPromiseCallback();
var modelInstance = this.modelInstance;
@ -984,6 +1001,7 @@ HasManyThrough.prototype.create = function create(data, done) {
else
async.map(to, createRelation, done);
});
return done.promise;
};
@ -1005,6 +1023,9 @@ HasManyThrough.prototype.add = function (acInst, data, done) {
}
var query = {};
data = data || {};
done = done || utils.createPromiseCallback();
// The primary key for the target model
var pk2 = definition.modelTo.definition.idName();
@ -1033,6 +1054,7 @@ HasManyThrough.prototype.add = function (acInst, data, done) {
}
done(err, ac);
});
return done.promise;
};
/**
@ -1060,9 +1082,12 @@ HasManyThrough.prototype.exists = function (acInst, done) {
definition.applyScope(this.modelInstance, filter);
done = done || utils.createPromiseCallback();
modelThrough.count(filter.where, function(err, ac) {
done(err, ac > 0);
});
return done.promise;
};
/**
@ -1091,12 +1116,15 @@ HasManyThrough.prototype.remove = function (acInst, done) {
definition.applyScope(this.modelInstance, filter);
done = done || utils.createPromiseCallback();
modelThrough.deleteAll(filter.where, function (err) {
if (!err) {
self.removeFromCache(query[fk2]);
}
done(err);
});
return done.promise;
};
@ -1186,6 +1214,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
get: function() {
var relation = new BelongsTo(definition, this);
var relationMethod = relation.related.bind(relation);
relationMethod.getAsync = relation.getAsync.bind(relation);
relationMethod.update = relation.update.bind(relation);
relationMethod.destroy = relation.destroy.bind(relation);
if (!polymorphic) {
@ -1221,6 +1250,7 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
cb = targetModelData;
targetModelData = {};
}
cb = cb || utils.createPromiseCallback();
this.definition.applyProperties(modelInstance, targetModelData || {});
@ -1241,6 +1271,7 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
cb && cb(err);
}
});
return cb.promise;
};
BelongsTo.prototype.build = function(targetModelData) {
@ -1250,6 +1281,7 @@ BelongsTo.prototype.build = function(targetModelData) {
};
BelongsTo.prototype.update = function (targetModelData, cb) {
cb = cb || utils.createPromiseCallback();
var definition = this.definition;
this.fetch(function(err, inst) {
if (inst instanceof ModelBaseClass) {
@ -1259,12 +1291,16 @@ BelongsTo.prototype.update = function (targetModelData, cb) {
+ ' is empty'));
}
});
return cb.promise;
};
BelongsTo.prototype.destroy = function (cb) {
var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance;
var fk = this.definition.keyFrom;
cb = cb || utils.createPromiseCallback();
this.fetch(function(err, targetModel) {
if (targetModel instanceof ModelBaseClass) {
modelInstance[fk] = null;
@ -1277,6 +1313,7 @@ BelongsTo.prototype.destroy = function (cb) {
+ ' is empty'));
}
});
return cb.promise;
};
/**
@ -1394,6 +1431,21 @@ BelongsTo.prototype.related = function (refresh, params) {
}
};
/**
* Define a Promise-based method for the belongsTo relation itself
* - order.customer.get(cb): Load the target model instance asynchronously
*
* @param {Function} cb Callback of the form function (err, inst)
* @returns {Promise | Undefined} returns promise if callback is omitted
*/
BelongsTo.prototype.getAsync = function (cb) {
cb = cb || utils.createPromiseCallback();
this.related(true, cb);
return cb.promise;
}
/**
* A hasAndBelongsToMany relation creates a direct many-to-many connection with
* another model, with no intervening model. For example, if your application
@ -1504,6 +1556,7 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
get: function() {
var relation = new HasOne(definition, this);
var relationMethod = relation.related.bind(relation)
relationMethod.getAsync = relation.getAsync.bind(relation);
relationMethod.create = relation.create.bind(relation);
relationMethod.build = relation.build.bind(relation);
relationMethod.update = relation.update.bind(relation);
@ -1559,6 +1612,8 @@ HasOne.prototype.create = function (targetModelData, cb) {
targetModelData = {};
}
targetModelData = targetModelData || {};
cb = cb || utils.createPromiseCallback();
targetModelData[fk] = modelInstance[pk];
var query = {where: {}};
query.where[fk] = targetModelData[fk];
@ -1579,9 +1634,11 @@ HasOne.prototype.create = function (targetModelData, cb) {
+ modelTo.modelName));
}
});
return cb.promise;
};
HasOne.prototype.update = function(targetModelData, cb) {
cb = cb || utils.createPromiseCallback();
var definition = this.definition;
var fk = this.definition.keyTo;
this.fetch(function(err, targetModel) {
@ -1593,9 +1650,11 @@ HasOne.prototype.update = function(targetModelData, cb) {
+ ' is empty'));
}
});
return cb.promise;
};
HasOne.prototype.destroy = function (cb) {
cb = cb || utils.createPromiseCallback();
this.fetch(function(err, targetModel) {
if (targetModel instanceof ModelBaseClass) {
targetModel.destroy(cb);
@ -1604,6 +1663,7 @@ HasOne.prototype.destroy = function (cb) {
+ ' is empty'));
}
});
return cb.promise;
};
/**
@ -1625,6 +1685,7 @@ HasMany.prototype.create = function (targetModelData, cb) {
targetModelData = {};
}
targetModelData = targetModelData || {};
cb = cb || utils.createPromiseCallback();
var fkAndProps = function(item) {
item[fk] = modelInstance[pk];
@ -1650,6 +1711,7 @@ HasMany.prototype.create = function (targetModelData, cb) {
cb && cb(err);
}
});
return cb.promise;
};
/**
* Build a target model instance
@ -1741,6 +1803,20 @@ HasOne.prototype.related = function (refresh, params) {
}
};
/**
* Define a Promise-based method for the hasOne relation itself
* - order.customer.getAsync(cb): Load the target model instance asynchronously
*
* @param {Function} cb Callback of the form function (err, inst)
* @returns {Promise | Undefined} Returns promise if cb is omitted
*/
HasOne.prototype.getAsync = function (cb) {
cb = cb || utils.createPromiseCallback();
this.related(true, cb);
return cb.promise;
};
RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params);
@ -1896,6 +1972,7 @@ EmbedsOne.prototype.create = function (targetModelData, cb) {
}
targetModelData = targetModelData || {};
cb = cb || utils.createPromiseCallback();
var inst = this.callScopeMethod('build', targetModelData);
@ -1928,6 +2005,7 @@ EmbedsOne.prototype.create = function (targetModelData, cb) {
updateEmbedded();
}
}
return cb.promise;
};
EmbedsOne.prototype.build = function (targetModelData) {
@ -1970,25 +2048,29 @@ EmbedsOne.prototype.update = function (targetModelData, cb) {
var embeddedInstance = modelInstance[propertyName];
if (embeddedInstance instanceof modelTo) {
embeddedInstance.setAttributes(data);
cb = cb || utils.createPromiseCallback();
if (typeof cb === 'function') {
modelInstance.save(function(err, inst) {
cb(err, inst ? inst[propertyName] : embeddedInstance);
});
}
} else if (!embeddedInstance && cb) {
this.callScopeMethod('create', data, cb);
return this.callScopeMethod('create', data, cb);
} else if (!embeddedInstance) {
this.callScopeMethod('build', data);
return this.callScopeMethod('build', data);
}
return cb.promise;
};
EmbedsOne.prototype.destroy = function (cb) {
var modelInstance = this.modelInstance;
var propertyName = this.definition.keyFrom;
modelInstance.unsetAttribute(propertyName, true);
cb = cb || utils.createPromiseCallback();
modelInstance.save(function (err, result) {
cb && cb(err, result);
});
return cb.promise;
};
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) {
@ -2386,6 +2468,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
targetModelData = {};
}
targetModelData = targetModelData || {};
cb = cb || utils.createPromiseCallback();
var embeddedList = this.embeddedList();
@ -2420,6 +2503,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
updateEmbedded();
}
}
return cb.promise;
};
EmbedsMany.prototype.build = function(targetModelData) {
@ -2478,6 +2562,7 @@ EmbedsMany.prototype.add = function (acInst, data, cb) {
cb = data;
data = {};
}
cb = cb || utils.createPromiseCallback();
var self = this;
var definition = this.definition;
@ -2513,6 +2598,7 @@ EmbedsMany.prototype.add = function (acInst, data, cb) {
cb(null, null);
}
});
return cb.promise;
};
/**
@ -2543,6 +2629,8 @@ EmbedsMany.prototype.remove = function (acInst, cb) {
belongsTo.applyScope(modelInstance, filter);
cb = cb || utils.createPromiseCallback();
modelInstance[definition.name](filter, function(err, items) {
if (err) return cb(err);
@ -2554,6 +2642,7 @@ EmbedsMany.prototype.remove = function (acInst, cb) {
cb(err);
});
});
return cb.promise;
};
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) {
@ -2654,6 +2743,7 @@ ReferencesMany.prototype.related = function(receiver, scopeParams, condOrRefresh
var actualCond = {};
var actualRefresh = false;
if (arguments.length === 3) {
cb = condOrRefresh;
} else if (arguments.length === 4) {
@ -2696,6 +2786,8 @@ ReferencesMany.prototype.findById = function (fkId, cb) {
this.definition.applyScope(modelInstance, filter);
cb = cb || utils.createPromiseCallback();
modelTo.findByIds(ids, filter, function (err, instances) {
if (err) {
return cb(err);
@ -2722,6 +2814,7 @@ ReferencesMany.prototype.findById = function (fkId, cb) {
cb(err);
}
});
return cb.promise;
};
ReferencesMany.prototype.exists = function (fkId, cb) {
@ -2729,7 +2822,10 @@ ReferencesMany.prototype.exists = function (fkId, cb) {
var ids = this.modelInstance[fk] || [];
var currentIds = ids.map(function(id) { return id.toString(); });
var fkId = (fkId || '').toString(); // mongodb
cb = cb || utils.createPromiseCallback();
process.nextTick(function() { cb(null, currentIds.indexOf(fkId) > -1) });
return cb.promise;
};
ReferencesMany.prototype.updateById = function (fkId, data, cb) {
@ -2737,27 +2833,33 @@ ReferencesMany.prototype.updateById = function (fkId, data, cb) {
cb = data;
data = {};
}
cb = cb || utils.createPromiseCallback();
this.findById(fkId, function(err, inst) {
if (err) return cb(err);
inst.updateAttributes(data, cb);
});
return cb.promise;
};
ReferencesMany.prototype.destroyById = function (fkId, cb) {
var self = this;
cb = cb || utils.createPromiseCallback();
this.findById(fkId, function(err, inst) {
if (err) return cb(err);
self.remove(inst, function(err, ids) {
inst.destroy(cb);
});
});
return cb.promise;
};
ReferencesMany.prototype.at = function (index, cb) {
var fk = this.definition.keyFrom;
var ids = this.modelInstance[fk] || [];
cb = cb || utils.createPromiseCallback();
this.findById(ids[index], cb);
return cb.promise;
};
ReferencesMany.prototype.create = function (targetModelData, cb) {
@ -2774,6 +2876,7 @@ ReferencesMany.prototype.create = function (targetModelData, cb) {
targetModelData = {};
}
targetModelData = targetModelData || {};
cb = cb || utils.createPromiseCallback();
var ids = modelInstance[fk] || [];
@ -2799,6 +2902,7 @@ ReferencesMany.prototype.create = function (targetModelData, cb) {
cb(err, inst);
});
});
return cb.promise;
};
ReferencesMany.prototype.build = function(targetModelData) {
@ -2843,6 +2947,8 @@ ReferencesMany.prototype.add = function (acInst, cb) {
});
};
cb = cb || utils.createPromiseCallback();
if (acInst instanceof modelTo) {
insert(acInst, cb);
} else {
@ -2856,6 +2962,7 @@ ReferencesMany.prototype.add = function (acInst, cb) {
insert(inst, cb);
});
}
return cb.promise;
};
/**
@ -2876,6 +2983,8 @@ ReferencesMany.prototype.remove = function (acInst, cb) {
var id = (acInst instanceof definition.modelTo) ? acInst[pk] : acInst;
id = id.toString();
cb = cb || utils.createPromiseCallback();
var index = currentIds.indexOf(id);
if (index > -1) {
ids.splice(index, 1);
@ -2885,4 +2994,5 @@ ReferencesMany.prototype.remove = function (acInst, cb) {
} else {
process.nextTick(function() { cb(null, ids); });
}
return cb.promise;
};

View File

@ -39,7 +39,7 @@ ScopeDefinition.prototype.targetModel = function(receiver) {
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
var name = this.name;
var self = receiver;
var actualCond = {};
var actualRefresh = false;
var saveOnCache = true;
@ -56,7 +56,7 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
} else {
throw new Error('Method can be only called with one or two arguments');
}
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or refresh is required
@ -148,7 +148,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
get: function () {
var targetModel = definition.targetModel(this);
var self = this;
var f = function(condOrRefresh, cb) {
if (arguments.length === 0) {
if (typeof f.value === 'function') {
@ -176,16 +176,35 @@ function defineScope(cls, targetClass, name, params, methods, options) {
}
}
};
f._receiver = this;
f._scope = typeof definition.params === 'function' ?
definition.params.call(self) : definition.params;
f._targetClass = targetModel.modelName;
if (f._scope.collect) {
f._targetClass = i8n.camelize(f._scope.collect);
}
f.getAsync = function (cond, cb) {
if (cb === undefined) {
if (cond === undefined) {
// getAsync()
cb = utils.createPromiseCallback();
cond = true;
} else if (typeof cond !== 'function') {
// getAsync({where:{}})
cb = utils.createPromiseCallback();
} else {
// getAsync(function(){})
cb = cond;
cond = true;
}
}
definition.related(self, f._scope, cond, cb);
return cb.promise;
}
f.build = build;
f.create = create;
f.updateAll = updateAll;
@ -261,7 +280,7 @@ function defineScope(cls, targetClass, name, params, methods, options) {
};
cls['__findOne__' + name] = fn_findOne;
var fn_count = function (cb) {
var f = this[name].count;
f.apply(this[name], arguments);
@ -284,9 +303,10 @@ function defineScope(cls, targetClass, name, params, methods, options) {
cb = data;
data = {};
}
this.build(data).save(cb);
cb = cb || utils.createPromiseCallback();
return this.build(data).save(cb);
}
/*
Callback
- The callback will be called after all elements are destroyed
@ -295,10 +315,12 @@ function defineScope(cls, targetClass, name, params, methods, options) {
*/
function destroyAll(where, cb) {
if (typeof where === 'function') cb = where, where = {};
cb = cb || utils.createPromiseCallback();
var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, { where: where || {} });
targetModel.destroyAll(filter.where, cb);
return targetModel.destroyAll(filter.where, cb);
}
function updateAll(where, data, cb) {
@ -332,11 +354,13 @@ function defineScope(cls, targetClass, name, params, methods, options) {
function count(where, cb) {
if (typeof where === 'function') cb = where, where = {};
cb = cb || utils.createPromiseCallback();
var targetModel = definition.targetModel(this._receiver);
var scoped = (this._scope && this._scope.where) || {};
var filter = mergeQuery({ where: scoped }, { where: where || {} });
targetModel.count(filter.where, cb);
}
return targetModel.count(filter.where, cb);
}
return definition;
}

File diff suppressed because it is too large Load Diff