diff --git a/lib/dao.js b/lib/dao.js index 1d813e4e1..ca15174ba 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1065,8 +1065,15 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) var obj, Model = self.lookupModel(data); if (data) { - obj = new Model(data, {fields: query.fields, applySetters: false, - persisted: true}); + var ctorOpts = { + fields: query.fields, + applySetters: false, + persisted: true, + }; + if (Model.settings.applyDefaultsOnReads === false) { + ctorOpts.applyDefaultValues = false; + } + obj = new Model(data, ctorOpts); } if (created) { @@ -1936,7 +1943,15 @@ DataAccessObject.find = function find(query, options, cb) { if (!err && Array.isArray(data)) { async.map(data, function(item, next) { var Model = self.lookupModel(item); - var obj = new Model(item, {fields: query.fields, applySetters: false, persisted: true}); + var ctorOpts = { + fields: query.fields, + applySetters: false, + persisted: true, + }; + if (Model.settings.applyDefaultsOnReads === false) { + ctorOpts.applyDefaultValues = false; + } + var obj = new Model(item, ctorOpts); if (query && query.include) { if (query.collect) { diff --git a/test/basic-querying.test.js b/test/basic-querying.test.js index 9bf0cf3c3..5eb7717c1 100644 --- a/test/basic-querying.test.js +++ b/test/basic-querying.test.js @@ -579,6 +579,75 @@ describe('basic-querying', function() { sample(['id']).expect(['id']); sample(['email']).expect(['email']); }); + + it('applies default values by default', function() { + // Backwards compatibility, see + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + var Player = db.define('Player', {name: String}); + var created; + return db.automigrate('Player') + .then(function() { return Player.create({name: 'Pen'}); }) + .then(function(result) { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(function() { + // And query existing data + return Player.findOne(); + }) + .then(function(found) { + should(found.toObject().active).be.oneOf([ + // For databases supporting `undefined` value, + // we convert `undefined` to property default. + false, + // For databases representing `undefined` as `null` (e.g. SQL), + // we treat `null` as a defined value and don't apply defaults. + null, + ]); + }); + }); + + it('preserves empty values from the database when "applyDefaultsOnReads" is false', function() { + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + var Player = db.define( + 'Player', + {name: String}, + {applyDefaultsOnReads: false} + ); + var created; + return db.automigrate('Player') + .then(function() { return Player.create({name: 'Pen'}); }) + .then(function(result) { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(function() { + // And query existing data + return Player.findOne(); + }) + .then(function(found) { + should(found.toObject().active).be.oneOf([ + undefined, // databases supporting `undefined` value + null, // databases representing `undefined` as `null` + ]); + }); + }); }); describe('count', function() { diff --git a/test/manipulation.test.js b/test/manipulation.test.js index bf28401eb..6b1e632a3 100644 --- a/test/manipulation.test.js +++ b/test/manipulation.test.js @@ -1240,6 +1240,82 @@ describe('manipulation', function() { }) .catch(done); }); + + it('applies default values on returned data', function() { + // Backwards compatibility, see + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + var Player = db.define('Player', {name: String}); + var created; + return db.automigrate('Player') + .then(function() { return Player.create({name: 'Pen'}); }) + .then(function(result) { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(function() { + // and findOrCreate an existing record + return Player.findOrCreate({id: created.id}, {name: 'updated'}); + }) + .then(function(result) { + var found = result[0]; + + // Backwards-compatibility + // When Pen does not have "active" flag set, we change it to default + should(found.toObject().active).be.oneOf([ + // For databases supporting `undefined` value, + // we convert `undefined` to property default. + false, + // For databases representing `undefined` as `null` (e.g. SQL), + // we treat `null` as a defined value and don't apply defaults. + null, + ]); + }); + }); + + it('preserves empty values from the database when "applyDefaultsOnReads" is false', function() { + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + var Player = db.define( + 'Player', + {name: String}, + {applyDefaultsOnReads: false} + ); + var created; + + return db.automigrate('Player') + .then(function() { return Player.create({name: 'Pen'}); }) + .then(function(result) { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(function() { + // And findOrCreate an existing record + return Player.findOrCreate({id: created.id}, {name: 'updated'}); + }) + .then(function(result) { + var found = result[0]; + + should(found.toObject().active).be.oneOf([ + undefined, // databases supporting `undefined` value + null, // databases representing `undefined` as `null` + ]); + }); + }); }); describe('destroy', function() {