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;
|
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
|
* 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) {
|
if(settings.unresolved) {
|
||||||
return NewClass;
|
return modelClass;
|
||||||
}
|
}
|
||||||
|
this.setupDataAccess(modelClass, settings);
|
||||||
// add data access objects
|
return modelClass;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -510,36 +528,27 @@ DataSource.prototype.mixin = function (ModelCtor) {
|
||||||
/**
|
/**
|
||||||
* Attach an existing model to a data source.
|
* 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) {
|
DataSource.prototype.attach = function (modelClass) {
|
||||||
if(ModelCtor.dataSource === this) {
|
if(modelClass.dataSource === this) {
|
||||||
// Already attached to the data source
|
// Already attached to the data source
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var className = ModelCtor.modelName;
|
var className = modelClass.modelName;
|
||||||
var properties = ModelCtor.dataSource.definitions[className].properties;
|
var properties = modelClass.dataSource.definitions[className].properties;
|
||||||
var settings = ModelCtor.dataSource.definitions[className].settings;
|
var settings = modelClass.dataSource.definitions[className].settings;
|
||||||
|
|
||||||
// redefine the dataSource
|
// redefine the dataSource
|
||||||
ModelCtor.dataSource = this;
|
modelClass.dataSource = this;
|
||||||
// add to def
|
// add to def
|
||||||
var def = new ModelDefinition(this, className, properties, settings);
|
var def = new ModelDefinition(this, className, properties, settings);
|
||||||
def.build();
|
def.build();
|
||||||
this.definitions[className] = def;
|
this.definitions[className] = def;
|
||||||
this.models[className] = ModelCtor;
|
this.models[className] = modelClass;
|
||||||
|
|
||||||
this.mixin(ModelCtor);
|
this.setupDataAccess(modelClass, settings);
|
||||||
|
|
||||||
if(this.connector && this.connector.define) {
|
|
||||||
// pass control to connector
|
|
||||||
this.connector.define({
|
|
||||||
model: ModelCtor,
|
|
||||||
properties: properties,
|
|
||||||
settings: settings
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
|
@ -530,6 +530,20 @@ describe('Load models with relations', function () {
|
||||||
done();
|
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