Implement scope.defineMethod/relation.defineMethod
This commit is contained in:
parent
4cb22793e2
commit
710ad35b39
|
@ -63,12 +63,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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -201,6 +227,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
|
||||||
|
@ -555,6 +590,7 @@ 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) {
|
||||||
|
@ -993,7 +1029,7 @@ 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,
|
||||||
|
@ -1011,12 +1047,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 +1066,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) {
|
||||||
|
@ -1222,8 +1260,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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1268,7 +1305,7 @@ 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,
|
||||||
|
@ -1287,11 +1324,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 +1341,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1603,6 +1642,8 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
}, scopeMethods, definition.options);
|
}, scopeMethods, definition.options);
|
||||||
|
|
||||||
scopeDefinition.related = scopeMethods.related;
|
scopeDefinition.related = scopeMethods.related;
|
||||||
|
|
||||||
|
return definition;
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
||||||
|
@ -2017,6 +2058,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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2130,4 +2130,77 @@ describe('relations', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('custom relation/scope methods', function () {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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: 1, categoryName: 'Category A' },
|
||||||
|
{ name: 'Product 2', categoryId: 1, 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();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue