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.applyFilter = applyFilter;
|
||||
|
||||
function Memory(m, settings) {
|
||||
if (m instanceof Memory) {
|
||||
|
|
|
@ -7,6 +7,8 @@ var i8n = require('inflection');
|
|||
var defineScope = require('./scope.js').defineScope;
|
||||
var mergeQuery = require('./scope.js').mergeQuery;
|
||||
var ModelBaseClass = require('./model.js');
|
||||
var applyFilter = require('./connectors/memory').applyFilter;
|
||||
var ValidationError = require('./validations.js').ValidationError;
|
||||
|
||||
exports.Relation = Relation;
|
||||
exports.RelationDefinition = RelationDefinition;
|
||||
|
@ -15,7 +17,8 @@ var RelationTypes = {
|
|||
belongsTo: 'belongsTo',
|
||||
hasMany: 'hasMany',
|
||||
hasOne: 'hasOne',
|
||||
hasAndBelongsToMany: 'hasAndBelongsToMany'
|
||||
hasAndBelongsToMany: 'hasAndBelongsToMany',
|
||||
embedsMany: 'embedsMany'
|
||||
};
|
||||
|
||||
exports.RelationTypes = RelationTypes;
|
||||
|
@ -24,13 +27,15 @@ exports.HasManyThrough = HasManyThrough;
|
|||
exports.HasOne = HasOne;
|
||||
exports.HasAndBelongsToMany = HasAndBelongsToMany;
|
||||
exports.BelongsTo = BelongsTo;
|
||||
exports.EmbedsMany = EmbedsMany;
|
||||
|
||||
var RelationClasses = {
|
||||
belongsTo: BelongsTo,
|
||||
hasMany: HasMany,
|
||||
hasManyThrough: HasManyThrough,
|
||||
hasOne: HasOne,
|
||||
hasAndBelongsToMany: HasAndBelongsToMany
|
||||
hasAndBelongsToMany: HasAndBelongsToMany,
|
||||
embedsMany: EmbedsMany
|
||||
};
|
||||
|
||||
function normalizeType(type) {
|
||||
|
@ -75,6 +80,7 @@ function RelationDefinition(definition) {
|
|||
this.properties = definition.properties || {};
|
||||
this.options = definition.options || {};
|
||||
this.scope = definition.scope;
|
||||
this.embed = definition.embed === true;
|
||||
}
|
||||
|
||||
RelationDefinition.prototype.toJSON = function () {
|
||||
|
@ -290,6 +296,23 @@ function HasOne(definition, modelInstance) {
|
|||
|
||||
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
|
||||
|
@ -1375,3 +1398,310 @@ HasOne.prototype.related = function (refresh, params) {
|
|||
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) {
|
||||
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) || {};
|
||||
targetClass.destroyAll(where, cb);
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -4,6 +4,7 @@ var should = require('./init.js');
|
|||
var db, Book, Chapter, Author, Reader;
|
||||
var Category, Product;
|
||||
var Picture, PictureLink;
|
||||
var Person, Address;
|
||||
|
||||
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