From 68cf633795788084b198e1b348e7dff3e3065cd5 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 5 Nov 2013 09:29:24 -0800 Subject: [PATCH] Add support for hasMany-through and more tests --- lib/datasource.js | 72 +++++++++++++++++++++++++++++++--------- test/loopback-dl.test.js | 42 +++++++++++++++++++++++ 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index 3972e9c9..8c4ada7c 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -12,6 +12,7 @@ var EventEmitter = require('events').EventEmitter; var util = require('util'); var path = require('path'); var fs = require('fs'); +var assert = require('assert'); var async = require('async'); var existsSync = fs.existsSync || path.existsSync; @@ -302,6 +303,13 @@ DataSource.prototype.setup = function(name, settings) { }; }; +function isModelClass(cls) { + if(!cls) { + return false; + } + return cls.prototype instanceof ModelBaseClass; +} + /** * Define a model class * @@ -383,33 +391,62 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define var relations = settings.relationships || settings.relations; // Create a function for the closure in the loop - var createListener = function(name, relation, targetModel) { - targetModel.once('defined', function(model) { - // The target model is resolved - var params = { - foreignKey: relation.foreignKey, - as: name, - model: model - }; - NewClass[relation.type].call(NewClass, name, params); - }); + 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]; - if (!r.type) { - throw new Error('Relation type is required for ' + r); - } - var targetModel = this.models[r.model]; + 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}); } - if(targetModel.settings.unresolved) { + 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); + createListener(rn, r, targetModel, throughModel); } else { // The target model is resolved var params = { @@ -417,6 +454,9 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define as: rn, model: targetModel }; + if(throughModel) { + params.through = throughModel; + } NewClass[r.type].call(NewClass, rn, params); } } diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 62cc4cd1..3421176d 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -444,6 +444,10 @@ describe('Load models with relations', function () { var ds = new DataSource('memory'); var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}, accounts: {type: 'hasMany', model: 'Account'}}}); + + assert(!User.relations['posts']); + assert(!User.relations['accounts']); + var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); var Account = ds.define('Account', {userId: Number, type: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); @@ -489,6 +493,44 @@ describe('Load models with relations', function () { }); + it('should throw if the relation type is invalid', function (done) { + var ds = new DataSource('memory'); + + var Post = ds.define('Post', {userId: Number, content: String}); + + try { + var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasXYZ', model: 'Post'}}}); + } catch (e) { + done(); + } + + }); + + it('should handle hasMany through', function (done) { + var ds = new DataSource('memory'); + var Physician = ds.createModel('Physician', { + name: String + }, {relations: {patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'}}}); + + var Patient = ds.createModel('Patient', { + name: String + }, {relations: {physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'}}}); + + assert(!Physician.relations['patients']); // Appointment hasn't been resolved yet + assert(!Patient.relations['physicians']); // Appointment hasn't been resolved yet + + var Appointment = ds.createModel('Appointment', { + physicianId: Number, + patientId: Number, + appointmentDate: Date + }, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}}); + + assert(Physician.relations['patients']); + assert(Patient.relations['physicians']); + done(); + }); + + }); describe('Load models from json', function () {