Add support for hasOne

This commit is contained in:
Raymond Feng 2014-06-16 01:17:37 -07:00
parent 34c1998f04
commit 2db43c58e5
3 changed files with 181 additions and 1 deletions

View File

@ -656,3 +656,142 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
this.hasMany(modelFrom, modelTo, {as: params.as, through: params.through});
};
/**
* HasOne
* @param modelFrom
* @param modelTo
* @param params
*/
RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
params = params || {};
if ('string' === typeof modelTo) {
params.as = modelTo;
if (params.model) {
modelTo = params.model;
} else {
var modelToName = modelTo.toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
}
}
var pk = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', true);
var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName,
type: RelationTypes.hasOne,
modelFrom: modelFrom,
keyFrom: pk,
keyTo: fk,
modelTo: modelTo
});
modelFrom.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
// Define a property for the scope so that we have 'this' for the scoped methods
Object.defineProperty(modelFrom.prototype, relationName, {
enumerable: true,
configurable: true,
get: function() {
var relation = new HasOne(relationDef, this);
var relationMethod = relation.related.bind(relation)
relationMethod.create = relation.create.bind(relation);
relationMethod.build = relation.build.bind(relation);
return relationMethod;
}
});
};
HasOne.prototype.create = function(targetModelData, cb) {
var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk];
modelTo.create(targetModelData, function(err, targetModel) {
if(!err) {
cb && cb(err, targetModel);
} else {
cb && cb(err);
}
});
};
HasOne.prototype.build = function(targetModelData) {
var modelTo = this.definition.modelTo;
var pk = this.definition.keyFrom;
var fk = this.definition.keyTo;
targetModelData = targetModelData || {};
targetModelData[fk] = this.modelInstance[pk];
return new modelTo(targetModelData);
};
/**
* Define the method for the hasOne relation itself
* It will support one of the following styles:
* - order.customer(refresh, callback): Load the target model instance asynchronously
* - order.customer(customer): Synchronous setter of the target model instance
* - order.customer(): Synchronous getter of the target model instance
*
* @param refresh
* @param params
* @returns {*}
*/
HasOne.prototype.related = function (refresh, params) {
var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
var relationName = this.definition.name;
if (arguments.length === 1) {
params = refresh;
refresh = false;
} else if (arguments.length > 2) {
throw new Error('Method can\'t be called with more than two arguments');
}
var cachedValue;
if (!refresh && modelInstance.__cachedRelations
&& (modelInstance.__cachedRelations[relationName] !== undefined)) {
cachedValue = modelInstance.__cachedRelations[relationName];
}
if (params instanceof ModelBaseClass) { // acts as setter
params[fk] = modelInstance[pk];
modelInstance.__cachedRelations[relationName] = params;
} else if (typeof params === 'function') { // acts as async getter
var cb = params;
if (cachedValue === undefined) {
var query = {where: {}};
query.where[fk] = modelInstance[pk];
modelTo.findOne(query, function (err, inst) {
if (err) {
return cb(err);
}
if (!inst) {
return cb(null, null);
}
// Check if the foreign key matches the primary key
if (inst[fk] === modelInstance[pk]) {
cb(null, inst);
} else {
cb(new Error('Permission denied'));
}
});
return modelInstance[pk];
} else {
cb(null, cachedValue);
return cachedValue;
}
} else if (params === undefined) { // acts as sync getter
return modelInstance[pk];
} else { // setter
params[fk] = modelInstance[pk];
delete modelInstance.__cachedRelations[relationName];
}
};

View File

@ -157,3 +157,7 @@ RelationMixin.belongsTo = function (modelTo, params) {
RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) {
RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
};
RelationMixin.hasOne = function hasMany(modelTo, params) {
RelationDefinition.hasOne(this, modelTo, params);
};

View File

@ -1,7 +1,7 @@
// This test written in mocha+should.js
var should = require('./init.js');
var db, Book, Chapter, Author, Reader;
var db, Book, Chapter, Author, Reader, Publisher;
describe('relations', function () {
before(function (done) {
@ -180,6 +180,43 @@ describe('relations', function () {
});
describe('hasOne', function () {
var Supplier, Account;
before(function () {
db = getSchema();
Supplier = db.define('Supplier', {name: String});
Account = db.define('Account', {accountNo: String});
});
it('can be declared using hasOne method', function () {
Supplier.hasOne(Account);
Object.keys((new Account()).toObject()).should.include('supplierId');
(new Supplier()).account.should.be.an.instanceOf(Function);
});
it('can be used to query data', function (done) {
// Supplier.hasOne(Account);
db.automigrate(function () {
Supplier.create({name: 'Supplier 1'}, function (e, supplier) {
should.not.exist(e);
should.exist(supplier);
supplier.account.create({accountNo: 'a01'}, function (err, account) {
supplier.account(function (e, act) {
should.not.exist(e);
should.exist(act);
act.should.be.an.instanceOf(Account);
supplier.account().should.equal(act.id);
done();
});
});
});
});
});
});
describe('hasAndBelongsToMany', function () {
var Article, Tag, ArticleTag;
it('can be declared', function (done) {