From 566da386aef6aa7eabaf456eb11d143498e086fc Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 Nov 2013 13:09:09 -0800 Subject: [PATCH 1/3] Refactor the relation handling and enable it with attach --- lib/datasource.js | 223 ++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 108 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index 8c4ada7c..a7f9005f 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -310,6 +310,108 @@ function isModelClass(cls) { return cls.prototype instanceof ModelBaseClass; } +/*! + * Define relations for the model class from the relations object + * @param modelClass + * @param relations + */ +DataSource.prototype.defineRelations = function(modelClass, relations) { + + // Create a function for the closure in the loop + var createListener = function (name, relation, targetModel, throughModel) { + if (targetModel && targetModel.settings.unresolved) { + targetModel.once('defined', function (model) { + // Check if the through model doesn't exist or resolved + if (!throughModel || !throughModel.settings.unresolved) { + // The target model is resolved + var params = { + foreignKey: relation.foreignKey, + as: name, + model: model + }; + if (throughModel) { + params.through = throughModel; + } + modelClass[relation.type].call(modelClass, name, params); + } + }); + } + if (throughModel && throughModel.settings.unresolved) { + // Set up a listener to the through model + throughModel.once('defined', function (model) { + if (!targetModel.settings.unresolved) { + // The target model is resolved + var params = { + foreignKey: relation.foreignKey, + as: name, + model: targetModel, + through: model + }; + modelClass[relation.type].call(modelClass, name, params); + } + }); + } + }; + + // Set up the relations + if (relations) { + for (var rn in relations) { + var r = relations[rn]; + assert(['belongsTo', 'hasMany', 'hasAndBelongsToMany'].indexOf(r.type) !== -1, "Invalid relation type: " + r.type); + var targetModel = isModelClass(r.model) ? r.model : this.models[r.model]; + if (!targetModel) { + // The target model doesn't exist, let create a place holder for it + targetModel = this.define(r.model, {}, {unresolved: true}); + } + var throughModel = null; + if (r.through) { + throughModel = isModelClass(r.through) ? r.through : this.models[r.through]; + if (!throughModel) { + // The through model doesn't exist, let create a place holder for it + throughModel = this.define(r.through, {}, {unresolved: true}); + } + } + if (targetModel.settings.unresolved || (throughModel && throughModel.settings.unresolved)) { + // Create a listener to defer the relation set up + createListener(rn, r, targetModel, throughModel); + } else { + // The target model is resolved + var params = { + foreignKey: r.foreignKey, + as: rn, + model: targetModel + }; + if (throughModel) { + params.through = throughModel; + } + modelClass[r.type].call(modelClass, rn, params); + } + } + } +}; + +/*! + * Set up the data access functions from the data source + * @param modelClass + * @param settings + */ +DataSource.prototype.setupDataAccess = function (modelClass, settings) { +// add data access objects + this.mixin(modelClass); + + var relations = settings.relationships || settings.relations; + this.defineRelations(modelClass, relations); + + if (this.connector && this.connector.define) { + // pass control to connector + this.connector.define({ + model: modelClass, + properties: modelClass.definition.properties, + settings: settings + }); + } +}; + /** * Define a model class * @@ -370,99 +472,13 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define } } - var NewClass = ModelBuilder.prototype.define.call(this, className, properties, settings); + var modelClass = ModelBuilder.prototype.define.call(this, className, properties, settings); if(settings.unresolved) { - return NewClass; + return modelClass; } - - // add data access objects - this.mixin(NewClass); - - if(this.connector && this.connector.define) { - // pass control to connector - this.connector.define({ - model: NewClass, - properties: NewClass.definition.properties, - settings: settings - }); - } - - var relations = settings.relationships || settings.relations; - - // Create a function for the closure in the loop - var createListener = function (name, relation, targetModel, throughModel) { - if (targetModel && targetModel.settings.unresolved) { - targetModel.once('defined', function (model) { - // Check if the through model doesn't exist or resolved - if (!throughModel || !throughModel.settings.unresolved) { - // The target model is resolved - var params = { - foreignKey: relation.foreignKey, - as: name, - model: model - }; - if (throughModel) { - params.through = throughModel; - } - NewClass[relation.type].call(NewClass, name, params); - } - }); - } - if (throughModel && throughModel.settings.unresolved) { - // Set up a listener to the through model - throughModel.once('defined', function (model) { - if (!targetModel.settings.unresolved) { - // The target model is resolved - var params = { - foreignKey: relation.foreignKey, - as: name, - model: targetModel, - through: model - }; - NewClass[relation.type].call(NewClass, name, params); - } - }); - } - }; - - // Set up the relations - if (relations) { - for (var rn in relations) { - var r = relations[rn]; - assert(['belongsTo', 'hasMany', 'hasAndBelongsToMany'].indexOf(r.type) !== -1, "Invalid relation type: " + r.type); - var targetModel = isModelClass(r.model) ? r.model : this.models[r.model]; - if(!targetModel) { - // The target model doesn't exist, let create a place holder for it - targetModel = this.define(r.model, {}, {unresolved: true}); - } - var throughModel = null; - if(r.through) { - throughModel = isModelClass(r.through) ? r.through : this.models[r.through]; - if(!throughModel) { - // The through model doesn't exist, let create a place holder for it - throughModel = this.define(r.through, {}, {unresolved: true}); - } - } - if(targetModel.settings.unresolved || (throughModel && throughModel.settings.unresolved)) { - // Create a listener to defer the relation set up - createListener(rn, r, targetModel, throughModel); - } else { - // The target model is resolved - var params = { - foreignKey: r.foreignKey, - as: rn, - model: targetModel - }; - if(throughModel) { - params.through = throughModel; - } - NewClass[r.type].call(NewClass, rn, params); - } - } - } - - return NewClass; + this.setupDataAccess(modelClass, settings); + return modelClass; }; @@ -510,36 +526,27 @@ DataSource.prototype.mixin = function (ModelCtor) { /** * Attach an existing model to a data source. * - * @param {Function} ModelCtor The model constructor + * @param {Function} modelClass The model constructor */ -DataSource.prototype.attach = function (ModelCtor) { - if(ModelCtor.dataSource === this) { +DataSource.prototype.attach = function (modelClass) { + if(modelClass.dataSource === this) { // Already attached to the data source return; } - var className = ModelCtor.modelName; - var properties = ModelCtor.dataSource.definitions[className].properties; - var settings = ModelCtor.dataSource.definitions[className].settings; + var className = modelClass.modelName; + var properties = modelClass.dataSource.definitions[className].properties; + var settings = modelClass.dataSource.definitions[className].settings; // redefine the dataSource - ModelCtor.dataSource = this; + modelClass.dataSource = this; // add to def var def = new ModelDefinition(this, className, properties, settings); def.build(); this.definitions[className] = def; - this.models[className] = ModelCtor; + this.models[className] = modelClass; - this.mixin(ModelCtor); - - if(this.connector && this.connector.define) { - // pass control to connector - this.connector.define({ - model: ModelCtor, - properties: properties, - settings: settings - }); - } + this.setupDataAccess(modelClass, settings); return this; }; From 89a30e7edd4a5358c677c9a6e25bb10ee55d6e3a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 Nov 2013 13:28:18 -0800 Subject: [PATCH 2/3] Add a test case for relations during attach --- test/loopback-dl.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 3421176d..1537132d 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -530,6 +530,20 @@ describe('Load models with relations', function () { done(); }); + it('should set up relations after attach', function (done) { + var ds = new DataSource('memory'); + var modelBuilder = new ModelBuilder(); + + var Post = modelBuilder.define('Post', {userId: Number, content: String}); + var User = modelBuilder.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}}); + + assert(!User.relations['posts']); + Post.attachTo(ds); + User.attachTo(ds); + assert(User.relations['posts']); + done(); + }); + }); From 580ce14f0ddec13ccb573f150ebb1e056ca9ce94 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 Nov 2013 13:30:54 -0800 Subject: [PATCH 3/3] Extract the relation types --- lib/datasource.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/datasource.js b/lib/datasource.js index a7f9005f..be663cbf 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -310,6 +310,8 @@ function isModelClass(cls) { return cls.prototype instanceof ModelBaseClass; } +DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany']; + /*! * Define relations for the model class from the relations object * @param modelClass @@ -357,7 +359,7 @@ DataSource.prototype.defineRelations = function(modelClass, relations) { if (relations) { for (var rn in relations) { var r = relations[rn]; - assert(['belongsTo', 'hasMany', 'hasAndBelongsToMany'].indexOf(r.type) !== -1, "Invalid relation type: " + r.type); + assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type); var targetModel = isModelClass(r.model) ? r.model : this.models[r.model]; if (!targetModel) { // The target model doesn't exist, let create a place holder for it