Polymorphic lookup from all registered dataSources

Polymorphic model lookup was previously limited to the same dataSource
as the modelFrom model, which turns out to be too restrictive. This was
uncovered by the use of a Transient model, not being able to lookup a
PersistedModel.
This commit is contained in:
Fabien Franzen 2014-09-04 17:31:53 +02:00
parent 07dbbd4224
commit 2c0ffee2d3
3 changed files with 45 additions and 15 deletions

View File

@ -4,6 +4,12 @@ exports.ModelBaseClass = require('./lib/model.js');
exports.GeoPoint = require('./lib/geo.js').GeoPoint; exports.GeoPoint = require('./lib/geo.js').GeoPoint;
exports.ValidationError = require('./lib/validations.js').ValidationError; exports.ValidationError = require('./lib/validations.js').ValidationError;
var dataSources = exports.dataSources = {};
exports.registerDataSource = function(ds) {
dataSources[ds.name] = ds;
};
exports.__defineGetter__('version', function () { exports.__defineGetter__('version', function () {
return require('./package.json').version; return require('./package.json').version;
}); });

View File

@ -4,6 +4,7 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var i8n = require('inflection'); var i8n = require('inflection');
var jdb = require('../');
var defineScope = require('./scope.js').defineScope; var defineScope = require('./scope.js').defineScope;
var mergeQuery = require('./scope.js').mergeQuery; var mergeQuery = require('./scope.js').mergeQuery;
var ModelBaseClass = require('./model.js'); var ModelBaseClass = require('./model.js');
@ -466,7 +467,7 @@ function findBelongsTo(modelFrom, modelTo, keyTo) {
* @param {String} modelName The model name * @param {String} modelName The model name
* @returns {*} The matching model class * @returns {*} The matching model class
*/ */
function lookupModel(models, modelName) { function lookupModel(models, modelName, internal) {
if(models[modelName]) { if(models[modelName]) {
return models[modelName]; return models[modelName];
} }
@ -476,6 +477,15 @@ function lookupModel(models, modelName) {
return models[name]; return models[name];
} }
} }
if (internal) return;
var keys = Object.keys(jdb.dataSources);
for (var k = 0; k < keys.length; k++) {
var ds = jdb.dataSources[keys[k]];
var models = ds.modelBuilder.models;
var model = lookupModel(models, modelName, true);
if (model) return model;
}
} }
function lookupModelTo(modelFrom, modelTo, params, singularize) { function lookupModelTo(modelFrom, modelTo, params, singularize) {
@ -503,9 +513,9 @@ function lookupModelTo(modelFrom, modelTo, params, singularize) {
* @param {Object|String} params Name of the polymorphic relation or params * @param {Object|String} params Name of the polymorphic relation or params
* @returns {Object} The normalized parameters * @returns {Object} The normalized parameters
*/ */
function polymorphicParams(params) { function polymorphicParams(params, as) {
if (typeof params === 'string') params = { as: params }; if (typeof params === 'string') params = { as: params };
if (typeof params.as !== 'string') params.as = 'reference'; // default if (typeof params.as !== 'string') params.as = as || 'reference'; // default
params.foreignKey = params.foreignKey || i8n.camelize(params.as + '_id', true); params.foreignKey = params.foreignKey || i8n.camelize(params.as + '_id', true);
params.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true); params.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true);
return params; return params;
@ -1079,21 +1089,23 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
var idName, relationName, fk; var idName, relationName, fk;
if (params.polymorphic) { if (params.polymorphic) {
relationName = params.as || (typeof modelTo === 'string' ? modelTo : null); // initially
if (params.polymorphic === true) { if (params.polymorphic === true) {
// modelTo arg will be the name of the polymorphic relation (string) // modelTo arg will be the name of the polymorphic relation (string)
polymorphic = polymorphicParams(modelTo); polymorphic = polymorphicParams(modelTo, relationName);
} else { } else {
polymorphic = polymorphicParams(params.polymorphic); polymorphic = polymorphicParams(params.polymorphic, relationName);
} }
modelTo = null; // will lookup dynamically modelTo = null; // will lookup dynamically
idName = params.idName || 'id'; idName = params.idName || 'id';
relationName = params.as || polymorphic.as; relationName = params.as || polymorphic.as; // finally
fk = polymorphic.foreignKey; fk = polymorphic.foreignKey;
discriminator = polymorphic.discriminator; discriminator = polymorphic.discriminator;
if (typeof polymorphic.idType === 'string') { // explicit key type if (polymorphic.idType) { // explicit key type
modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: polymorphic.idType, index: true }); modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: polymorphic.idType, index: true });
} else { // try to use the same foreign key type as modelFrom } else { // try to use the same foreign key type as modelFrom
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName); modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName);
@ -2208,10 +2220,7 @@ EmbedsMany.prototype.build = function(targetModelData) {
var assignId = (forceId || targetModelData[pk] === undefined); var assignId = (forceId || targetModelData[pk] === undefined);
if (assignId && typeof connector.generateId === 'function') { if (assignId && pkType === Number) {
var id = connector.generateId(modelTo.modelName, targetModelData, pk);
targetModelData[pk] = id;
} else if (assignId && pkType === Number) {
var ids = embeddedList.map(function(m) { var ids = embeddedList.map(function(m) {
return (typeof m[pk] === 'number' ? m[pk] : 0); return (typeof m[pk] === 'number' ? m[pk] : 0);
}); });
@ -2220,6 +2229,9 @@ EmbedsMany.prototype.build = function(targetModelData) {
} else { } else {
targetModelData[pk] = 1; targetModelData[pk] = 1;
} }
} else if (assignId && typeof connector.generateId === 'function') {
var id = connector.generateId(modelTo.modelName, targetModelData, pk);
targetModelData[pk] = id;
} }
this.definition.applyProperties(modelInstance, targetModelData); this.definition.applyProperties(modelInstance, targetModelData);

View File

@ -1,6 +1,7 @@
// This test written in mocha+should.js // This test written in mocha+should.js
var should = require('./init.js'); var should = require('./init.js');
var Schema = require('../').Schema; var jdb = require('../');
var Schema = jdb.Schema;
var db, tmp, Book, Chapter, Author, Reader; var db, tmp, Book, Chapter, Author, Reader;
var Category, Job; var Category, Job;
@ -2420,11 +2421,20 @@ describe('relations', function () {
before(function (done) { before(function (done) {
db = getSchema(); db = getSchema();
tmp = getTransientSchema();
// register here, so transient models
// can lookup related models (polymorphic)
jdb.registerDataSource(db);
Book = db.define('Book', {name: String}); Book = db.define('Book', {name: String});
Author = db.define('Author', {name: String}); Author = db.define('Author', {name: String});
Reader = db.define('Reader', {name: String}); Reader = db.define('Reader', {name: String});
Link = db.define('Link', {name: String, notes: String}); // generic model Link = tmp.define('Link', {
id: {type: Number, id: true},
name: String, notes: String
}); // generic model
Link.validatesPresenceOf('linkedId'); Link.validatesPresenceOf('linkedId');
Link.validatesPresenceOf('linkedType'); Link.validatesPresenceOf('linkedType');
@ -2438,13 +2448,15 @@ describe('relations', function () {
}); });
it('can be declared', function (done) { it('can be declared', function (done) {
var idType = db.connector.getDefaultIdType();
Book.embedsMany(Link, { as: 'people', Book.embedsMany(Link, { as: 'people',
polymorphic: 'linked', polymorphic: 'linked',
scope: { include: 'linked' } scope: { include: 'linked' }
}); });
Link.belongsTo('linked', { Link.belongsTo('linked', {
polymorphic: true, // needs unique auto-id polymorphic: { idType: idType }, // native type
properties: { name: 'name' }, // denormalized properties: { name: 'name' }, // denormalized
options: { invertProperties: true } options: { invertProperties: true }
}); });
db.automigrate(done); db.automigrate(done);