Merge pull request #34 from strongloop/type-or-relation-resolution
Enable deferred type/relation resolutions
This commit is contained in:
commit
5d92bcb098
|
@ -12,6 +12,7 @@ var EventEmitter = require('events').EventEmitter;
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
var existsSync = fs.existsSync || path.existsSync;
|
var existsSync = fs.existsSync || path.existsSync;
|
||||||
|
@ -302,6 +303,13 @@ DataSource.prototype.setup = function(name, settings) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isModelClass(cls) {
|
||||||
|
if(!cls) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return cls.prototype instanceof ModelBaseClass;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a model class
|
* Define a model class
|
||||||
*
|
*
|
||||||
|
@ -364,6 +372,10 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define
|
||||||
|
|
||||||
var NewClass = ModelBuilder.prototype.define.call(this, className, properties, settings);
|
var NewClass = ModelBuilder.prototype.define.call(this, className, properties, settings);
|
||||||
|
|
||||||
|
if(settings.unresolved) {
|
||||||
|
return NewClass;
|
||||||
|
}
|
||||||
|
|
||||||
// add data access objects
|
// add data access objects
|
||||||
this.mixin(NewClass);
|
this.mixin(NewClass);
|
||||||
|
|
||||||
|
@ -376,6 +388,80 @@ DataSource.prototype.createModel = DataSource.prototype.define = function define
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return NewClass;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -104,34 +104,49 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
settings.strict = false;
|
settings.strict = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// every class can receive hash of data as optional param
|
// Check if there is a unresolved model with the same name
|
||||||
var ModelClass = function ModelConstructor(data, dataSource) {
|
var ModelClass = this.models[className];
|
||||||
if(!(this instanceof ModelConstructor)) {
|
|
||||||
return new ModelConstructor(data, dataSource);
|
|
||||||
}
|
|
||||||
ModelBaseClass.apply(this, arguments);
|
|
||||||
if(dataSource) {
|
|
||||||
hiddenProperty(this, '__dataSource', dataSource);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// mix in EventEmitter (don't inherit from)
|
if(!ModelClass) {
|
||||||
var events = new EventEmitter();
|
// every class can receive hash of data as optional param
|
||||||
for (var f in EventEmitter.prototype) {
|
ModelClass = function ModelConstructor(data, dataSource) {
|
||||||
if (typeof EventEmitter.prototype[f] === 'function') {
|
if(!(this instanceof ModelConstructor)) {
|
||||||
ModelClass[f] = events[f].bind(events);
|
return new ModelConstructor(data, dataSource);
|
||||||
|
}
|
||||||
|
if(ModelClass.settings.unresolved) {
|
||||||
|
throw new Error('Model ' + ModelClass.modelName + ' is not defined.');
|
||||||
|
}
|
||||||
|
ModelBaseClass.apply(this, arguments);
|
||||||
|
if(dataSource) {
|
||||||
|
hiddenProperty(this, '__dataSource', dataSource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// mix in EventEmitter (don't inherit from)
|
||||||
|
var events = new EventEmitter();
|
||||||
|
for (var f in EventEmitter.prototype) {
|
||||||
|
if (typeof EventEmitter.prototype[f] === 'function') {
|
||||||
|
ModelClass[f] = events[f].bind(events);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
util.inherits(ModelClass, ModelBaseClass);
|
||||||
|
hiddenProperty(ModelClass, 'modelName', className);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store class in model pool
|
||||||
|
this.models[className] = ModelClass;
|
||||||
|
|
||||||
|
// Return the unresolved model
|
||||||
|
if(settings.unresolved) {
|
||||||
|
ModelClass.settings = {unresolved: true};
|
||||||
|
return ModelClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add metadata to the ModelClass
|
// Add metadata to the ModelClass
|
||||||
hiddenProperty(ModelClass, 'dataSource', dataSource);
|
hiddenProperty(ModelClass, 'dataSource', dataSource);
|
||||||
hiddenProperty(ModelClass, 'schema', dataSource); // For backward compatibility
|
hiddenProperty(ModelClass, 'schema', dataSource); // For backward compatibility
|
||||||
hiddenProperty(ModelClass, 'modelName', className);
|
|
||||||
hiddenProperty(ModelClass, 'pluralModelName', pluralName || inflection.pluralize(className));
|
hiddenProperty(ModelClass, 'pluralModelName', pluralName || inflection.pluralize(className));
|
||||||
hiddenProperty(ModelClass, 'relations', {});
|
hiddenProperty(ModelClass, 'relations', {});
|
||||||
|
|
||||||
util.inherits(ModelClass, ModelBaseClass);
|
|
||||||
|
|
||||||
// inherit ModelBaseClass static methods
|
// inherit ModelBaseClass static methods
|
||||||
for (var i in ModelBaseClass) {
|
for (var i in ModelBaseClass) {
|
||||||
if(i !== '_mixins') {
|
if(i !== '_mixins') {
|
||||||
|
@ -143,8 +158,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
ModelClass.setter = {};
|
ModelClass.setter = {};
|
||||||
|
|
||||||
var modelDefinition = new ModelDefinition(this, className, properties, settings);
|
var modelDefinition = new ModelDefinition(this, className, properties, settings);
|
||||||
// store class in model pool
|
|
||||||
this.models[className] = ModelClass;
|
|
||||||
this.definitions[className] = modelDefinition;
|
this.definitions[className] = modelDefinition;
|
||||||
|
|
||||||
// expose properties on the ModelClass
|
// expose properties on the ModelClass
|
||||||
|
@ -343,6 +357,8 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
|
|
||||||
ModelClass.forEachProperty(ModelClass.registerProperty);
|
ModelClass.forEachProperty(ModelClass.registerProperty);
|
||||||
|
|
||||||
|
ModelClass.emit('defined', ModelClass);
|
||||||
|
|
||||||
return ModelClass;
|
return ModelClass;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -472,6 +488,8 @@ ModelBuilder.prototype.resolveType = function(type) {
|
||||||
if (schemaType) {
|
if (schemaType) {
|
||||||
return schemaType;
|
return schemaType;
|
||||||
} else {
|
} else {
|
||||||
|
// The type cannot be resolved, let's create a place holder
|
||||||
|
type = this.define(type, {}, {unresolved: true});
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
} else if (type.constructor.name === 'Object') {
|
} else if (type.constructor.name === 'Object') {
|
||||||
|
|
|
@ -44,16 +44,18 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
var methodName = params.as || i8n.camelize(anotherClass.pluralModelName, true);
|
var methodName = params.as || i8n.camelize(anotherClass.pluralModelName, true);
|
||||||
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
|
||||||
|
|
||||||
|
var idName = this.dataSource.idName(this.modelName) || 'id';
|
||||||
|
|
||||||
this.relations[methodName] = {
|
this.relations[methodName] = {
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
keyFrom: 'id',
|
keyFrom: idName,
|
||||||
keyTo: fk,
|
keyTo: fk,
|
||||||
modelTo: anotherClass,
|
modelTo: anotherClass,
|
||||||
multiple: true
|
multiple: true
|
||||||
};
|
};
|
||||||
// each instance of this class should have method named
|
// each instance of this class should have method named
|
||||||
// pluralize(anotherClass.modelName)
|
// pluralize(anotherClass.modelName)
|
||||||
// which is actually just anotherClass.all({where: {thisModelNameId: this.id}}, cb);
|
// which is actually just anotherClass.all({where: {thisModelNameId: this[idName]}}, cb);
|
||||||
var scopeMethods = {
|
var scopeMethods = {
|
||||||
findById: find,
|
findById: find,
|
||||||
destroy: destroy
|
destroy: destroy
|
||||||
|
@ -69,7 +71,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
done = function() {};
|
done = function() {};
|
||||||
}
|
}
|
||||||
var self = this;
|
var self = this;
|
||||||
var id = this.id;
|
var id = this[idName];
|
||||||
anotherClass.create(data, function(err, ac) {
|
anotherClass.create(data, function(err, ac) {
|
||||||
if (err) return done(err, ac);
|
if (err) return done(err, ac);
|
||||||
var d = {};
|
var d = {};
|
||||||
|
@ -89,16 +91,16 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
scopeMethods.add = function(acInst, done) {
|
scopeMethods.add = function(acInst, done) {
|
||||||
var data = {};
|
var data = {};
|
||||||
var query = {};
|
var query = {};
|
||||||
query[fk] = this.id;
|
query[fk] = this[idName];
|
||||||
data[params.through.relationNameFor(fk)] = this;
|
data[params.through.relationNameFor(fk)] = this;
|
||||||
query[fk2] = acInst.id || acInst;
|
query[fk2] = acInst[idName] || acInst;
|
||||||
data[params.through.relationNameFor(fk2)] = acInst;
|
data[params.through.relationNameFor(fk2)] = acInst;
|
||||||
params.through.findOrCreate({where: query}, data, done);
|
params.through.findOrCreate({where: query}, data, done);
|
||||||
};
|
};
|
||||||
scopeMethods.remove = function(acInst, done) {
|
scopeMethods.remove = function(acInst, done) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var q = {};
|
var q = {};
|
||||||
q[fk2] = acInst.id || acInst;
|
q[fk2] = acInst[idName] || acInst;
|
||||||
params.through.findOne({where: q}, function(err, d) {
|
params.through.findOne({where: q}, function(err, d) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
|
@ -114,7 +116,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
defineScope(this.prototype, params.through || anotherClass, methodName, function () {
|
defineScope(this.prototype, params.through || anotherClass, methodName, function () {
|
||||||
var filter = {};
|
var filter = {};
|
||||||
filter.where = {};
|
filter.where = {};
|
||||||
filter.where[fk] = this.id;
|
filter.where[fk] = this[idName];
|
||||||
if (params.through) {
|
if (params.through) {
|
||||||
filter.collect = i8n.camelize(anotherClass.modelName, true);
|
filter.collect = i8n.camelize(anotherClass.modelName, true);
|
||||||
filter.include = filter.collect;
|
filter.include = filter.collect;
|
||||||
|
@ -131,7 +133,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
anotherClass.findById(id, function (err, inst) {
|
anotherClass.findById(id, function (err, inst) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) return cb(new Error('Not found'));
|
if (!inst) return cb(new Error('Not found'));
|
||||||
if (inst[fk] && inst[fk].toString() == this.id.toString()) {
|
if (inst[fk] && inst[fk].toString() == this[idName].toString()) {
|
||||||
cb(null, inst);
|
cb(null, inst);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied'));
|
cb(new Error('Permission denied'));
|
||||||
|
@ -144,7 +146,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
anotherClass.findById(id, function (err, inst) {
|
anotherClass.findById(id, function (err, inst) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) return cb(new Error('Not found'));
|
if (!inst) return cb(new Error('Not found'));
|
||||||
if (inst[fk] && inst[fk].toString() == self.id.toString()) {
|
if (inst[fk] && inst[fk].toString() == self[idName].toString()) {
|
||||||
inst.destroy(cb);
|
inst.destroy(cb);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied'));
|
cb(new Error('Permission denied'));
|
||||||
|
@ -192,13 +194,15 @@ Relation.belongsTo = function (anotherClass, params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var idName = this.dataSource.idName(this.modelName) || 'id';
|
||||||
var methodName = params.as || i8n.camelize(anotherClass.modelName, true);
|
var methodName = params.as || i8n.camelize(anotherClass.modelName, true);
|
||||||
var fk = params.foreignKey || methodName + 'Id';
|
var fk = params.foreignKey || methodName + 'Id';
|
||||||
|
|
||||||
this.relations[methodName] = {
|
this.relations[methodName] = {
|
||||||
type: 'belongsTo',
|
type: 'belongsTo',
|
||||||
keyFrom: fk,
|
keyFrom: fk,
|
||||||
keyTo: 'id',
|
keyTo: idName,
|
||||||
modelTo: anotherClass,
|
modelTo: anotherClass,
|
||||||
multiple: false
|
multiple: false
|
||||||
};
|
};
|
||||||
|
@ -214,7 +218,7 @@ Relation.belongsTo = function (anotherClass, params) {
|
||||||
anotherClass.findById(id, function (err,inst) {
|
anotherClass.findById(id, function (err,inst) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) return cb(null, null);
|
if (!inst) return cb(null, null);
|
||||||
if (inst.id === this[fk]) {
|
if (inst[idName] === this[fk]) {
|
||||||
cb(null, inst);
|
cb(null, inst);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Permission denied'));
|
cb(new Error('Permission denied'));
|
||||||
|
@ -235,7 +239,7 @@ Relation.belongsTo = function (anotherClass, params) {
|
||||||
cachedValue = this.__cachedRelations[methodName];
|
cachedValue = this.__cachedRelations[methodName];
|
||||||
}
|
}
|
||||||
if (p instanceof ModelBaseClass) { // acts as setter
|
if (p instanceof ModelBaseClass) { // acts as setter
|
||||||
this[fk] = p.id;
|
this[fk] = p[idName];
|
||||||
this.__cachedRelations[methodName] = p;
|
this.__cachedRelations[methodName] = p;
|
||||||
} else if (typeof p === 'function') { // acts as async getter
|
} else if (typeof p === 'function') { // acts as async getter
|
||||||
if (typeof cachedValue === 'undefined') {
|
if (typeof cachedValue === 'undefined') {
|
||||||
|
|
|
@ -157,6 +157,38 @@ describe('ModelBuilder define model', function () {
|
||||||
done(null, User);
|
done(null, User);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to reference models by name before they are defined', function (done) {
|
||||||
|
var modelBuilder = new ModelBuilder();
|
||||||
|
|
||||||
|
var User = modelBuilder.define('User', {name: String, address: 'Address'});
|
||||||
|
|
||||||
|
var user;
|
||||||
|
try {
|
||||||
|
user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
|
||||||
|
assert(false, 'An exception should have been thrown');
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
var Address = modelBuilder.define('Address', {
|
||||||
|
street: String,
|
||||||
|
city: String,
|
||||||
|
state: String,
|
||||||
|
zipCode: String,
|
||||||
|
country: String
|
||||||
|
});
|
||||||
|
|
||||||
|
user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
|
||||||
|
|
||||||
|
User.modelName.should.equal('User');
|
||||||
|
User.definition.properties.address.should.have.property('type', Address);
|
||||||
|
user.should.be.a('object');
|
||||||
|
assert(user.name === 'Joe');
|
||||||
|
user.address.should.have.property('city', 'San Jose');
|
||||||
|
user.address.should.have.property('state', 'CA');
|
||||||
|
done(null, User);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -387,6 +419,120 @@ describe('DataSource define model', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Load models with relations', function () {
|
||||||
|
it('should set up relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String});
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}});
|
||||||
|
|
||||||
|
assert(User.relations['posts']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up belongsTo relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var User = ds.define('User', {name: String});
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
|
||||||
|
|
||||||
|
assert(Post.relations['user']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up hasMany and belongsTo relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}, accounts: {type: 'hasMany', model: 'Account'}}});
|
||||||
|
|
||||||
|
assert(!User.relations['posts']);
|
||||||
|
assert(!User.relations['accounts']);
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
|
||||||
|
|
||||||
|
var Account = ds.define('Account', {userId: Number, type: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
|
||||||
|
|
||||||
|
assert(Post.relations['user']);
|
||||||
|
assert.deepEqual(Post.relations['user'], {
|
||||||
|
type: 'belongsTo',
|
||||||
|
keyFrom: 'userId',
|
||||||
|
keyTo: 'id',
|
||||||
|
modelTo: User,
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
assert(User.relations['posts']);
|
||||||
|
assert.deepEqual(User.relations['posts'], {
|
||||||
|
type: 'hasMany',
|
||||||
|
keyFrom: 'id',
|
||||||
|
keyTo: 'userId',
|
||||||
|
modelTo: Post,
|
||||||
|
multiple: true
|
||||||
|
});
|
||||||
|
assert(User.relations['accounts']);
|
||||||
|
assert.deepEqual(User.relations['accounts'], {
|
||||||
|
type: 'hasMany',
|
||||||
|
keyFrom: 'id',
|
||||||
|
keyTo: 'userId',
|
||||||
|
modelTo: Account,
|
||||||
|
multiple: true
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if a relation is missing type', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String});
|
||||||
|
|
||||||
|
try {
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {model: 'Post'}}});
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the relation type is invalid', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Post = ds.define('Post', {userId: Number, content: String});
|
||||||
|
|
||||||
|
try {
|
||||||
|
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasXYZ', model: 'Post'}}});
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle hasMany through', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
var Physician = ds.createModel('Physician', {
|
||||||
|
name: String
|
||||||
|
}, {relations: {patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'}}});
|
||||||
|
|
||||||
|
var Patient = ds.createModel('Patient', {
|
||||||
|
name: String
|
||||||
|
}, {relations: {physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'}}});
|
||||||
|
|
||||||
|
assert(!Physician.relations['patients']); // Appointment hasn't been resolved yet
|
||||||
|
assert(!Patient.relations['physicians']); // Appointment hasn't been resolved yet
|
||||||
|
|
||||||
|
var Appointment = ds.createModel('Appointment', {
|
||||||
|
physicianId: Number,
|
||||||
|
patientId: Number,
|
||||||
|
appointmentDate: Date
|
||||||
|
}, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}});
|
||||||
|
|
||||||
|
assert(Physician.relations['patients']);
|
||||||
|
assert(Patient.relations['physicians']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('Load models from json', function () {
|
describe('Load models from json', function () {
|
||||||
it('should be able to define models from json', function () {
|
it('should be able to define models from json', function () {
|
||||||
var path = require('path'),
|
var path = require('path'),
|
||||||
|
|
Loading…
Reference in New Issue