Merge pull request #514 from fabien/feature/relation-methods
Enable defineMethod for singular relations
This commit is contained in:
commit
2f0561a7a8
|
@ -90,6 +90,14 @@ function extendScopeMethods(definition, scopeMethods, ext) {
|
|||
return [].concat(customMethods || []);
|
||||
};
|
||||
|
||||
function bindRelationMethods(relation, relationMethod, definition) {
|
||||
var methods = definition.methods || {};
|
||||
Object.keys(methods).forEach(function(m) {
|
||||
if (typeof methods[m] !== 'function') return;
|
||||
relationMethod[m] = methods[m].bind(relation);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Relation definition class. Use to define relationships between models.
|
||||
* @param {Object} definition
|
||||
|
@ -120,6 +128,7 @@ function RelationDefinition(definition) {
|
|||
this.options = definition.options || {};
|
||||
this.scope = definition.scope;
|
||||
this.embed = definition.embed === true;
|
||||
this.methods = definition.methods || {};
|
||||
}
|
||||
|
||||
RelationDefinition.prototype.toJSON = function () {
|
||||
|
@ -159,13 +168,22 @@ RelationDefinition.prototype.defineMethod = function(name, fn) {
|
|||
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) {
|
||||
var method;
|
||||
if (definition.multiple) {
|
||||
var scope = this.modelFrom.scopes[this.name];
|
||||
if (!scope) throw new Error('Unknown relation scope: ' + this.name);
|
||||
method = scope.defineMethod(name, function() {
|
||||
var relation = new relationClass(definition, this);
|
||||
return fn.apply(relation, arguments);
|
||||
});
|
||||
} else {
|
||||
definition.methods[name] = fn;
|
||||
method = function() {
|
||||
var rel = this[relationName];
|
||||
return rel[name].apply(rel, arguments);
|
||||
}
|
||||
}
|
||||
if (method && fn.shared) {
|
||||
sharedMethod(definition, name, method, fn);
|
||||
modelFrom.prototype['__' + name + '__' + relationName] = method;
|
||||
}
|
||||
|
@ -1152,7 +1170,8 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
|||
properties: params.properties,
|
||||
scope: params.scope,
|
||||
options: params.options,
|
||||
polymorphic: polymorphic
|
||||
polymorphic: polymorphic,
|
||||
methods: params.methods
|
||||
});
|
||||
|
||||
// Define a property for the scope so that we have 'this' for the scoped methods
|
||||
|
@ -1169,6 +1188,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
|
|||
relationMethod.build = relation.build.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
}
|
||||
bindRelationMethods(relation, relationMethod, definition);
|
||||
return relationMethod;
|
||||
}
|
||||
});
|
||||
|
@ -1466,7 +1486,8 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
|||
properties: params.properties,
|
||||
scope: params.scope,
|
||||
options: params.options,
|
||||
polymorphic: polymorphic
|
||||
polymorphic: polymorphic,
|
||||
methods: params.methods
|
||||
});
|
||||
|
||||
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
|
||||
|
@ -1483,6 +1504,7 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
|
|||
relationMethod.update = relation.update.bind(relation);
|
||||
relationMethod.destroy = relation.destroy.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
bindRelationMethods(relation, relationMethod, definition);
|
||||
return relationMethod;
|
||||
}
|
||||
});
|
||||
|
@ -1740,7 +1762,8 @@ RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
|
|||
properties: params.properties,
|
||||
scope: params.scope,
|
||||
options: params.options,
|
||||
embed: true
|
||||
embed: true,
|
||||
methods: params.methods
|
||||
});
|
||||
|
||||
var opts = { type: modelTo };
|
||||
|
@ -1785,6 +1808,7 @@ RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
|
|||
relationMethod.destroy = relation.destroy.bind(relation);
|
||||
relationMethod.value = relation.embeddedValue.bind(relation);
|
||||
relationMethod._targetClass = definition.modelTo.modelName;
|
||||
bindRelationMethods(relation, relationMethod, definition);
|
||||
return relationMethod;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1617,12 +1617,30 @@ describe('relations', function () {
|
|||
(new Item).list.should.be.an.instanceOf(Function);
|
||||
|
||||
// syntax 2 (new)
|
||||
Fear.belongsTo('mind');
|
||||
Fear.belongsTo('mind', {
|
||||
methods: { check: function() { return true; } }
|
||||
});
|
||||
|
||||
Object.keys((new Fear).toObject()).should.containEql('mindId');
|
||||
(new Fear).mind.should.be.an.instanceOf(Function);
|
||||
// (new Fear).mind.build().should.be.an.instanceOf(Mind);
|
||||
});
|
||||
|
||||
it('should setup a custom method on accessor', function() {
|
||||
var rel = Fear.relations['mind'];
|
||||
rel.defineMethod('other', function() {
|
||||
return true;
|
||||
})
|
||||
});
|
||||
|
||||
it('should have setup a custom method on accessor', function() {
|
||||
var f = new Fear();
|
||||
f.mind.check.should.be.a.function;
|
||||
f.mind.check().should.be.true;
|
||||
f.mind.other.should.be.a.function;
|
||||
f.mind.other().should.be.true;
|
||||
});
|
||||
|
||||
it('can be used to query data', function (done) {
|
||||
List.hasMany('todos', {model: Item});
|
||||
db.automigrate(function () {
|
||||
|
@ -1823,11 +1841,29 @@ describe('relations', function () {
|
|||
});
|
||||
|
||||
it('can be declared using hasOne method', function () {
|
||||
Supplier.hasOne(Account, { properties: { name: 'supplierName' } });
|
||||
Supplier.hasOne(Account, {
|
||||
properties: { name: 'supplierName' },
|
||||
methods: { check: function() { return true; } }
|
||||
});
|
||||
Object.keys((new Account()).toObject()).should.containEql('supplierId');
|
||||
(new Supplier()).account.should.be.an.instanceOf(Function);
|
||||
});
|
||||
|
||||
it('should setup a custom method on accessor', function() {
|
||||
var rel = Supplier.relations['account'];
|
||||
rel.defineMethod('other', function() {
|
||||
return true;
|
||||
})
|
||||
});
|
||||
|
||||
it('should have setup a custom method on accessor', function() {
|
||||
var s = new Supplier();
|
||||
s.account.check.should.be.a.function;
|
||||
s.account.check().should.be.true;
|
||||
s.account.other.should.be.a.function;
|
||||
s.account.other().should.be.true;
|
||||
});
|
||||
|
||||
it('can be used to query data', function (done) {
|
||||
db.automigrate(function () {
|
||||
Supplier.create({name: 'Supplier 1'}, function (e, supplier) {
|
||||
|
@ -2213,7 +2249,8 @@ describe('relations', function () {
|
|||
|
||||
it('can be declared using embedsOne method', function (done) {
|
||||
Person.embedsOne(Passport, {
|
||||
default: {name: 'Anonymous'} // a bit contrived
|
||||
default: {name: 'Anonymous'}, // a bit contrived
|
||||
methods: { check: function() { return true; } }
|
||||
});
|
||||
Person.embedsOne(Address); // all by default
|
||||
db.automigrate(done);
|
||||
|
@ -2228,6 +2265,21 @@ describe('relations', function () {
|
|||
p.passportItem.destroy.should.be.a.function;
|
||||
});
|
||||
|
||||
it('should setup a custom method on accessor', function() {
|
||||
var rel = Person.relations['passportItem'];
|
||||
rel.defineMethod('other', function() {
|
||||
return true;
|
||||
})
|
||||
});
|
||||
|
||||
it('should have setup a custom method on accessor', function() {
|
||||
var p = new Person();
|
||||
p.passportItem.check.should.be.a.function;
|
||||
p.passportItem.check().should.be.true;
|
||||
p.passportItem.other.should.be.a.function;
|
||||
p.passportItem.other().should.be.true;
|
||||
});
|
||||
|
||||
it('should behave properly without default or being set', function (done) {
|
||||
var p = new Person();
|
||||
should.not.exist(p.address);
|
||||
|
|
Loading…
Reference in New Issue