From fe74c8019ccde392cd3e2257e6610be5f815d427 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 12 Sep 2014 14:25:35 -0700 Subject: [PATCH] Tidy up model building from data sources See https://github.com/strongloop/loopback/issues/560 --- lib/datasource.js | 38 ++++++++++++++++---- lib/introspection.js | 6 ++-- lib/model-builder.js | 9 +++-- test/introspection.test.js | 72 ++++++++++++++++++++++++++------------ test/loopback-dl.test.js | 51 +++++++++++++++++---------- 5 files changed, 125 insertions(+), 51 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index edb1d380..572c1276 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -1427,6 +1427,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { */ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb) { var self = this; + options = options || {}; this.discoverSchemas(modelName, options, function (err, schemas) { if (err) { cb && cb(err, schemas); @@ -1436,14 +1437,16 @@ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb) var schemaList = []; for (var s in schemas) { var schema = schemas[s]; + if (options.base) { + schema.options = schema.options || {}; + schema.options.base = options.base; + } schemaList.push(schema); } - var models = self.modelBuilder.buildModels(schemaList); - // Now attach the models to the data source - for (var m in models) { - models[m].attachTo(self); - } + var models = self.modelBuilder.buildModels(schemaList, + self.createModel.bind(self)); + cb && cb(err, models); }); }; @@ -1462,18 +1465,41 @@ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb) * @param {Object} [options] The options */ DataSource.prototype.discoverAndBuildModelsSync = function (modelName, options) { + options = options || {}; var schemas = this.discoverSchemasSync(modelName, options); var schemaList = []; for (var s in schemas) { var schema = schemas[s]; + if (options.base) { + schema.options = schema.options || {}; + schema.options.base = options.base; + } schemaList.push(schema); } - var models = this.modelBuilder.buildModels(schemaList); + var models = this.modelBuilder.buildModels(schemaList, + this.createModel.bind(this)); + return models; }; +/** + * Introspect a JSON object and build a model class + * @param {String} name Name of the model + * @param {Object} json The json object representing a model instance + * @param {Object} options Options + * @returns {*} + */ +DataSource.prototype.buildModelFromInstance = function (name, json, options) { + + // Introspect the JSON document to generate a schema + var schema = ModelBuilder.introspect(json); + + // Create a model for the generated schema + return this.createModel(name, schema, options); +}; + /** * Check whether migrations needed * This method applies only to SQL connectors. diff --git a/lib/introspection.js b/lib/introspection.js index aa2d5e4a..1ce08198 100644 --- a/lib/introspection.js +++ b/lib/introspection.js @@ -24,12 +24,13 @@ module.exports = function getIntrospector(ModelBuilder) { return 'date'; } + var itemType; if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { if (value[i] === null || value[i] === undefined) { continue; } - var itemType = introspectType(value[i]); + itemType = introspectType(value[i]); if (itemType) { return [itemType]; } @@ -43,7 +44,7 @@ module.exports = function getIntrospector(ModelBuilder) { var properties = {}; for (var p in value) { - var itemType = introspectType(value[p]); + itemType = introspectType(value[p]); if (itemType) { properties[p] = itemType; } @@ -54,6 +55,7 @@ module.exports = function getIntrospector(ModelBuilder) { return properties; } + ModelBuilder.introspect = introspectType; return introspectType; } diff --git a/lib/model-builder.js b/lib/model-builder.js index df46130e..5962a1a0 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -632,7 +632,7 @@ ModelBuilder.prototype.resolveType = function (type) { * @param {*} schemas The schemas * @returns {Object} A map of model constructors keyed by model name */ -ModelBuilder.prototype.buildModels = function (schemas) { +ModelBuilder.prototype.buildModels = function (schemas, createModel) { var models = {}; // Normalize the schemas to be an array of the schema objects {name: , properties: {}, options: {}} @@ -656,7 +656,12 @@ ModelBuilder.prototype.buildModels = function (schemas) { for (var s in schemas) { var name = this.getSchemaName(schemas[s].name); schemas[s].name = name; - var model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options); + var model; + if(typeof createModel === 'function') { + model = createModel(schemas[s].name, schemas[s].properties, schemas[s].options); + } else { + model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options); + } models[name] = model; relations = relations.concat(model.definition.relations); } diff --git a/test/introspection.test.js b/test/introspection.test.js index 6fda1bcb..720b9e0c 100644 --- a/test/introspection.test.js +++ b/test/introspection.test.js @@ -1,8 +1,29 @@ var assert = require('assert'); -var ModelBuilder = require('../lib/model-builder').ModelBuilder; +var ModelBuilder = require('..').ModelBuilder; +var DataSource = require('../').DataSource; var introspectType = require('../lib/introspection')(ModelBuilder); var traverse = require('traverse'); +var json = { + name: 'Joe', + age: 30, + birthday: new Date(), + vip: true, + address: { + street: '1 Main St', + city: 'San Jose', + state: 'CA', + zipcode: '95131', + country: 'US' + }, + friends: ['John', 'Mary'], + emails: [ + {label: 'work', id: 'x@sample.com'}, + {label: 'home', id: 'x@home.com'} + ], + tags: [] +}; + describe('Introspection of model definitions from JSON', function () { it('should handle simple types', function () { @@ -61,27 +82,6 @@ describe('Introspection of model definitions from JSON', function () { }); it('should build a model from the introspected schema', function (done) { - - var json = { - name: 'Joe', - age: 30, - birthday: new Date(), - vip: true, - address: { - street: '1 Main St', - city: 'San Jose', - state: 'CA', - zipcode: '95131', - country: 'US' - }, - friends: ['John', 'Mary'], - emails: [ - {label: 'work', id: 'x@sample.com'}, - {label: 'home', id: 'x@home.com'} - ], - tags: [] - }; - var copy = traverse(json).clone(); var schema = introspectType(json); @@ -97,5 +97,33 @@ describe('Introspection of model definitions from JSON', function () { assert.deepEqual(obj, copy); done(); }); + + it('should build a model using buildModelFromInstance', function (done) { + var copy = traverse(json).clone(); + + var builder = new ModelBuilder(); + var Model = builder.buildModelFromInstance('MyModel', copy, {idInjection: false}); + + var obj = new Model(json); + obj = obj.toObject(); + assert.deepEqual(obj, copy); + done(); + }); + + it('should build a model using DataSource.buildModelFromInstance', function (done) { + var copy = traverse(json).clone(); + + var builder = new DataSource('memory'); + var Model = builder.buildModelFromInstance('MyModel', copy, + {idInjection: false}); + + assert.equal(Model.dataSource, builder); + + var obj = new Model(json); + obj = obj.toObject(); + assert.deepEqual(obj, copy); + done(); + }); + }); diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index bb02bfcf..10c9c10e 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -1418,28 +1418,31 @@ describe('DataAccessObject', function () { }); describe('Load models from json', function () { - it('should be able to define models from json', function () { - var path = require('path'), - fs = require('fs'); - - /** - * Load LDL schemas from a json doc - * @param schemaFile The dataSource json file - * @returns A map of schemas keyed by name - */ - function loadSchemasSync(schemaFile, dataSource) { - // Set up the data source - if (!dataSource) { - dataSource = new DataSource('memory'); - } - - // Read the dataSource JSON file - var schemas = JSON.parse(fs.readFileSync(schemaFile)); - - return dataSource.modelBuilder.buildModels(schemas); + var path = require('path'), + fs = require('fs'); + /** + * Load LDL schemas from a json doc + * @param schemaFile The dataSource json file + * @returns A map of schemas keyed by name + */ + function loadSchemasSync(schemaFile, dataSource) { + var modelBuilder, createModel; + // Set up the data source + if (!dataSource) { + modelBuilder = new ModelBuilder(); + } else { + modelBuilder = dataSource.modelBuilder; + createModel = dataSource.createModel.bind(dataSource); } + // Read the dataSource JSON file + var schemas = JSON.parse(fs.readFileSync(schemaFile)); + return modelBuilder.buildModels(schemas, createModel); + } + + it('should be able to define models from json', function () { + var models = loadSchemasSync(path.join(__dirname, 'test1-schemas.json')); models.should.have.property('AnonymousModel_0'); @@ -1459,6 +1462,16 @@ describe('Load models from json', function () { } }); + it('should be able to define models from json using dataSource', function() { + var ds = new DataSource('memory'); + + var models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json'), ds); + models.should.have.property('Address'); + models.should.have.property('Account'); + models.should.have.property('Customer'); + assert.equal(models.Address.dataSource, ds); + }); + it('should allow customization of default model base class', function () { var modelBuilder = new ModelBuilder();