Merge branch 'feature/embed-hasmany' of github.com:fabien/loopback-datasource-juggler into fabien-feature/embed-hasmany
This commit is contained in:
commit
1b44a6d779
|
@ -17,6 +17,7 @@ exports.initialize = function initializeDataSource(dataSource, callback) {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.Memory = Memory;
|
exports.Memory = Memory;
|
||||||
|
exports.applyFilter = applyFilter;
|
||||||
|
|
||||||
function Memory(m, settings) {
|
function Memory(m, settings) {
|
||||||
if (m instanceof Memory) {
|
if (m instanceof Memory) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ var i8n = require('inflection');
|
||||||
var defineScope = require('./scope.js').defineScope;
|
var defineScope = require('./scope.js').defineScope;
|
||||||
var mergeQuery = require('./scope.js').mergeQuery;
|
var mergeQuery = require('./scope.js').mergeQuery;
|
||||||
var ModelBaseClass = require('./model.js');
|
var ModelBaseClass = require('./model.js');
|
||||||
|
var applyFilter = require('./connectors/memory').applyFilter;
|
||||||
|
var ValidationError = require('./validations.js').ValidationError;
|
||||||
|
|
||||||
exports.Relation = Relation;
|
exports.Relation = Relation;
|
||||||
exports.RelationDefinition = RelationDefinition;
|
exports.RelationDefinition = RelationDefinition;
|
||||||
|
@ -15,7 +17,8 @@ var RelationTypes = {
|
||||||
belongsTo: 'belongsTo',
|
belongsTo: 'belongsTo',
|
||||||
hasMany: 'hasMany',
|
hasMany: 'hasMany',
|
||||||
hasOne: 'hasOne',
|
hasOne: 'hasOne',
|
||||||
hasAndBelongsToMany: 'hasAndBelongsToMany'
|
hasAndBelongsToMany: 'hasAndBelongsToMany',
|
||||||
|
embedsMany: 'embedsMany'
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.RelationTypes = RelationTypes;
|
exports.RelationTypes = RelationTypes;
|
||||||
|
@ -24,13 +27,15 @@ exports.HasManyThrough = HasManyThrough;
|
||||||
exports.HasOne = HasOne;
|
exports.HasOne = HasOne;
|
||||||
exports.HasAndBelongsToMany = HasAndBelongsToMany;
|
exports.HasAndBelongsToMany = HasAndBelongsToMany;
|
||||||
exports.BelongsTo = BelongsTo;
|
exports.BelongsTo = BelongsTo;
|
||||||
|
exports.EmbedsMany = EmbedsMany;
|
||||||
|
|
||||||
var RelationClasses = {
|
var RelationClasses = {
|
||||||
belongsTo: BelongsTo,
|
belongsTo: BelongsTo,
|
||||||
hasMany: HasMany,
|
hasMany: HasMany,
|
||||||
hasManyThrough: HasManyThrough,
|
hasManyThrough: HasManyThrough,
|
||||||
hasOne: HasOne,
|
hasOne: HasOne,
|
||||||
hasAndBelongsToMany: HasAndBelongsToMany
|
hasAndBelongsToMany: HasAndBelongsToMany,
|
||||||
|
embedsMany: EmbedsMany
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeType(type) {
|
function normalizeType(type) {
|
||||||
|
@ -75,6 +80,7 @@ function RelationDefinition(definition) {
|
||||||
this.properties = definition.properties || {};
|
this.properties = definition.properties || {};
|
||||||
this.options = definition.options || {};
|
this.options = definition.options || {};
|
||||||
this.scope = definition.scope;
|
this.scope = definition.scope;
|
||||||
|
this.embed = definition.embed === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RelationDefinition.prototype.toJSON = function () {
|
RelationDefinition.prototype.toJSON = function () {
|
||||||
|
@ -290,6 +296,23 @@ function HasOne(definition, modelInstance) {
|
||||||
|
|
||||||
util.inherits(HasOne, Relation);
|
util.inherits(HasOne, Relation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EmbedsMany subclass
|
||||||
|
* @param {RelationDefinition|Object} definition
|
||||||
|
* @param {Object} modelInstance
|
||||||
|
* @returns {EmbedsMany}
|
||||||
|
* @constructor
|
||||||
|
* @class EmbedsMany
|
||||||
|
*/
|
||||||
|
function EmbedsMany(definition, modelInstance) {
|
||||||
|
if (!(this instanceof EmbedsMany)) {
|
||||||
|
return new EmbedsMany(definition, modelInstance);
|
||||||
|
}
|
||||||
|
assert(definition.type === RelationTypes.embedsMany);
|
||||||
|
Relation.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(EmbedsMany, Relation);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Find the relation by foreign key
|
* Find the relation by foreign key
|
||||||
|
@ -1375,3 +1398,310 @@ HasOne.prototype.related = function (refresh, params) {
|
||||||
self.resetCache();
|
self.resetCache();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RelationDefinition.embedsMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
|
var thisClassName = modelFrom.modelName;
|
||||||
|
params = params || {};
|
||||||
|
if (typeof modelTo === 'string') {
|
||||||
|
params.as = modelTo;
|
||||||
|
if (params.model) {
|
||||||
|
modelTo = params.model;
|
||||||
|
} else {
|
||||||
|
var modelToName = i8n.singularize(modelTo).toLowerCase();
|
||||||
|
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelTo.dataSource.name !== 'memory') {
|
||||||
|
throw new Error('Invalid embedded model: `' + modelTo.modelName + '` (memory connector only)');
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessorName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List');
|
||||||
|
var relationName = params.property || i8n.camelize(modelTo.pluralModelName, true);
|
||||||
|
var fk = modelTo.dataSource.idName(modelTo.modelName) || 'id';
|
||||||
|
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
|
||||||
|
|
||||||
|
var definition = new RelationDefinition({
|
||||||
|
name: relationName,
|
||||||
|
type: RelationTypes.embedsMany,
|
||||||
|
modelFrom: modelFrom,
|
||||||
|
keyFrom: idName,
|
||||||
|
keyTo: fk,
|
||||||
|
modelTo: modelTo,
|
||||||
|
multiple: true,
|
||||||
|
properties: params.properties,
|
||||||
|
scope: params.scope,
|
||||||
|
options: params.options,
|
||||||
|
embed: true
|
||||||
|
});
|
||||||
|
|
||||||
|
modelFrom.dataSource.defineProperty(modelFrom.modelName, relationName, {
|
||||||
|
type: [modelTo], default: function() { return []; }
|
||||||
|
});
|
||||||
|
|
||||||
|
// unique id is required
|
||||||
|
modelTo.validatesPresenceOf(idName);
|
||||||
|
modelFrom.validate(relationName, function(err) {
|
||||||
|
var embeddedList = this[relationName] || [];
|
||||||
|
var ids = embeddedList.map(function(m) { return m[idName]; });
|
||||||
|
var uniqueIds = ids.filter(function(id, pos) {
|
||||||
|
return ids.indexOf(id) === pos;
|
||||||
|
});
|
||||||
|
if (ids.length !== uniqueIds.length) {
|
||||||
|
this.errors.add(relationName, 'Contains duplicate `' + idName + '`', 'uniqueness');
|
||||||
|
err(false);
|
||||||
|
}
|
||||||
|
}, { code: 'uniqueness' })
|
||||||
|
|
||||||
|
// validate all embedded items
|
||||||
|
if (definition.options.validate) {
|
||||||
|
modelFrom.validate(relationName, function(err) {
|
||||||
|
var embeddedList = this[relationName] || [];
|
||||||
|
var hasErrors = false;
|
||||||
|
embeddedList.forEach(function(item) {
|
||||||
|
if (item instanceof modelTo) {
|
||||||
|
if (!item.isValid()) {
|
||||||
|
hasErrors = true;
|
||||||
|
var id = item[idName] || '(blank)';
|
||||||
|
var first = Object.keys(item.errors)[0];
|
||||||
|
var msg = 'contains invalid item: `' + id + '`';
|
||||||
|
msg += ' (' + first + ' ' + item.errors[first] + ')';
|
||||||
|
this.errors.add(relationName, msg, 'invalid');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasErrors = true;
|
||||||
|
this.errors.add(relationName, 'Contains invalid item', 'invalid');
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
if (hasErrors) err(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var scopeMethods = {
|
||||||
|
findById: scopeMethod(definition, 'findById'),
|
||||||
|
destroy: scopeMethod(definition, 'destroyById'),
|
||||||
|
updateById: scopeMethod(definition, 'updateById'),
|
||||||
|
exists: scopeMethod(definition, 'exists')
|
||||||
|
};
|
||||||
|
|
||||||
|
var findByIdFunc = scopeMethods.findById;
|
||||||
|
modelFrom.prototype['__findById__' + relationName] = findByIdFunc;
|
||||||
|
|
||||||
|
var destroyByIdFunc = scopeMethods.destroy;
|
||||||
|
modelFrom.prototype['__destroyById__' + relationName] = destroyByIdFunc;
|
||||||
|
|
||||||
|
var updateByIdFunc = scopeMethods.updateById;
|
||||||
|
modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc;
|
||||||
|
|
||||||
|
scopeMethods.create = scopeMethod(definition, 'create');
|
||||||
|
scopeMethods.build = scopeMethod(definition, 'build');
|
||||||
|
|
||||||
|
// Mix the property and scoped methods into the prototype class
|
||||||
|
var scopeDefinition = defineScope(modelFrom.prototype, modelTo, accessorName, function () {
|
||||||
|
return {};
|
||||||
|
}, scopeMethods);
|
||||||
|
|
||||||
|
scopeDefinition.related = scopeMethod(definition, 'related'); // bound to definition
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
|
||||||
|
var name = this.definition.name;
|
||||||
|
var self = receiver;
|
||||||
|
|
||||||
|
var actualCond = {};
|
||||||
|
var actualRefresh = false;
|
||||||
|
if (arguments.length === 3) {
|
||||||
|
cb = condOrRefresh;
|
||||||
|
} else if (arguments.length === 4) {
|
||||||
|
if (typeof condOrRefresh === 'boolean') {
|
||||||
|
actualRefresh = condOrRefresh;
|
||||||
|
} else {
|
||||||
|
actualCond = condOrRefresh;
|
||||||
|
actualRefresh = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Method can be only called with one or two arguments');
|
||||||
|
}
|
||||||
|
|
||||||
|
var embeddedList = self[name] || [];
|
||||||
|
|
||||||
|
var params = mergeQuery(actualCond, scopeParams);
|
||||||
|
if (params.where) {
|
||||||
|
embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.nextTick(function() { cb(null, embeddedList); });
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.findById = function (fkId, cb) {
|
||||||
|
var pk = this.definition.keyFrom;
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var relationName = this.definition.name;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
var embeddedList = modelInstance[relationName] || [];
|
||||||
|
|
||||||
|
fkId = fkId.toString(); // in case of explicit id
|
||||||
|
|
||||||
|
var find = function(id) {
|
||||||
|
for (var i = 0; i < embeddedList.length; i++) {
|
||||||
|
var item = embeddedList[i];
|
||||||
|
if (item[pk].toString() === fkId) return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var item = find(fkId);
|
||||||
|
item = (item instanceof modelTo) ? item : null;
|
||||||
|
|
||||||
|
if (typeof cb === 'function') {
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(null, item);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return item; // sync
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.exists = function (fkId, cb) {
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var inst = this.findById(fkId, function (err, inst) {
|
||||||
|
if (cb) cb(err, inst instanceof modelTo);
|
||||||
|
});
|
||||||
|
return inst instanceof modelTo; // sync
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.updateById = function (fkId, data, cb) {
|
||||||
|
if (typeof data === 'function') {
|
||||||
|
cb = data;
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var relationName = this.definition.name;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
var embeddedList = modelInstance[relationName] || [];
|
||||||
|
|
||||||
|
var inst = this.findById(fkId);
|
||||||
|
|
||||||
|
if (inst instanceof modelTo) {
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
for (var key in data) {
|
||||||
|
inst[key] = data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err = inst.isValid() ? null : new ValidationError(inst);
|
||||||
|
if (err && typeof cb === 'function') {
|
||||||
|
return process.nextTick(function() {
|
||||||
|
cb(err, inst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof cb === 'function') {
|
||||||
|
modelInstance.updateAttribute(relationName,
|
||||||
|
embeddedList, function(err) {
|
||||||
|
cb(err, inst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (typeof cb === 'function') {
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(null, null); // not found
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return inst; // sync
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.destroyById = function (fkId, cb) {
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var relationName = this.definition.name;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
|
var embeddedList = modelInstance[relationName] || [];
|
||||||
|
|
||||||
|
var inst = this.findById(fkId);
|
||||||
|
|
||||||
|
if (inst instanceof modelTo) {
|
||||||
|
var index = embeddedList.indexOf(inst);
|
||||||
|
if (index > -1) embeddedList.splice(index, 1);
|
||||||
|
if (typeof cb === 'function') {
|
||||||
|
modelInstance.updateAttribute(relationName,
|
||||||
|
embeddedList, function(err) {
|
||||||
|
cb(err, inst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (typeof cb === 'function') {
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(null, null); // not found
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return inst; // sync
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
|
var pk = this.definition.keyFrom;
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var relationName = this.definition.name;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
var autoId = this.definition.options.autoId !== false;
|
||||||
|
|
||||||
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
|
cb = targetModelData;
|
||||||
|
targetModelData = {};
|
||||||
|
}
|
||||||
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
|
var embeddedList = modelInstance[relationName] || [];
|
||||||
|
|
||||||
|
var inst = this.build(targetModelData);
|
||||||
|
|
||||||
|
var err = inst.isValid() ? null : new ValidationError(inst);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
var index = embeddedList.indexOf(inst);
|
||||||
|
if (index > -1) embeddedList.splice(index, 1);
|
||||||
|
return process.nextTick(function() {
|
||||||
|
cb(err, embeddedList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modelInstance.updateAttribute(relationName,
|
||||||
|
embeddedList, function(err, modelInst) {
|
||||||
|
cb(err, modelInst[relationName]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedsMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
|
||||||
|
var pk = this.definition.keyFrom;
|
||||||
|
var modelTo = this.definition.modelTo;
|
||||||
|
var relationName = this.definition.name;
|
||||||
|
var modelInstance = this.modelInstance;
|
||||||
|
var autoId = this.definition.options.autoId !== false;
|
||||||
|
|
||||||
|
var embeddedList = modelInstance[relationName] || [];
|
||||||
|
|
||||||
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
|
cb = targetModelData;
|
||||||
|
targetModelData = {};
|
||||||
|
}
|
||||||
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
|
if (typeof targetModelData[pk] !== 'number' && autoId) {
|
||||||
|
var ids = embeddedList.map(function(m) {
|
||||||
|
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
||||||
|
});
|
||||||
|
targetModelData[pk] = (Math.max(ids) || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.definition.applyProperties(this.modelInstance, targetModelData);
|
||||||
|
|
||||||
|
var inst = new modelTo(targetModelData);
|
||||||
|
|
||||||
|
if (this.definition.options.prepend) {
|
||||||
|
embeddedList.unshift(inst);
|
||||||
|
} else {
|
||||||
|
embeddedList.push(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inst;
|
||||||
|
};
|
||||||
|
|
|
@ -161,3 +161,7 @@ RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params
|
||||||
RelationMixin.hasOne = function hasMany(modelTo, params) {
|
RelationMixin.hasOne = function hasMany(modelTo, params) {
|
||||||
RelationDefinition.hasOne(this, modelTo, params);
|
RelationDefinition.hasOne(this, modelTo, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RelationMixin.embedsMany = function hasMany(modelTo, params) {
|
||||||
|
RelationDefinition.embedsMany(this, modelTo, params);
|
||||||
|
};
|
||||||
|
|
|
@ -224,6 +224,8 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
var where = (this._scope && this._scope.where) || {};
|
var where = (this._scope && this._scope.where) || {};
|
||||||
targetClass.destroyAll(where, cb);
|
targetClass.destroyAll(where, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -4,6 +4,7 @@ var should = require('./init.js');
|
||||||
var db, Book, Chapter, Author, Reader;
|
var db, Book, Chapter, Author, Reader;
|
||||||
var Category, Product;
|
var Category, Product;
|
||||||
var Picture, PictureLink;
|
var Picture, PictureLink;
|
||||||
|
var Person, Address;
|
||||||
|
|
||||||
describe('relations', function () {
|
describe('relations', function () {
|
||||||
|
|
||||||
|
@ -1218,4 +1219,293 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('embedsMany', function () {
|
||||||
|
before(function (done) {
|
||||||
|
db = getSchema();
|
||||||
|
Person = db.define('Person', {name: String});
|
||||||
|
Address = db.define('Address', {street: String});
|
||||||
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
|
db.automigrate(function () {
|
||||||
|
Person.destroyAll(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be declared', function (done) {
|
||||||
|
Person.embedsMany(Address);
|
||||||
|
db.automigrate(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have setup embedded accessor/scope', function() {
|
||||||
|
var p = new Person({ name: 'Fred' });
|
||||||
|
p.addresses.should.be.an.array;
|
||||||
|
p.addresses.should.have.length(0);
|
||||||
|
p.addressList.should.be.a.function;
|
||||||
|
p.addressList.findById.should.be.a.function;
|
||||||
|
p.addressList.updateById.should.be.a.function;
|
||||||
|
p.addressList.destroy.should.be.a.function;
|
||||||
|
p.addressList.exists.should.be.a.function;
|
||||||
|
p.addressList.create.should.be.a.function;
|
||||||
|
p.addressList.build.should.be.a.function;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create embedded items on scope', function(done) {
|
||||||
|
Person.create({ name: 'Fred' }, function(err, p) {
|
||||||
|
p.addressList.create({ street: 'Street 1' }, function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
addresses.should.have.length(1);
|
||||||
|
addresses[0].id.should.equal(1);
|
||||||
|
addresses[0].street.should.equal('Street 1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create embedded items on scope', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.create({ street: 'Street 2' }, function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
addresses.should.have.length(2);
|
||||||
|
addresses[0].id.should.equal(1);
|
||||||
|
addresses[0].street.should.equal('Street 1');
|
||||||
|
addresses[1].id.should.equal(2);
|
||||||
|
addresses[1].street.should.equal('Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return embedded items from scope', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList(function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
addresses.should.have.length(2);
|
||||||
|
addresses[0].id.should.equal(1);
|
||||||
|
addresses[0].street.should.equal('Street 1');
|
||||||
|
addresses[1].id.should.equal(2);
|
||||||
|
addresses[1].street.should.equal('Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter embedded items on scope', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList({ where: { street: 'Street 2' } }, function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
addresses.should.have.length(1);
|
||||||
|
addresses[0].id.should.equal(2);
|
||||||
|
addresses[0].street.should.equal('Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate embedded items', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.create({}, function(err, addresses) {
|
||||||
|
should.exist(err);
|
||||||
|
err.name.should.equal('ValidationError');
|
||||||
|
err.details.codes.street.should.eql(['presence']);
|
||||||
|
addresses.should.have.length(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.findById(2, function(err, address) {
|
||||||
|
address.should.be.instanceof(Address);
|
||||||
|
address.id.should.equal(2);
|
||||||
|
address.street.should.equal('Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if item exists', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.exists(2, function(err, exists) {
|
||||||
|
should.not.exist(err);
|
||||||
|
exists.should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.updateById(2, { street: 'New Street' }, function(err, address) {
|
||||||
|
address.should.be.instanceof(Address);
|
||||||
|
address.id.should.equal(2);
|
||||||
|
address.street.should.equal('New Street');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate the update of embedded items', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.updateById(2, { street: null }, function(err, address) {
|
||||||
|
err.name.should.equal('ValidationError');
|
||||||
|
err.details.codes.street.should.eql(['presence']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find embedded items by id - verify', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.findById(2, function(err, address) {
|
||||||
|
address.should.be.instanceof(Address);
|
||||||
|
address.id.should.equal(2);
|
||||||
|
address.street.should.equal('New Street');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addresses.should.have.length(2);
|
||||||
|
p.addressList.destroy(1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
p.addresses.should.have.length(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have embedded items - verify', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addresses.should.have.length(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('embedsMany - explicit ids', function () {
|
||||||
|
before(function (done) {
|
||||||
|
db = getSchema();
|
||||||
|
Person = db.define('Person', {name: String});
|
||||||
|
Address = db.define('Address', {id: { type: String, id: true }, street: String});
|
||||||
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
|
db.automigrate(function () {
|
||||||
|
Person.destroyAll(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be declared', function (done) {
|
||||||
|
Person.embedsMany(Address, { options: { autoId: false, validate: true } });
|
||||||
|
db.automigrate(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create embedded items on scope', function(done) {
|
||||||
|
Person.create({ name: 'Fred' }, function(err, p) {
|
||||||
|
p.addressList.create({ id: 'home', street: 'Street 1' }, function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, addresses) {
|
||||||
|
addresses.should.have.length(2);
|
||||||
|
addresses[0].id.should.equal('home');
|
||||||
|
addresses[0].street.should.equal('Street 1');
|
||||||
|
addresses[1].id.should.equal('work');
|
||||||
|
addresses[1].street.should.equal('Work Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.findById('work', function(err, address) {
|
||||||
|
address.should.be.instanceof(Address);
|
||||||
|
address.id.should.equal('work');
|
||||||
|
address.street.should.equal('Work Street 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check for duplicate ids', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.create({ id: 'home', street: 'Invalid' }, function(err, addresses) {
|
||||||
|
should.exist(err);
|
||||||
|
err.name.should.equal('ValidationError');
|
||||||
|
err.details.codes.addresses.should.eql(['uniqueness']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addressList.updateById('home', { street: 'New Street' }, function(err, address) {
|
||||||
|
address.should.be.instanceof(Address);
|
||||||
|
address.id.should.equal('home');
|
||||||
|
address.street.should.equal('New Street');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove embedded items by id', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addresses.should.have.length(2);
|
||||||
|
p.addressList.destroy('home', function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
p.addresses.should.have.length(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have embedded items - verify', function(done) {
|
||||||
|
Person.findOne(function(err, p) {
|
||||||
|
p.addresses.should.have.length(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate all embedded items', function(done) {
|
||||||
|
var addresses = [];
|
||||||
|
addresses.push({ id: 'home', street: 'Home Street' });
|
||||||
|
addresses.push({ id: 'work', street: '' });
|
||||||
|
Person.create({ name: 'Wilma', addresses: addresses }, function(err, p) {
|
||||||
|
err.name.should.equal('ValidationError');
|
||||||
|
var expected = 'The `Person` instance is not valid. ';
|
||||||
|
expected += 'Details: `addresses` contains invalid item: `work` (street can\'t be blank).';
|
||||||
|
err.message.should.equal(expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build embedded items', function(done) {
|
||||||
|
Person.create({ name: 'Wilma' }, function(err, p) {
|
||||||
|
p.addressList.build({ id: 'home', street: 'Home' });
|
||||||
|
p.addressList.build({ id: 'work', street: 'Work' });
|
||||||
|
p.addresses.should.have.length(2);
|
||||||
|
p.save(function(err, p) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have embedded items - verify', function(done) {
|
||||||
|
Person.findOne({ where: { name: 'Wilma' } }, function(err, p) {
|
||||||
|
p.name.should.equal('Wilma');
|
||||||
|
p.addresses.should.have.length(2);
|
||||||
|
p.addresses[0].id.should.equal('home');
|
||||||
|
p.addresses[0].street.should.equal('Home');
|
||||||
|
p.addresses[1].id.should.equal('work');
|
||||||
|
p.addresses[1].street.should.equal('Work');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue