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');