Merge pull request #36 from strongloop/redefine-model
Refactor the relation handling and enable it with attach
This commit is contained in:
commit
8feb531680
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue