Merge pull request #36 from strongloop/redefine-model

Refactor the relation handling and enable it with attach
This commit is contained in:
Raymond Feng 2013-11-07 13:33:59 -08:00
commit 8feb531680
2 changed files with 131 additions and 108 deletions

View File

@ -310,6 +310,110 @@ 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
* @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(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
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 +474,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 +528,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;
};

View File

@ -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();
});
});