Merge pull request #38 from strongloop/refactor-ds

Remove inheritence from DataSource to ModelBuilder
This commit is contained in:
Raymond Feng 2013-11-13 10:32:06 -08:00
commit 13659ca19d
6 changed files with 91 additions and 65 deletions

View File

@ -791,11 +791,10 @@ var defineScope = require('./scope.js').defineScope;
/** /**
* Define scope * Define scope
*/ */
DataAccessObject.scope = function (name, filter) { DataAccessObject.scope = function (name, filter, targetClass) {
defineScope(this, this, name, filter); defineScope(this, targetClass || this, name, filter);
}; };
// jutil.mixin(DataAccessObject, validations.Validatable); // jutil.mixin(DataAccessObject, validations.Validatable);
jutil.mixin(DataAccessObject, Inclusion); jutil.mixin(DataAccessObject, Inclusion);
jutil.mixin(DataAccessObject, Relation); jutil.mixin(DataAccessObject, Relation);

View File

@ -53,7 +53,7 @@ var slice = Array.prototype.slice;
* }); * });
* ``` * ```
*/ */
function DataSource(name, settings) { function DataSource(name, settings, modelBuilder) {
if (!(this instanceof DataSource)) { if (!(this instanceof DataSource)) {
return new DataSource(name, settings); return new DataSource(name, settings);
} }
@ -74,7 +74,9 @@ function DataSource(name, settings) {
settings = utils.parseSettings(settings); settings = utils.parseSettings(settings);
} }
ModelBuilder.call(this, name, settings); this.modelBuilder = modelBuilder || new ModelBuilder();
this.models = this.modelBuilder.models;
this.definitions = this.modelBuilder.definitions;
// operation metadata // operation metadata
// Initialize it before calling setup as the connector might register operations // Initialize it before calling setup as the connector might register operations
@ -113,8 +115,7 @@ function DataSource(name, settings) {
var fn = this.DataAccessObject.prototype[name]; var fn = this.DataAccessObject.prototype[name];
if(typeof fn === 'function') { if(typeof fn === 'function') {
var returns = fn.returns;
this.defineOperation(name, { this.defineOperation(name, {
prototype: true, prototype: true,
accepts: fn.accepts, accepts: fn.accepts,
@ -128,17 +129,12 @@ function DataSource(name, settings) {
}.bind(this)); }.bind(this));
} }
util.inherits(DataSource, ModelBuilder); util.inherits(DataSource, EventEmitter);
// allow child classes to supply a data access object // allow child classes to supply a data access object
DataSource.DataAccessObject = DataAccessObject; DataSource.DataAccessObject = DataAccessObject;
// Copy over statics
for (var m in ModelBuilder) {
if (!DataSource.hasOwnProperty(m) && 'super_' !== m) {
DataSource[m] = ModelBuilder[m];
}
}
/** /**
* Set up the connector instance for backward compatibility with JugglingDB schema/adapter * Set up the connector instance for backward compatibility with JugglingDB schema/adapter
@ -312,6 +308,10 @@ function isModelClass(cls) {
DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany']; DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany'];
function isModelDataSourceAttached(model) {
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);
}
/*! /*!
* Define relations for the model class from the relations object * Define relations for the model class from the relations object
* @param modelClass * @param modelClass
@ -321,10 +321,10 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
// Create a function for the closure in the loop // Create a function for the closure in the loop
var createListener = function (name, relation, targetModel, throughModel) { var createListener = function (name, relation, targetModel, throughModel) {
if (targetModel && targetModel.settings.unresolved) { if (!isModelDataSourceAttached(targetModel)) {
targetModel.once('defined', function (model) { targetModel.once('dataSourceAttached', function (model) {
// Check if the through model doesn't exist or resolved // Check if the through model doesn't exist or resolved
if (!throughModel || !throughModel.settings.unresolved) { if (!throughModel || isModelDataSourceAttached(throughModel)) {
// The target model is resolved // The target model is resolved
var params = { var params = {
foreignKey: relation.foreignKey, foreignKey: relation.foreignKey,
@ -337,11 +337,12 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
modelClass[relation.type].call(modelClass, name, params); modelClass[relation.type].call(modelClass, name, params);
} }
}); });
} }
if (throughModel && throughModel.settings.unresolved) { if (throughModel && !isModelDataSourceAttached(throughModel)) {
// Set up a listener to the through model // Set up a listener to the through model
throughModel.once('defined', function (model) { throughModel.once('dataSourceAttached', function (model) {
if (!targetModel.settings.unresolved) { if (isModelDataSourceAttached(targetModel)) {
// The target model is resolved // The target model is resolved
var params = { var params = {
foreignKey: relation.foreignKey, foreignKey: relation.foreignKey,
@ -360,20 +361,20 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
for (var rn in relations) { for (var rn in relations) {
var r = relations[rn]; var r = relations[rn];
assert(DataSource.relationTypes.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]; var targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model);
if (!targetModel) { if (!targetModel) {
// The target model doesn't exist, let create a place holder for it // The target model doesn't exist, let create a place holder for it
targetModel = this.define(r.model, {}, {unresolved: true}); targetModel = this.define(r.model, {}, {unresolved: true});
} }
var throughModel = null; var throughModel = null;
if (r.through) { if (r.through) {
throughModel = isModelClass(r.through) ? r.through : this.models[r.through]; throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through);
if (!throughModel) { if (!throughModel) {
// The through model doesn't exist, let create a place holder for it // The through model doesn't exist, let create a place holder for it
throughModel = this.define(r.through, {}, {unresolved: true}); throughModel = this.define(r.through, {}, {unresolved: true});
} }
} }
if (targetModel.settings.unresolved || (throughModel && throughModel.settings.unresolved)) { if (!isModelDataSourceAttached(targetModel) || (throughModel && !isModelDataSourceAttached(throughModel))) {
// Create a listener to defer the relation set up // Create a listener to defer the relation set up
createListener(rn, r, targetModel, throughModel); createListener(rn, r, targetModel, throughModel);
} else { } else {
@ -475,12 +476,16 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define
} }
} }
var modelClass = ModelBuilder.prototype.define.call(this, className, properties, settings); var modelClass = this.modelBuilder.define(className, properties, settings);
modelClass.dataSource = this;
if(settings.unresolved) { if(settings.unresolved) {
return modelClass; return modelClass;
} }
this.setupDataAccess(modelClass, settings); this.setupDataAccess(modelClass, settings);
modelClass.emit('dataSourceAttached', modelClass);
return modelClass; return modelClass;
}; };
@ -526,6 +531,14 @@ DataSource.prototype.mixin = function (ModelCtor) {
}); });
}; };
DataSource.prototype.getModel = function(name) {
return this.modelBuilder.getModel(name);
};
DataSource.prototype.getModelDefinition = function(name) {
return this.modelBuilder.getModelDefinition(name);
};
/** /**
* Attach an existing model to a data source. * Attach an existing model to a data source.
* *
@ -538,19 +551,22 @@ DataSource.prototype.attach = function (modelClass) {
return; return;
} }
var className = modelClass.modelName; var className = modelClass.modelName;
var properties = modelClass.dataSource.definitions[className].properties; var modelDef = modelClass.dataSource.getModelDefinition(className);
var settings = modelClass.dataSource.definitions[className].settings; var properties = modelDef.properties;
var settings = modelDef.settings;
// redefine the dataSource // redefine the dataSource
modelClass.dataSource = this; modelClass.dataSource = this;
// add to def // add to def
var def = new ModelDefinition(this, className, properties, settings); var def = new ModelDefinition(this.modelBuilder, className, properties, settings);
def.build(); def.build();
this.definitions[className] = def; this.modelBuilder.definitions[className] = def;
this.models[className] = modelClass; this.modelBuilder.models[className] = modelClass;
this.setupDataAccess(modelClass, settings); this.setupDataAccess(modelClass, settings);
modelClass.emit('dataSourceAttached', modelClass);
return this; return this;
}; };
@ -558,13 +574,13 @@ DataSource.prototype.attach = function (modelClass) {
* Define single property named `prop` on `model` * Define single property named `prop` on `model`
* *
* @param {String} model - name of model * @param {String} model - name of model
* @param {String} prop - name of propery * @param {String} prop - name of property
* @param {Object} params - property settings * @param {Object} params - property settings
*/ */
DataSource.prototype.defineProperty = function (model, prop, params) { DataSource.prototype.defineProperty = function (model, prop, params) {
ModelBuilder.prototype.defineProperty.call(this, model, prop, params); this.modelBuilder.defineProperty(model, prop, params);
var resolvedProp = this.definitions[model].properties[prop]; var resolvedProp = this.getModelDefinition(model).properties[prop];
if (this.connector.defineProperty) { if (this.connector.defineProperty) {
this.connector.defineProperty(model, prop, resolvedProp); this.connector.defineProperty(model, prop, resolvedProp);
} }
@ -1225,7 +1241,7 @@ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb)
schemaList.push(schema); schemaList.push(schema);
} }
var models = self.buildModels(schemaList); var models = self.modelBuilder.buildModels(schemaList);
cb && cb(err, models); cb && cb(err, models);
}); });
}; };
@ -1252,7 +1268,7 @@ DataSource.prototype.discoverAndBuildModelsSync = function (modelName, options)
schemaList.push(schema); schemaList.push(schema);
} }
var models = this.buildModels(schemaList); var models = this.modelBuilder.buildModels(schemaList);
return models; return models;
}; };
@ -1305,7 +1321,7 @@ DataSource.prototype.freeze = function freeze() {
* @param {String} modelName The model name * @param {String} modelName The model name
*/ */
DataSource.prototype.tableName = function (modelName) { DataSource.prototype.tableName = function (modelName) {
return this.definitions[modelName].tableName(this.connector.name); return this.getModelDefinition(modelName).tableName(this.connector.name);
}; };
/** /**
@ -1315,7 +1331,7 @@ DataSource.prototype.tableName = function (modelName) {
* @returns {String} columnName * @returns {String} columnName
*/ */
DataSource.prototype.columnName = function (modelName, propertyName) { DataSource.prototype.columnName = function (modelName, propertyName) {
return this.definitions[modelName].columnName(this.connector.name, propertyName); return this.getModelDefinition(modelName).columnName(this.connector.name, propertyName);
}; };
/** /**
@ -1325,7 +1341,7 @@ DataSource.prototype.columnName = function (modelName, propertyName) {
* @returns {Object} column metadata * @returns {Object} column metadata
*/ */
DataSource.prototype.columnMetadata = function (modelName, propertyName) { DataSource.prototype.columnMetadata = function (modelName, propertyName) {
return this.definitions[modelName].columnMetadata(this.connector.name, propertyName); return this.getModelDefinition(modelName).columnMetadata(this.connector.name, propertyName);
}; };
/** /**
@ -1334,7 +1350,7 @@ DataSource.prototype.columnMetadata = function (modelName, propertyName) {
* @returns {String[]} column names * @returns {String[]} column names
*/ */
DataSource.prototype.columnNames = function (modelName) { DataSource.prototype.columnNames = function (modelName) {
return this.definitions[modelName].columnNames(this.connector.name); return this.getModelDefinition(modelName).columnNames(this.connector.name);
}; };
/** /**
@ -1343,7 +1359,7 @@ DataSource.prototype.columnNames = function (modelName) {
* @returns {String} columnName for ID * @returns {String} columnName for ID
*/ */
DataSource.prototype.idColumnName = function(modelName) { DataSource.prototype.idColumnName = function(modelName) {
return this.definitions[modelName].idColumnName(this.connector.name); return this.getModelDefinition(modelName).idColumnName(this.connector.name);
}; };
/** /**
@ -1352,10 +1368,10 @@ DataSource.prototype.idColumnName = function(modelName) {
* @returns {String} property name for ID * @returns {String} property name for ID
*/ */
DataSource.prototype.idName = function(modelName) { DataSource.prototype.idName = function(modelName) {
if(!this.definitions[modelName].idName) { if(!this.getModelDefinition(modelName).idName) {
console.log(this.definitions[modelName]); console.error('No id name', this.getModelDefinition(modelName));
} }
return this.definitions[modelName].idName(); return this.getModelDefinition(modelName).idName();
}; };
/** /**
@ -1364,7 +1380,7 @@ DataSource.prototype.idName = function(modelName) {
* @returns {String[]} property names for IDs * @returns {String[]} property names for IDs
*/ */
DataSource.prototype.idNames = function (modelName) { DataSource.prototype.idNames = function (modelName) {
return this.definitions[modelName].idNames(); return this.getModelDefinition(modelName).idNames();
}; };
@ -1376,7 +1392,7 @@ DataSource.prototype.idNames = function (modelName) {
*/ */
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) { DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) {
// quit if key already defined // quit if key already defined
if (this.definitions[className].rawProperties[key]) return; if (this.getModelDefinition(className).rawProperties[key]) return;
if (this.connector.defineForeignKey) { if (this.connector.defineForeignKey) {
var cb = function (err, keyType) { var cb = function (err, keyType) {
@ -1428,7 +1444,7 @@ DataSource.prototype.disconnect = function disconnect(cb) {
DataSource.prototype.copyModel = function copyModel(Master) { DataSource.prototype.copyModel = function copyModel(Master) {
var dataSource = this; var dataSource = this;
var className = Master.modelName; var className = Master.modelName;
var md = Master.dataSource.definitions[className]; var md = Master.dataSource.getModelDefinition(className);
var Slave = function SlaveModel() { var Slave = function SlaveModel() {
Master.apply(this, [].slice.call(arguments)); Master.apply(this, [].slice.call(arguments));
}; };
@ -1442,11 +1458,11 @@ DataSource.prototype.copyModel = function copyModel(Master) {
hiddenProperty(Slave, 'modelName', className); hiddenProperty(Slave, 'modelName', className);
hiddenProperty(Slave, 'relations', Master.relations); hiddenProperty(Slave, 'relations', Master.relations);
if (!(className in dataSource.models)) { if (!(className in dataSource.modelBuilder.models)) {
// store class in model pool // store class in model pool
dataSource.models[className] = Slave; dataSource.modelBuilder.models[className] = Slave;
dataSource.definitions[className] = new ModelDefinition(dataSource, md.name, md.properties, md.settings); dataSource.modelBuilder.definitions[className] = new ModelDefinition(dataSource.modelBuilder, md.name, md.properties, md.settings);
if ((!dataSource.isTransaction) && dataSource.connector && dataSource.connector.define) { if ((!dataSource.isTransaction) && dataSource.connector && dataSource.connector.define) {
dataSource.connector.define({ dataSource.connector.define({
@ -1483,11 +1499,12 @@ DataSource.prototype.transaction = function() {
transaction.connector = dataSource.connector.transaction(); transaction.connector = dataSource.connector.transaction();
// create blank models pool // create blank models pool
transaction.models = {}; transaction.modelBuilder = new ModelBuilder();
transaction.definitions = {}; transaction.models = transaction.modelBuilder.models;
transaction.definitions = transaction.modelBuilder.definitions;
for (var i in dataSource.models) { for (var i in dataSource.modelBuilder.models) {
dataSource.copyModel.call(transaction, dataSource.models[i]); dataSource.copyModel.call(transaction, dataSource.modelBuilder.models[i]);
} }
transaction.exec = function(cb) { transaction.exec = function(cb) {

View File

@ -55,6 +55,14 @@ function isModelClass(cls) {
return cls.prototype instanceof DefaultModelBaseClass; return cls.prototype instanceof DefaultModelBaseClass;
} }
ModelBuilder.prototype.getModel = function(name) {
return this.models[name];
};
ModelBuilder.prototype.getModelDefinition = function(name) {
return this.definitions[name];
};
/** /**
* Define a model class * Define a model class
* *
@ -146,7 +154,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
var events = new EventEmitter(); var events = new EventEmitter();
for (var f in EventEmitter.prototype) { for (var f in EventEmitter.prototype) {
if (typeof EventEmitter.prototype[f] === 'function') { if (typeof EventEmitter.prototype[f] === 'function') {
ModelClass[f] = events[f].bind(events); ModelClass[f] = EventEmitter.prototype[f].bind(events);
} }
} }
util.inherits(ModelClass, ModelBaseClass); util.inherits(ModelClass, ModelBaseClass);
@ -170,9 +178,10 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
// inherit ModelBaseClass static methods // inherit ModelBaseClass static methods
for (var i in ModelBaseClass) { for (var i in ModelBaseClass) {
if(i !== '_mixins') { // We need to skip properties that are already in the subclass, for example, the event emitter methods
ModelClass[i] = ModelBaseClass[i]; if(i !== '_mixins' && !(i in ModelClass)) {
} ModelClass[i] = ModelBaseClass[i];
}
} }
ModelClass.getter = {}; ModelClass.getter = {};

View File

@ -34,9 +34,9 @@ Relation.hasMany = function hasMany(anotherClass, params) {
anotherClass = params.model; anotherClass = params.model;
} else { } else {
var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); var anotherClassName = i8n.singularize(anotherClass).toLowerCase();
for(var name in this.dataSource.models) { for(var name in this.dataSource.modelBuilder.models) {
if (name.toLowerCase() === anotherClassName) { if (name.toLowerCase() === anotherClassName) {
anotherClass = this.dataSource.models[name]; anotherClass = this.dataSource.modelBuilder.models[name];
} }
} }
} }
@ -187,9 +187,9 @@ Relation.belongsTo = function (anotherClass, params) {
anotherClass = params.model; anotherClass = params.model;
} else { } else {
var anotherClassName = anotherClass.toLowerCase(); var anotherClassName = anotherClass.toLowerCase();
for(var name in this.dataSource.models) { for(var name in this.dataSource.modelBuilder.models) {
if (name.toLowerCase() === anotherClassName) { if (name.toLowerCase() === anotherClassName) {
anotherClass = this.dataSource.models[name]; anotherClass = this.dataSource.modelBuilder.models[name];
} }
} }
} }
@ -271,7 +271,7 @@ Relation.belongsTo = function (anotherClass, params) {
*/ */
Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) { Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) {
params = params || {}; params = params || {};
var models = this.dataSource.models; var models = this.dataSource.modelBuilder.models;
if ('string' === typeof anotherClass) { if ('string' === typeof anotherClass) {
params.as = anotherClass; params.as = anotherClass;

View File

@ -2,13 +2,14 @@
var should = require('./init.js'); var should = require('./init.js');
var Schema = require('../').Schema; var Schema = require('../').Schema;
var ModelBuilder = require('../').ModelBuilder;
describe('JSON property', function() { describe('JSON property', function() {
var dataSource, Model; var dataSource, Model;
it('should be defined', function() { it('should be defined', function() {
dataSource = getSchema(); dataSource = getSchema();
Model = dataSource.define('Model', {propertyName: Schema.JSON}); Model = dataSource.define('Model', {propertyName: ModelBuilder.JSON});
var m = new Model; var m = new Model;
(new Boolean('propertyName' in m)).should.eql(true); (new Boolean('propertyName' in m)).should.eql(true);
should.not.exist(m.propertyName); should.not.exist(m.propertyName);

View File

@ -200,7 +200,7 @@ describe('DataSource define model', function () {
// define models // define models
var Post = ds.define('Post', { var Post = ds.define('Post', {
title: { type: String, length: 255 }, title: { type: String, length: 255 },
content: { type: DataSource.Text }, content: { type: ModelBuilder.Text },
date: { type: Date, default: function () { date: { type: Date, default: function () {
return new Date(); return new Date();
} }, } },
@ -211,7 +211,7 @@ describe('DataSource define model', function () {
// simpler way to describe model // simpler way to describe model
var User = ds.define('User', { var User = ds.define('User', {
name: String, name: String,
bio: DataSource.Text, bio: ModelBuilder.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: Date,
age: Number age: Number
@ -592,7 +592,7 @@ describe('Load models from json', function () {
// Read the dataSource JSON file // Read the dataSource JSON file
var schemas = JSON.parse(fs.readFileSync(schemaFile)); var schemas = JSON.parse(fs.readFileSync(schemaFile));
return dataSource.buildModels(schemas); return dataSource.modelBuilder.buildModels(schemas);
} }