Merge branch 'release/2.9.0' into production

This commit is contained in:
Raymond Feng 2014-09-12 22:53:08 -07:00
commit fd68cae20b
10 changed files with 471 additions and 65 deletions

View File

@ -137,14 +137,16 @@ DataAccessObject.create = function (data, callback) {
}
var obj;
var idValue = getIdValue(this, data);
// if we come from save
if (data instanceof Model && !getIdValue(this, data)) {
if (data instanceof Model && !idValue) {
obj = data;
} else {
obj = new Model(data);
}
data = obj.toObject(true);
// validation required
obj.isValid(function (valid) {
if (valid) {
@ -170,6 +172,7 @@ DataAccessObject.create = function (data, callback) {
if (err) {
return callback(err, obj);
}
obj.__persisted = true;
saveDone.call(obj, function () {
createDone.call(obj, function () {
callback(err, obj);
@ -315,7 +318,7 @@ DataAccessObject.findById = function find(id, cb) {
if (!getIdValue(this, data)) {
setIdValue(this, data, id);
}
obj = new this(data, {applySetters: false});
obj = new this(data, {applySetters: false, persisted: true});
}
cb(err, obj);
}.bind(this));
@ -732,7 +735,7 @@ DataAccessObject.find = function find(query, cb) {
this.getDataSource().connector.all(this.modelName, query, function (err, data) {
if (data && data.forEach) {
data.forEach(function (d, i) {
var obj = new self(d, {fields: query.fields, applySetters: false});
var obj = new self(d, {fields: query.fields, applySetters: false, persisted: true});
if (query && query.include) {
if (query.collect) {
@ -928,7 +931,7 @@ DataAccessObject.prototype.save = function (options, callback) {
var data = inst.toObject(true);
var modelName = Model.modelName;
if (!getIdValue(Model, this)) {
if (this.isNewRecord()) {
return Model.create(this, callback);
}
@ -959,7 +962,7 @@ DataAccessObject.prototype.save = function (options, callback) {
if (err) {
return callback(err, inst);
}
inst._initProperties(data);
inst._initProperties(data, { persisted: true });
updateDone.call(inst, function () {
saveDone.call(inst, function () {
callback(err, inst);
@ -1027,7 +1030,7 @@ DataAccessObject.updateAll = function (where, data, cb) {
};
DataAccessObject.prototype.isNewRecord = function () {
return !getIdValue(this.constructor, this);
return !this.__persisted;
};
/**
@ -1168,6 +1171,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
inst._adapter().updateAttributes(model, getIdValue(inst.constructor, inst),
inst.constructor._forDB(typedData), function (err) {
if (!err) inst.__persisted = true;
done.call(inst, function () {
saveDone.call(inst, function () {
if(cb) cb(err, inst);

View File

@ -489,6 +489,9 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
var idType = this.connector.getDefaultIdType() || String;
idProp.type = idType;
modelClass.definition.properties[idName].type = idType;
if (settings.forceId) {
modelClass.validatesAbsenceOf(idName, { if: 'isNewRecord' });
}
}
if (this.connector.define) {
// pass control to connector

View File

@ -47,6 +47,7 @@ function ModelBaseClass(data, options) {
* @param {Object} options An object to control the instantiation
* @property {Boolean} applySetters Controls if the setters will be applied
* @property {Boolean} strict Set the instance level strict mode
* @property {Boolean} persisted Whether the instance has been persisted
* @private
*/
ModelBaseClass.prototype._initProperties = function (data, options) {
@ -67,7 +68,11 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
if(strict === undefined) {
strict = ctor.definition.settings.strict;
}
if (options.persisted !== undefined) {
this.__persisted = options.persisted === true;
}
if (ctor.hideInternalProperties) {
// Object.defineProperty() is expensive. We only try to make the internal
// properties hidden (non-enumerable) if the model class has the

View File

@ -11,9 +11,6 @@ var applyFilter = require('./connectors/memory').applyFilter;
var ValidationError = require('./validations.js').ValidationError;
var debug = require('debug')('loopback:relations');
exports.Relation = Relation;
exports.RelationDefinition = RelationDefinition;
var RelationTypes = {
belongsTo: 'belongsTo',
hasMany: 'hasMany',
@ -24,16 +21,6 @@ var RelationTypes = {
embedsMany: 'embedsMany'
};
exports.RelationTypes = RelationTypes;
exports.HasMany = HasMany;
exports.HasManyThrough = HasManyThrough;
exports.HasOne = HasOne;
exports.HasAndBelongsToMany = HasAndBelongsToMany;
exports.BelongsTo = BelongsTo;
exports.ReferencesMany = ReferencesMany;
exports.EmbedsOne = EmbedsOne;
exports.EmbedsMany = EmbedsMany;
var RelationClasses = {
belongsTo: BelongsTo,
hasMany: HasMany,
@ -45,6 +32,21 @@ var RelationClasses = {
embedsMany: EmbedsMany
};
exports.Relation = Relation;
exports.RelationDefinition = RelationDefinition;
exports.RelationTypes = RelationTypes;
exports.RelationClasses = RelationClasses;
exports.HasMany = HasMany;
exports.HasManyThrough = HasManyThrough;
exports.HasOne = HasOne;
exports.HasAndBelongsToMany = HasAndBelongsToMany;
exports.BelongsTo = BelongsTo;
exports.ReferencesMany = ReferencesMany;
exports.EmbedsOne = EmbedsOne;
exports.EmbedsMany = EmbedsMany;
function normalizeType(type) {
if (!type) {
return type;
@ -1711,6 +1713,7 @@ RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
relationMethod.build = relation.build.bind(relation);
relationMethod.update = relation.update.bind(relation);
relationMethod.destroy = relation.destroy.bind(relation);
relationMethod.value = relation.embeddedValue.bind(relation);
relationMethod._targetClass = definition.modelTo.modelName;
return relationMethod;
}
@ -1749,6 +1752,8 @@ EmbedsOne.prototype.related = function (refresh, params) {
}
var embeddedInstance = modelInstance[propertyName];
embeddedInstance.__persisted = true;
if (typeof params === 'function') { // acts as async getter
var cb = params;
process.nextTick(function() {
@ -1759,6 +1764,11 @@ EmbedsOne.prototype.related = function (refresh, params) {
}
};
EmbedsOne.prototype.embeddedValue = function(modelInstance) {
modelInstance = modelInstance || this.modelInstance;
return modelInstance[this.definition.keyFrom];
};
EmbedsOne.prototype.create = function (targetModelData, cb) {
var modelTo = this.definition.modelTo;
var propertyName = this.definition.keyFrom;
@ -1773,29 +1783,53 @@ EmbedsOne.prototype.create = function (targetModelData, cb) {
var inst = this.build(targetModelData);
var err = inst.isValid() ? null : new ValidationError(inst);
if (err) {
return process.nextTick(function() {
cb(err);
var updateEmbedded = function() {
modelInstance.updateAttribute(propertyName,
inst, function(err) {
cb(err, err ? null : inst);
});
}
};
modelInstance.updateAttribute(propertyName,
inst, function(err) {
cb(err, err ? null : inst);
});
if (this.definition.options.persistent) {
inst.save(function(err) { // will validate
if (err) return cb(err, inst);
updateEmbedded();
});
} else {
var err = inst.isValid() ? null : new ValidationError(inst);
if (err) {
process.nextTick(function() {
cb(err);
});
} else {
updateEmbedded();
}
}
};
EmbedsOne.prototype.build = function (targetModelData) {
var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance;
var propertyName = this.definition.keyFrom;
var forceId = this.definition.options.forceId;
var persistent = this.definition.options.persistent;
var connector = modelTo.dataSource.connector;
targetModelData = targetModelData || {};
this.definition.applyProperties(modelInstance, targetModelData);
var pk = this.definition.keyTo;
var pkProp = modelTo.definition.properties[pk];
var assignId = (forceId || targetModelData[pk] === undefined);
assignId = assignId && !persistent && (pkProp && pkProp.generated);
if (assignId && typeof connector.generateId === 'function') {
var id = connector.generateId(modelTo.modelName, targetModelData, pk);
targetModelData[pk] = id;
}
var embeddedInstance = new modelTo(targetModelData);
modelInstance[propertyName] = embeddedInstance;
@ -1863,12 +1897,25 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
embed: true
});
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, {
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, {
type: [modelTo], default: function() { return []; }
});
if (typeof modelTo.dataSource.connector.generateId !== 'function') {
modelTo.validatesPresenceOf(idName); // unique id is required
modelFrom.validate(propertyName, function(err) {
var self = this;
var embeddedList = this[propertyName] || [];
var hasErrors = false;
embeddedList.forEach(function(item, idx) {
if (item instanceof modelTo && item[idName] == undefined) {
hasErrors = true;
var msg = 'contains invalid item at index `' + idx + '`:';
msg += ' `' + idName + '` is blank';
self.errors.add(propertyName, msg, 'invalid');
}
});
if (hasErrors) err(false);
});
}
if (!params.polymorphic) {
@ -1891,13 +1938,17 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
var self = this;
var embeddedList = this[propertyName] || [];
var hasErrors = false;
embeddedList.forEach(function(item) {
embeddedList.forEach(function(item, idx) {
if (item instanceof modelTo) {
if (!item.isValid()) {
hasErrors = true;
var id = item[idName] || '(blank)';
var id = item[idName];
var first = Object.keys(item.errors)[0];
var msg = 'contains invalid item: `' + id + '`';
if (id) {
var msg = 'contains invalid item: `' + id + '`';
} else {
var msg = 'contains invalid item at index `' + idx + '`';
}
msg += ' (`' + first + '` ' + item.errors[first] + ')';
self.errors.add(propertyName, msg, 'invalid');
}
@ -1920,7 +1971,8 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
get: scopeMethod(definition, 'get'),
set: scopeMethod(definition, 'set'),
unset: scopeMethod(definition, 'unset'),
at: scopeMethod(definition, 'at')
at: scopeMethod(definition, 'at'),
value: scopeMethod(definition, 'embeddedValue')
};
var findByIdFunc = scopeMethods.findById;
@ -1968,6 +2020,12 @@ EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
var self = this;
var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance;
if (this.definition.options.persistent) {
var pk = this.definition.keyTo;
inst.__persisted = !!inst[pk];
} else {
inst.__persisted = true;
}
inst.triggerParent = function(actionName, callback) {
if (actionName === 'save' || actionName === 'destroy') {
var embeddedList = self.embeddedList();
@ -2000,7 +2058,8 @@ EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
}
};
EmbedsMany.prototype.embeddedList = function(modelInstance) {
EmbedsMany.prototype.embeddedList =
EmbedsMany.prototype.embeddedValue = function(modelInstance) {
modelInstance = modelInstance || this.modelInstance;
var embeddedList = modelInstance[this.definition.keyFrom] || [];
embeddedList.forEach(this.prepareEmbeddedInstance.bind(this));
@ -2028,7 +2087,7 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
var params = mergeQuery(actualCond, scopeParams);
if (params.where) { // TODO [fabien] Support order/sorting
if (params.where && Object.keys(params.where).length > 0) { // TODO [fabien] Support order/sorting
embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
}
@ -2180,28 +2239,39 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
var inst = this.build(targetModelData);
var err = inst.isValid() ? null : new ValidationError(inst);
if (err) {
return process.nextTick(function() {
cb(err);
var updateEmbedded = function() {
modelInstance.updateAttribute(propertyName,
embeddedList, function(err, modelInst) {
cb(err, err ? null : inst);
});
}
};
modelInstance.updateAttribute(propertyName,
embeddedList, function(err, modelInst) {
cb(err, err ? null : inst);
});
if (this.definition.options.persistent) {
inst.save(function(err) { // will validate
if (err) return cb(err, inst);
updateEmbedded();
});
} else {
var err = inst.isValid() ? null : new ValidationError(inst);
if (err) {
process.nextTick(function() {
cb(err);
});
} else {
updateEmbedded();
}
}
};
EmbedsMany.prototype.build = function(targetModelData) {
var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance;
var forceId = this.definition.options.forceId;
var persistent = this.definition.options.persistent;
var connector = modelTo.dataSource.connector;
var pk = this.definition.keyTo;
var pkProp = modelTo.definition.properties[pk]
var pkProp = modelTo.definition.properties[pk];
var pkType = pkProp && pkProp.type;
var embeddedList = this.embeddedList();
@ -2209,6 +2279,7 @@ EmbedsMany.prototype.build = function(targetModelData) {
targetModelData = targetModelData || {};
var assignId = (forceId || targetModelData[pk] === undefined);
assignId = assignId && !persistent;
if (assignId && pkType === Number) {
var ids = embeddedList.map(function(m) {

View File

@ -55,13 +55,13 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
} else {
throw new Error('Method can be only called with one or two arguments');
}
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or refresh is required
var params = mergeQuery(actualCond, scopeParams);
var targetModel = this.targetModel(receiver);
return targetModel.find(params, function (err, data) {
targetModel.find(params, function (err, data) {
if (!err && saveOnCache) {
defineCachedRelations(self);
self.__cachedRelations[name] = data;
@ -149,10 +149,16 @@ function defineScope(cls, targetClass, name, params, methods, options) {
var self = this;
var f = function(condOrRefresh, cb) {
if(arguments.length === 1) {
definition.related(self, f._scope, condOrRefresh);
if (arguments.length === 0) {
if (typeof f.value === 'function') {
return f.value(self);
} else if (self.__cachedRelations) {
return self.__cachedRelations[name];
}
} else if (arguments.length === 1) {
return definition.related(self, f._scope, condOrRefresh);
} else {
definition.related(self, f._scope, condOrRefresh, cb);
return definition.related(self, f._scope, condOrRefresh, cb);
}
};

View File

@ -641,13 +641,14 @@ function configure(cls, validation, args, opts) {
conf.customValidator = args.pop();
}
conf.validation = validation;
var attr = args[0];
if (typeof attr === 'string') {
var validation = extend({}, conf);
validation.options = opts || {};
cls.validations[attr] = cls.validations[attr] || [];
cls.validations[attr].push(validation);
}
args.forEach(function (attr) {
if (typeof attr === 'string') {
var validation = extend({}, conf);
validation.options = opts || {};
cls.validations[attr] = cls.validations[attr] || [];
cls.validations[attr].push(validation);
}
});
}
function Errors() {

View File

@ -1,6 +1,6 @@
{
"name": "loopback-datasource-juggler",
"version": "2.8.0",
"version": "2.9.0",
"description": "LoopBack DataSoure Juggler",
"keywords": [
"StrongLoop",

View File

@ -16,7 +16,7 @@ describe('manipulation', function () {
age: {type: Number, index: true},
dob: Date,
createdAt: {type: Number, default: Date.now}
});
}, { forceId: true });
db.automigrate(done);
@ -40,6 +40,18 @@ describe('manipulation', function () {
});
});
});
it('should instantiate an object', function (done) {
var p = new Person({name: 'Anatoliy'});
p.name.should.equal('Anatoliy');
p.isNewRecord().should.be.true;
p.save(function(err, inst) {
should.not.exist(err);
inst.isNewRecord().should.be.false;
inst.should.equal(p);
done();
});
});
it('should return instance of object', function (done) {
var person = Person.create(function (err, p) {
@ -50,6 +62,31 @@ describe('manipulation', function () {
person.should.be.an.instanceOf(Person);
should.not.exist(person.id);
});
it('should not allow user-defined value for the id of object - create', function (done) {
Person.create({id: 123456}, function (err, p) {
err.should.be.instanceof(ValidationError);
err.message.should.equal('The `Person` instance is not valid. Details: `id` can\'t be set.');
err.statusCode.should.equal(422);
p.should.be.instanceof(Person);
p.id.should.equal(123456);
p.isNewRecord().should.be.true;
done();
});
});
it('should not allow user-defined value for the id of object - save', function (done) {
var p = new Person({id: 123456});
p.isNewRecord().should.be.true;
p.save(function(err, inst) {
err.should.be.instanceof(ValidationError);
err.message.should.equal('The `Person` instance is not valid. Details: `id` can\'t be set.');
err.statusCode.should.equal(422);
inst.id.should.equal(123456);
inst.isNewRecord().should.be.true;
done();
});
});
it('should work when called without callback', function (done) {
Person.afterCreate = function (next) {

View File

@ -13,6 +13,10 @@ var getTransientDataSource = function(settings) {
return new DataSource('transient', settings, db.modelBuilder);
};
var getMemoryDataSource = function(settings) {
return new DataSource('memory', settings, db.modelBuilder);
};
describe('relations', function () {
describe('hasMany', function () {
@ -91,10 +95,14 @@ describe('relations', function () {
should.not.exist(err);
should.exist(ch);
ch.should.have.lengthOf(3);
var chapters = book.chapters();
chapters.should.eql(ch);
book.chapters({order: 'name DESC'}, function (e, c) {
should.not.exist(e);
should.exist(c);
c.shift().name.should.equal('z');
c.pop().name.should.equal('a');
done();
@ -298,6 +306,10 @@ describe('relations', function () {
});
function verify(physician) {
physician.patients(function (err, ch) {
var patients = physician.patients();
patients.should.eql(ch);
should.not.exist(err);
should.exist(ch);
ch.should.have.lengthOf(3);
@ -842,6 +854,10 @@ describe('relations', function () {
Author.findOne(function (err, author) {
author.avatar(function (err, p) {
should.not.exist(err);
var avatar = author.avatar();
avatar.should.equal(p);
p.name.should.equal('Avatar');
p.imageableId.should.eql(author.id);
p.imageableType.should.equal('Author');
@ -971,6 +987,10 @@ describe('relations', function () {
Author.findOne(function (err, author) {
author.pictures(function (err, pics) {
should.not.exist(err);
var pictures = author.pictures();
pictures.should.eql(pics);
pics.should.have.length(1);
pics[0].name.should.equal('Author Pic');
done();
@ -1638,6 +1658,9 @@ describe('relations', function () {
article.tags(function (e, tags) {
should.not.exist(e);
should.exist(tags);
article.tags().should.eql(tags);
done();
});
});
@ -1756,6 +1779,7 @@ describe('relations', function () {
passport.toObject().should.eql({name: 'Fredric'});
passport.should.be.an.instanceOf(Passport);
passport.should.equal(p.passport);
passport.should.equal(p.passportItem.value());
done();
});
});
@ -1829,6 +1853,81 @@ describe('relations', function () {
});
describe('embedsOne - persisted model', function () {
// This test spefically uses the Memory connector
// in order to test the use of the auto-generated
// id, in the sequence of the related model.
before(function () {
db = getMemoryDataSource();
Person = db.define('Person', {name: String});
Passport = db.define('Passport',
{name:{type:'string', required: true}}
);
});
it('can be declared using embedsOne method', function (done) {
Person.embedsOne(Passport, {
options: {persistent: true}
});
db.automigrate(done);
});
it('should create an item - to offset id', function(done) {
Passport.create({name:'Wilma'}, function(err, p) {
should.not.exist(err);
p.id.should.equal(1);
p.name.should.equal('Wilma');
done();
});
});
it('should create an embedded item on scope', function(done) {
Person.create({name: 'Fred'}, function(err, p) {
should.not.exist(err);
p.passportItem.create({name: 'Fredric'}, function(err, passport) {
should.not.exist(err);
p.passport.id.should.eql(2);
p.passport.name.should.equal('Fredric');
done();
});
});
});
});
describe('embedsOne - generated id', function () {
before(function () {
tmp = getTransientDataSource();
db = getSchema();
Person = db.define('Person', {name: String});
Passport = tmp.define('Passport',
{id: {type:'string', id: true, generated:true}},
{name: {type:'string', required: true}}
);
});
it('can be declared using embedsOne method', function (done) {
Person.embedsOne(Passport);
db.automigrate(done);
});
it('should create an embedded item on scope', function(done) {
Person.create({name: 'Fred'}, function(err, p) {
should.not.exist(err);
p.passportItem.create({name: 'Fredric'}, function(err, passport) {
should.not.exist(err);
passport.id.should.match(/^[0-9a-fA-F]{24}$/);
p.passport.name.should.equal('Fredric');
done();
});
});
});
});
describe('embedsMany', function () {
var address1, address2;
@ -1891,6 +1990,13 @@ describe('relations', function () {
Person.findOne(function(err, p) {
p.addressList(function(err, addresses) {
should.not.exist(err);
var list = p.addressList();
list.should.equal(addresses);
list.should.equal(p.addresses);
p.addressList.value().should.equal(list);
addresses.should.have.length(2);
addresses[0].id.should.eql(address1.id);
addresses[0].street.should.equal('Street 1');
@ -2012,6 +2118,45 @@ describe('relations', function () {
});
describe('embedsMany - numeric ids + forceId', function () {
before(function (done) {
tmp = getTransientDataSource();
db = getSchema();
Person = db.define('Person', {name: String});
Address = tmp.define('Address', {
id: {type: Number, id:true},
street: String
});
db.automigrate(function () {
Person.destroyAll(done);
});
});
it('can be declared', function (done) {
Person.embedsMany(Address, {options: {forceId: true}});
db.automigrate(done);
});
it('should create embedded items on scope', function(done) {
Person.create({ name: 'Fred' }, function(err, p) {
p.addressList.create({ street: 'Street 1' }, function(err, address) {
should.not.exist(err);
address.id.should.equal(1);
p.addressList.create({ street: 'Street 2' }, function(err, address) {
address.id.should.equal(2);
p.addressList.create({ id: 12345, street: 'Street 3' }, function(err, address) {
address.id.should.equal(3);
done();
});
});
});
});
});
});
describe('embedsMany - explicit ids', function () {
before(function (done) {
tmp = getTransientDataSource();
@ -2183,6 +2328,134 @@ describe('relations', function () {
});
describe('embedsMany - persisted model', function () {
var address0, address1, address2;
var person;
// This test spefically uses the Memory connector
// in order to test the use of the auto-generated
// id, in the sequence of the related model.
before(function (done) {
db = getMemoryDataSource();
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) {
// to save related model itself, set
// persistent: true
Person.embedsMany(Address, {
scope: {order: 'street'},
options: {persistent: true}
});
db.automigrate(done);
});
it('should create individual items (0)', function(done) {
Address.create({ street: 'Street 0' }, function(err, inst) {
inst.id.should.equal(1); // offset sequence
address0 = inst;
done();
});
});
it('should create individual items (1)', function(done) {
Address.create({ street: 'Street 1' }, function(err, inst) {
inst.id.should.equal(2);
address1 = inst;
done();
});
});
it('should create individual items (2)', function(done) {
Address.create({ street: 'Street 2' }, function(err, inst) {
inst.id.should.equal(3);
address2 = inst;
done();
});
});
it('should create individual items (3)', function(done) {
Address.create({ street: 'Street 3' }, function(err, inst) {
inst.id.should.equal(4); // offset sequence
done();
});
});
it('should add embedded items on scope', function(done) {
Person.create({ name: 'Fred' }, function(err, p) {
person = p;
p.addressList.create(address1.toObject(), function(err, address) {
should.not.exist(err);
address.id.should.eql(2);
address.street.should.equal('Street 1');
p.addressList.create(address2.toObject(), function(err, address) {
should.not.exist(err);
address.id.should.eql(3);
address.street.should.equal('Street 2');
done();
});
});
});
});
it('should create embedded items on scope', function(done) {
Person.findById(person.id, function(err, p) {
p.addressList.create({ street: 'Street 4' }, function(err, address) {
should.not.exist(err);
address.id.should.equal(5); // in Address sequence, correct offset
address.street.should.equal('Street 4');
done();
});
});
});
it('should have embedded items on scope', function(done) {
Person.findById(person.id, function(err, p) {
p.addressList(function(err, addresses) {
should.not.exist(err);
addresses.should.have.length(3);
addresses[0].street.should.equal('Street 1');
addresses[1].street.should.equal('Street 2');
addresses[2].street.should.equal('Street 4');
done();
});
});
});
it('should validate embedded items on scope - id', function(done) {
Person.create({ name: 'Wilma' }, function(err, p) {
p.addressList.create({ id: null, street: 'Street 1' }, function(err, address) {
should.not.exist(err);
address.street.should.equal('Street 1');
done();
});
});
});
it('should validate embedded items on scope - street', function(done) {
Person.create({ name: 'Wilma' }, function(err, p) {
p.addressList.create({ id: 1234 }, function(err, address) {
should.exist(err);
err.name.should.equal('ValidationError');
err.details.codes.street.should.eql(['presence']);
var expected = 'The `Address` instance is not valid. ';
expected += 'Details: `street` can\'t be blank.';
err.message.should.equal(expected);
done();
});
});
});
});
describe('embedsMany - relations, scope and properties', function () {
var category, job1, job2, job3;

View File

@ -145,9 +145,15 @@ describe('validations', function () {
it('should validate presence', function () {
User.validatesPresenceOf('name', 'email');
var validations = User.validations;
validations.name.should.eql([{validation: 'presence', options: {}}]);
validations.email.should.eql([{validation: 'presence', options: {}}]);
var u = new User;
u.isValid().should.not.be.true;
u.name = 1;
u.isValid().should.not.be.true;
u.email = 2;
u.isValid().should.be.true;
});