diff --git a/lib/dao.js b/lib/dao.js index cf7dc7ea9..75bb0b094 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -1109,8 +1109,15 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) let obj; if (data) { - obj = new Model(data, {fields: query.fields, applySetters: false, - persisted: true}); + const ctorOpts = { + fields: query.fields, + applySetters: false, + persisted: true, + }; + if (Model.settings.applyDefaultsOnReads === false) { + ctorOpts.applyDefaultValues = false; + } + obj = new Model(data, ctorOpts); } if (created) { @@ -1632,6 +1639,9 @@ DataAccessObject.find = function find(query, options, cb) { applySetters: false, persisted: true, }; + if (Model.settings.applyDefaultsOnReads === false) { + ctorOpts.applyDefaultValues = false; + } let obj; try { obj = new Model(data, ctorOpts); diff --git a/test/basic-querying.test.js b/test/basic-querying.test.js index 57fd3b4e9..cef6f540c 100644 --- a/test/basic-querying.test.js +++ b/test/basic-querying.test.js @@ -917,6 +917,75 @@ describe('basic-querying', function() { }); }); }); + + it('applies default values by default', () => { + // Backwards compatibility, see + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + const Player = db.define('Player', {name: String}); + let created; + return db.automigrate('Player') + .then(() => Player.create({name: 'Pen'})) + .then(result => { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(() => { + // And query existing data + return Player.findOne(); + }) + .then(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', () => { + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + const Player = db.define( + 'Player', + {name: String}, + {applyDefaultsOnReads: false} + ); + let created; + return db.automigrate('Player') + .then(() => Player.create({name: 'Pen'})) + .then(result => { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(() => { + // And query existing data + return Player.findOne(); + }) + .then(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 9c939c0d6..7a42726d5 100644 --- a/test/manipulation.test.js +++ b/test/manipulation.test.js @@ -1619,6 +1619,78 @@ describe('manipulation', function() { }) .catch(done); }); + + it('applies default values on returned data', () => { + // Backwards compatibility, see + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + const Player = db.define('Player', {name: String}); + let created; + return db.automigrate('Player') + .then(() => Player.create({name: 'Pen'})) + .then(result => { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(() => { + // and findOrCreate an existing record + return Player.findOrCreate({id: created.id}, {name: 'updated'}); + }) + .spread(found => { + // 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', () => { + // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 + + // Initially, all Players were always active, no property was needed + const Player = db.define( + 'Player', + {name: String}, + {applyDefaultsOnReads: false} + ); + let created; + + return db.automigrate('Player') + .then(() => Player.create({name: 'Pen'})) + .then(result => { + created = result; + + // Later on, we decide to introduce `active` property + Player.defineProperty('active', { + type: Boolean, + default: false, + }); + return db.autoupdate('Player'); + }) + .then(() => { + // And findOrCreate an existing record + return Player.findOrCreate({id: created.id}, {name: 'updated'}); + }) + .spread(found => { + should(found.toObject().active).be.oneOf([ + undefined, // databases supporting `undefined` value + null, // databases representing `undefined` as `null` + ]); + }); + }); }); describe('destroy', function() {