From a446551a59781cd296af1a6c278401dc512f8b72 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Wed, 20 Aug 2014 14:03:38 +0200 Subject: [PATCH 1/6] Fix relations for RDBMS connectors (mysql, postgresql) - fix tests (explicit model/property definitions) - fix include vs. RDBMS model strictness --- lib/dao.js | 2 +- lib/include.js | 4 +++- lib/model.js | 25 +++++++++++++++++++------ lib/relation-definition.js | 3 +-- lib/utils.js | 4 ++-- test/relations.test.js | 18 ++++++++++-------- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/lib/dao.js b/lib/dao.js index 3d76be09..82b0585f 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -338,7 +338,7 @@ DataAccessObject.findByIds = function(ids, cond, cb) { } var filter = { where: {} }; - filter.where[pk] = { inq: ids }; + filter.where[pk] = { inq: [].concat(ids) }; mergeQuery(filter, cond || {}); this.find(filter, function(err, results) { cb(err, err ? results : utils.sortObjectsByIds(pk, ids, results)); diff --git a/lib/include.js b/lib/include.js index 5fe6548c..dacf78af 100644 --- a/lib/include.js +++ b/lib/include.js @@ -42,7 +42,7 @@ function Inclusion() { */ Inclusion.include = function (objects, include, cb) { var self = this; - + if (!include || (Array.isArray(include) && include.length === 0) || (isPlainObject(include) && Object.keys(include).length === 0)) { // The objects are empty @@ -123,6 +123,7 @@ Inclusion.include = function (objects, include, cb) { return callback(); } } + var inst = (obj instanceof self) ? obj : new self(obj); // Calling the relation method on the instance inst[relationName](function (err, result) { @@ -131,6 +132,7 @@ Inclusion.include = function (objects, include, cb) { } else { defineCachedRelations(obj); obj.__cachedRelations[relationName] = result; + if(obj === inst) { obj.__data[relationName] = result; obj.setStrict(false); diff --git a/lib/model.js b/lib/model.js index b1c28a99..dfcb0ef0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -12,6 +12,7 @@ var jutil = require('./jutil'); var List = require('./list'); var Hookable = require('./hooks'); var validations = require('./validations.js'); +var _extend = util._extend; // Set up an object for quick lookup var BASE_TYPES = { @@ -56,7 +57,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) { // Convert the data to be plain object to avoid pollutions data = data.toObject(false); } - var properties = ctor.definition.properties; + var properties = _extend({}, ctor.definition.properties); data = data || {}; options = options || {}; @@ -130,27 +131,39 @@ ModelBaseClass.prototype._initProperties = function (data, options) { self.__data[p] = propVal; } } else if (ctor.relations[p]) { + if (!properties[p]) { + var modelTo = ctor.relations[p].modelTo || ModelBaseClass; + var multiple = ctor.relations[p].multiple; + var typeName = multiple ? 'Array' : modelTo.modelName; + var propType = multiple ? [modelTo] : modelTo; + properties[p] = { name: typeName, type: propType }; + this.setStrict(false); + } + // Relation if (ctor.relations[p].type === 'belongsTo' && propVal != null) { // If the related model is populated self.__data[ctor.relations[p].keyFrom] = propVal[ctor.relations[p].keyTo]; + } else if (!self.__data[p] && propVal != null) { + self.__data[p] = propVal; } self.__cachedRelations[p] = propVal; } else { // Un-managed property - if (strict === false) { - self[p] = self.__data[p] = propVal; + if (strict === false || self.__cachedRelations[p]) { + self[p] = self.__data[p] = propVal || self.__cachedRelations[p]; } else if (strict === 'throw') { throw new Error('Unknown property: ' + p); } } } - + keys = Object.keys(properties); size = keys.length; for (k = 0; k < size; k++) { p = keys[k]; + // var prop propVal = self.__data[p]; // Set default values @@ -172,10 +185,10 @@ ModelBaseClass.prototype._initProperties = function (data, options) { self.__data[p] = def; } } - + // Handle complex types (JSON/Object) var type = properties[p].type; - if (! BASE_TYPES[type.name]) { + if (!BASE_TYPES[type.name]) { if (typeof self.__data[p] !== 'object' && self.__data[p]) { try { self.__data[p] = JSON.parse(self.__data[p] + ''); diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 84ebc44e..22804c3f 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -954,7 +954,7 @@ HasManyThrough.prototype.exists = function (acInst, done) { var keys = throughKeys(definition); var fk1 = keys[0]; var fk2 = keys[1]; - + query[fk1] = this.modelInstance[pk1]; query[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst; @@ -1175,7 +1175,6 @@ BelongsTo.prototype.related = function (refresh, params) { cachedValue = self.getCache(); } if (params instanceof ModelBaseClass) { // acts as setter - modelTo = params.constructor; modelInstance[fk] = params[pk]; if (discriminator) { diff --git a/lib/utils.js b/lib/utils.js index 6afb9129..c774ecae 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -210,12 +210,12 @@ function isPlainObject(obj) { function sortObjectsByIds(idName, ids, objects, strict) { ids = ids.map(function(id) { - return (typeof id === 'object') ? id.toString() : id; + return (typeof id === 'object') ? String(id) : id; }); var indexOf = function(x) { var isObj = (typeof x[idName] === 'object'); // ObjectID - var id = isObj ? x[idName].toString() : x[idName]; + var id = isObj ? String(x[idName]) : x[idName]; return ids.indexOf(id); }; diff --git a/test/relations.test.js b/test/relations.test.js index 871ecb54..d227eb1d 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -189,7 +189,7 @@ describe('relations', function () { }); describe('hasMany through', function () { - var Physician, Patient, Appointment; + var Physician, Patient, Appointment, Address; before(function (done) { db = getSchema(); @@ -199,13 +199,15 @@ describe('relations', function () { default: function () { return new Date(); }}}); - + Address = db.define('Address', {name: String}); + Physician.hasMany(Patient, {through: Appointment}); Patient.hasMany(Physician, {through: Appointment}); + Patient.belongsTo(Address); Appointment.belongsTo(Patient); Appointment.belongsTo(Physician); - db.automigrate(['Physician', 'Patient', 'Appointment'], function (err) { + db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], function (err) { done(err); }); }); @@ -277,11 +279,10 @@ describe('relations', function () { }); it('should allow to use include syntax on related data', function (done) { - var Address = db.define('Address', {name: String}); - Patient.belongsTo(Address); Physician.create(function (err, physician) { physician.patients.create({name: 'a'}, function (err, patient) { Address.create({name: 'z'}, function (err, address) { + should.not.exist(err); patient.address(address); patient.save(function() { verify(physician, address.id); @@ -353,6 +354,7 @@ describe('relations', function () { var id; Physician.create(function (err, physician) { physician.patients.create({name: 'a'}, function (err, ch) { + should.not.exist(err); id = ch.id; physician.patients.create({name: 'z'}, function () { physician.patients.create({name: 'c'}, function () { @@ -1169,7 +1171,7 @@ describe('relations', function () { var Person, Passport; it('can be declared with scope and properties', function (done) { - Person = db.define('Person', {name: String, age: Number}); + Person = db.define('Person', {name: String, age: Number, passportNotes: String}); Passport = db.define('Passport', {name: String, notes: String}); Passport.belongsTo(Person, { properties: { notes: 'passportNotes' }, @@ -1644,7 +1646,7 @@ describe('relations', function () { db = getSchema(); Category = db.define('Category', {name: String}); Product = db.define('Product', {name: String}); - Link = db.define('Link', {name: String}); + Link = db.define('Link', {name: String, notes: String}); }); it('can be declared', function (done) { @@ -1878,7 +1880,7 @@ describe('relations', function () { Author = db.define('Author', {name: String}); Reader = db.define('Reader', {name: String}); - Link = db.define('Link'); // generic model + Link = db.define('Link', {name: String, notes: String}); // generic model Link.validatesPresenceOf('linkedId'); Link.validatesPresenceOf('linkedType'); From 3b81b928fbfcddf19ca42456ad18076b190b760e Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Tue, 19 Aug 2014 22:05:27 +0200 Subject: [PATCH 2/6] Implement DAO unsetAttribute --- lib/dao.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/dao.js b/lib/dao.js index 82b0585f..58ca673f 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1113,6 +1113,15 @@ DataAccessObject.prototype.setAttributes = function setAttributes(data) { Model.emit('set', inst); }; +DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullify) { + if (nullify) { + this[name] = this.__data[name] = null; + } else { + delete this[name]; + delete this.__data[name]; + } +}; + /** * Update set of attributes. * Performs validation before updating. From faed0510c1a751fe3dc5347e61897756717ea7f4 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Tue, 19 Aug 2014 22:06:55 +0200 Subject: [PATCH 3/6] Coerce embedded model types --- lib/model.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index dfcb0ef0..ee60ce72 100644 --- a/lib/model.js +++ b/lib/model.js @@ -189,6 +189,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) { // Handle complex types (JSON/Object) var type = properties[p].type; if (!BASE_TYPES[type.name]) { + if (typeof self.__data[p] !== 'object' && self.__data[p]) { try { self.__data[p] = JSON.parse(self.__data[p] + ''); @@ -196,7 +197,14 @@ ModelBaseClass.prototype._initProperties = function (data, options) { self.__data[p] = String(self.__data[p]); } } - if (type.name === 'Array' || Array.isArray(type)) { + + if (type.prototype instanceof ModelBaseClass) { + if (!(self.__data[p] instanceof type) + && typeof self.__data[p] === 'object' + && self.__data[p] !== null ) { + self.__data[p] = new type(self.__data[p]); + } + } else if (type.name === 'Array' || Array.isArray(type)) { if (!(self.__data[p] instanceof List) && self.__data[p] !== undefined && self.__data[p] !== null ) { From 1ec5357a6c0d3fb96cf164c8402741bf4ae5dacc Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Tue, 19 Aug 2014 22:10:35 +0200 Subject: [PATCH 4/6] Implemented embedsOne --- lib/relation-definition.js | 195 +++++++++++++++++++++++++++++++++++-- lib/relations.js | 10 +- test/relations.test.js | 127 +++++++++++++++++++++++- 3 files changed, 321 insertions(+), 11 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 22804c3f..f0621046 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -20,6 +20,7 @@ var RelationTypes = { hasOne: 'hasOne', hasAndBelongsToMany: 'hasAndBelongsToMany', referencesMany: 'referencesMany', + embedsOne: 'embedsOne', embedsMany: 'embedsMany' }; @@ -30,6 +31,7 @@ exports.HasOne = HasOne; exports.HasAndBelongsToMany = HasAndBelongsToMany; exports.BelongsTo = BelongsTo; exports.ReferencesMany = ReferencesMany; +exports.EmbedsOne = EmbedsOne; exports.EmbedsMany = EmbedsMany; var RelationClasses = { @@ -39,6 +41,7 @@ var RelationClasses = { hasOne: HasOne, hasAndBelongsToMany: HasAndBelongsToMany, referencesMany: ReferencesMany, + embedsOne: EmbedsOne, embedsMany: EmbedsMany }; @@ -384,6 +387,24 @@ function HasOne(definition, modelInstance) { util.inherits(HasOne, Relation); +/** + * EmbedsOne subclass + * @param {RelationDefinition|Object} definition + * @param {Object} modelInstance + * @returns {EmbedsMany} + * @constructor + * @class EmbedsOne + */ +function EmbedsOne(definition, modelInstance) { + if (!(this instanceof EmbedsOne)) { + return new EmbedsMany(definition, modelInstance); + } + assert(definition.type === RelationTypes.embedsOne); + Relation.apply(this, arguments); +} + +util.inherits(EmbedsOne, Relation); + /** * EmbedsMany subclass * @param {RelationDefinition|Object} definition @@ -1538,6 +1559,170 @@ HasOne.prototype.related = function (refresh, params) { } }; +RelationDefinition.embedsOne = function (modelFrom, modelTo, params) { + params = params || {}; + modelTo = lookupModelTo(modelFrom, modelTo, params); + + var thisClassName = modelFrom.modelName; + var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'Item'); + var propertyName = params.property || i8n.camelize(modelTo.modelName, true); + var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id'; + + if (relationName === propertyName) { + propertyName = '_' + propertyName; + debug('EmbedsOne property cannot be equal to relation name: ' + + 'forcing property %s for relation %s', propertyName, relationName); + } + + var definition = modelFrom.relations[relationName] = new RelationDefinition({ + name: relationName, + type: RelationTypes.embedsOne, + modelFrom: modelFrom, + keyFrom: propertyName, + keyTo: idName, + modelTo: modelTo, + multiple: false, + properties: params.properties, + scope: params.scope, + options: params.options, + embed: true + }); + + var opts = { type: modelTo }; + + if (params.default === true) { + opts.default = function() { return new modelTo(); }; + } else if (typeof params.default === 'object') { + opts.default = (function(def) { + return function() { return new modelTo(def); }; + }(params.default)); + } + + modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts); + + // validate the embedded instance + if (definition.options.validate) { + modelFrom.validate(relationName, function(err) { + var inst = this[propertyName]; + if (inst instanceof modelTo) { + if (!inst.isValid()) { + var first = Object.keys(inst.errors)[0]; + var msg = 'is invalid: `' + first + '` ' + inst.errors[first]; + this.errors.add(relationName, msg, 'invalid'); + err(false); + } + } + }); + } + + // Define a property for the scope so that we have 'this' for the scoped methods + Object.defineProperty(modelFrom.prototype, relationName, { + enumerable: true, + configurable: true, + get: function() { + var relation = new EmbedsOne(definition, this); + var relationMethod = relation.related.bind(relation) + relationMethod.create = relation.create.bind(relation); + relationMethod.build = relation.build.bind(relation); + relationMethod.destroy = relation.destroy.bind(relation); + relationMethod._targetClass = definition.modelTo.modelName; + return relationMethod; + } + }); + + // FIXME: [rfeng] Wrap the property into a function for remoting + // so that it can be accessed as /api/// + // For example, /api/orders/1/customer + var fn = function() { + var f = this[relationName]; + f.apply(this, arguments); + }; + modelFrom.prototype['__get__' + relationName] = fn; + + return definition; +}; + +EmbedsOne.prototype.related = function (refresh, params) { + var modelTo = this.definition.modelTo; + var modelInstance = this.modelInstance; + var propertyName = this.definition.keyFrom; + + if (arguments.length === 1) { + params = refresh; + refresh = false; + } else if (arguments.length > 2) { + throw new Error('Method can\'t be called with more than two arguments'); + } + + if (params instanceof ModelBaseClass) { // acts as setter + if (params instanceof modelTo) { + this.definition.applyProperties(modelInstance, params); + modelInstance.setAttribute(propertyName, params); + } + return; + } + + var embeddedInstance = modelInstance[propertyName]; + if (typeof params === 'function') { // acts as async getter + var cb = params; + process.nextTick(function() { + cb(null, embeddedInstance); + }); + } else if (params === undefined) { // acts as sync getter + return embeddedInstance; + } +}; + +EmbedsOne.prototype.create = function (targetModelData, cb) { + var modelTo = this.definition.modelTo; + var propertyName = this.definition.keyFrom; + var modelInstance = this.modelInstance; + + if (typeof targetModelData === 'function' && !cb) { + cb = targetModelData; + targetModelData = {}; + } + + targetModelData = targetModelData || {}; + + var inst = this.build(targetModelData); + + var err = inst.isValid() ? null : new ValidationError(inst); + + if (err) { + return process.nextTick(function() { + cb(err); + }); + } + + modelInstance.updateAttribute(propertyName, + inst, function(err) { + cb(err, err ? null : inst); + }); +}; + +EmbedsOne.prototype.build = function (targetModelData) { + var modelTo = this.definition.modelTo; + var modelInstance = this.modelInstance; + + targetModelData = targetModelData || {}; + + this.definition.applyProperties(modelInstance, targetModelData); + + return new modelTo(targetModelData); +}; + +EmbedsOne.prototype.destroy = function (cb) { + var modelInstance = this.modelInstance; + var propertyName = this.definition.keyFrom; + modelInstance.unsetAttribute(propertyName, true); + if (typeof cb === 'function') { + modelInstance.save(function(err) { + cb(err); + }); + } +}; + RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) { params = params || {}; modelTo = lookupModelTo(modelFrom, modelTo, params, true); @@ -1601,7 +1786,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) var id = item[idName] || '(blank)'; var first = Object.keys(item.errors)[0]; var msg = 'contains invalid item: `' + id + '`'; - msg += ' (' + first + ' ' + item.errors[first] + ')'; + msg += ' (`' + first + '` ' + item.errors[first] + ')'; self.errors.add(propertyName, msg, 'invalid'); } } else { @@ -1715,15 +1900,11 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb var modelInstance = this.modelInstance; var actualCond = {}; - var actualRefresh = false; if (arguments.length === 3) { cb = condOrRefresh; } else if (arguments.length === 4) { - if (typeof condOrRefresh === 'boolean') { - actualRefresh = condOrRefresh; - } else { + if (typeof condOrRefresh === 'object') { actualCond = condOrRefresh; - actualRefresh = true; } } else { throw new Error('Method can be only called with one or two arguments'); @@ -1922,7 +2103,7 @@ EmbedsMany.prototype.build = function(targetModelData) { } } - this.definition.applyProperties(this.modelInstance, targetModelData); + this.definition.applyProperties(modelInstance, targetModelData); var inst = new modelTo(targetModelData); diff --git a/lib/relations.js b/lib/relations.js index ba9a36d7..790433fc 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -158,14 +158,18 @@ RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params return RelationDefinition.hasAndBelongsToMany(this, modelTo, params); }; -RelationMixin.hasOne = function hasMany(modelTo, params) { +RelationMixin.hasOne = function hasOne(modelTo, params) { return RelationDefinition.hasOne(this, modelTo, params); }; -RelationMixin.referencesMany = function hasMany(modelTo, params) { +RelationMixin.referencesMany = function referencesMany(modelTo, params) { return RelationDefinition.referencesMany(this, modelTo, params); }; -RelationMixin.embedsMany = function hasMany(modelTo, params) { +RelationMixin.embedsOne = function embedsOne(modelTo, params) { + return RelationDefinition.embedsOne(this, modelTo, params); +}; + +RelationMixin.embedsMany = function embedsMany(modelTo, params) { return RelationDefinition.embedsMany(this, modelTo, params); }; diff --git a/test/relations.test.js b/test/relations.test.js index d227eb1d..37b59c7a 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -1322,6 +1322,131 @@ describe('relations', function () { }); }); + describe('embedsOne', function () { + + var person; + var Other; + + before(function () { + db = getSchema(); + Person = db.define('Person', {name: String}); + Passport = db.define('Passport', + {name:{type:'string', required: true}}, + {idInjection: false} + ); + Other = db.define('Other', {name: String}); + }); + + it('can be declared using embedsOne method', function () { + Person.embedsOne(Passport, { + default: {name: 'Anonymous'}, // a bit contrived + options: {validate: true} + }); + }); + + it('should have setup a property and accessor', function() { + var p = new Person(); + p.passport.should.be.an.object; // because of default + p.passportItem.should.be.a.function; + p.passportItem.create.should.be.a.function; + p.passportItem.build.should.be.a.function; + p.passportItem.destroy.should.be.a.function; + }); + + it('should return an instance with default values', function() { + var p = new Person(); + p.passport.toObject().should.eql({name: 'Anonymous'}); + p.passportItem().should.equal(p.passport); + p.passportItem(function(err, passport) { + should.not.exist(err); + passport.should.equal(p.passport); + }); + }); + + it('should embed a model instance', function() { + var p = new Person(); + p.passportItem(new Passport({name: 'Fred'})); + p.passport.toObject().should.eql({name: 'Fred'}); + p.passport.should.be.an.instanceOf(Passport); + }); + + it('should not embed an invalid model type', function() { + var p = new Person(); + p.passportItem(new Other()); + p.passport.toObject().should.eql({name: 'Anonymous'}); + p.passport.should.be.an.instanceOf(Passport); + }); + + 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.toObject().should.eql({name: 'Fredric'}); + p.passport.should.be.an.instanceOf(Passport); + done(); + }); + }); + }); + + it('should get an embedded item on scope', function(done) { + Person.findOne(function(err, p) { + should.not.exist(err); + var passport = p.passportItem(); + passport.toObject().should.eql({name: 'Fredric'}); + passport.should.be.an.instanceOf(Passport); + passport.should.equal(p.passport); + done(); + }); + }); + + it('should validate an embedded item on scope - on creation', function(done) { + var p = new Person({name: 'Fred'}); + p.passportItem.create({}, function(err, passport) { + should.exist(err); + err.name.should.equal('ValidationError'); + var msg = 'The `Passport` instance is not valid.'; + msg += ' Details: `name` can\'t be blank.'; + err.message.should.equal(msg); + done(); + }); + }); + + it('should validate an embedded item on scope - on update', function(done) { + Person.findOne(function(err, p) { + var passport = p.passportItem(); + passport.name = null; + p.save(function(err) { + should.exist(err); + err.name.should.equal('ValidationError'); + var msg = 'The `Person` instance is not valid.'; + msg += ' Details: `passportItem` is invalid: `name` can\'t be blank.'; + err.message.should.equal(msg); + done(); + }); + }); + }); + + it('should destroy an embedded item on scope', function(done) { + Person.findOne(function(err, p) { + p.passportItem.destroy(function(err) { + should.not.exist(err); + should.equal(p.passport, null); + done(); + }); + }); + }); + + it('should get an embedded item on scope - verify', function(done) { + Person.findOne(function(err, p) { + should.not.exist(err); + should.equal(p.passport, null); + done(); + }); + }); + + }); + describe('embedsMany', function () { var address1, address2; @@ -1593,7 +1718,7 @@ describe('relations', function () { 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).'; + expected += 'Details: `addresses` contains invalid item: `work` (`street` can\'t be blank).'; err.message.should.equal(expected); done(); }); From 20e5d3c01b8dc925ae66d0c37eae18256abf742f Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Tue, 19 Aug 2014 22:15:30 +0200 Subject: [PATCH 5/6] Validate embedded models by default --- lib/relation-definition.js | 4 ++-- test/relations.test.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index f0621046..670a5dfd 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -1601,7 +1601,7 @@ RelationDefinition.embedsOne = function (modelFrom, modelTo, params) { modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts); // validate the embedded instance - if (definition.options.validate) { + if (definition.options.validate !== false) { modelFrom.validate(relationName, function(err) { var inst = this[propertyName]; if (inst instanceof modelTo) { @@ -1774,7 +1774,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) } // validate all embedded items - if (definition.options.validate) { + if (definition.options.validate !== false) { modelFrom.validate(propertyName, function(err) { var self = this; var embeddedList = this[propertyName] || []; diff --git a/test/relations.test.js b/test/relations.test.js index 37b59c7a..e0788a7e 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -1339,8 +1339,7 @@ describe('relations', function () { it('can be declared using embedsOne method', function () { Person.embedsOne(Passport, { - default: {name: 'Anonymous'}, // a bit contrived - options: {validate: true} + default: {name: 'Anonymous'} // a bit contrived }); }); @@ -1642,7 +1641,7 @@ describe('relations', function () { }); it('can be declared', function (done) { - Person.embedsMany(Address, { options: { autoId: false, validate: true } }); + Person.embedsMany(Address, { options: { autoId: false } }); db.automigrate(done); }); From 5088c4dd740203e2fd7d1d1010c9e8cd6143ec10 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 19 Aug 2014 15:23:37 -0700 Subject: [PATCH 6/6] Fix test cases --- test/relations.test.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/relations.test.js b/test/relations.test.js index e0788a7e..b20f3f35 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -1375,10 +1375,12 @@ describe('relations', function () { p.passport.toObject().should.eql({name: 'Anonymous'}); p.passport.should.be.an.instanceOf(Passport); }); - + + var personId; it('should create an embedded item on scope', function(done) { Person.create({name: 'Fred'}, function(err, p) { should.not.exist(err); + personId = p.id; p.passportItem.create({name: 'Fredric'}, function(err, passport) { should.not.exist(err); p.passport.toObject().should.eql({name: 'Fredric'}); @@ -1389,7 +1391,7 @@ describe('relations', function () { }); it('should get an embedded item on scope', function(done) { - Person.findOne(function(err, p) { + Person.findById(personId, function(err, p) { should.not.exist(err); var passport = p.passportItem(); passport.toObject().should.eql({name: 'Fredric'}); @@ -1412,7 +1414,7 @@ describe('relations', function () { }); it('should validate an embedded item on scope - on update', function(done) { - Person.findOne(function(err, p) { + Person.findById(personId, function(err, p) { var passport = p.passportItem(); passport.name = null; p.save(function(err) { @@ -1427,7 +1429,7 @@ describe('relations', function () { }); it('should destroy an embedded item on scope', function(done) { - Person.findOne(function(err, p) { + Person.findById(personId, function(err, p) { p.passportItem.destroy(function(err) { should.not.exist(err); should.equal(p.passport, null); @@ -1437,7 +1439,7 @@ describe('relations', function () { }); it('should get an embedded item on scope - verify', function(done) { - Person.findOne(function(err, p) { + Person.findById(personId, function(err, p) { should.not.exist(err); should.equal(p.passport, null); done();