From af8cf19242eb84df75fbfa479c7fc113efbe0b5e Mon Sep 17 00:00:00 2001 From: Dimitris Halatsis Date: Tue, 31 May 2016 10:44:17 +0300 Subject: [PATCH] Persist changes on parent for embedsOne Allow direct save of changes on embedded model to be persisted on parent document. Person.embedsOne(Address); Person.findById(someId) .then(function(p){ var address = p.addressItem(); address.street = 'new street' // This will now persist changes on parent document return address.save(); }) --- lib/relation-definition.js | 55 +++++++++++++++++++++++++++++++++++--- test/relations.test.js | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index ee31c1d5..7e33b814 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -2068,7 +2068,7 @@ EmbedsOne.prototype.related = function(condOrRefresh, options, cb) { return; } - var embeddedInstance = modelInstance[propertyName]; + var embeddedInstance = this.embeddedValue(); if (embeddedInstance) { embeddedInstance.__persisted = true; @@ -2083,9 +2083,56 @@ EmbedsOne.prototype.related = function(condOrRefresh, options, cb) { } }; +EmbedsOne.prototype.prepareEmbeddedInstance = function(inst) { + if (inst && inst.triggerParent !== 'function') { + 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') { + var embeddedValue = self.embeddedValue(); + modelInstance.updateAttribute(propertyName, + embeddedValue, function(err, modelInst) { + callback(err, err ? null : modelInst); + }); + } else if (actionName === 'destroy') { + modelInstance.unsetAttribute(propertyName, true); + // cannot delete property completely the way save works. operator $unset needed like mongo + modelInstance.save(function(err, modelInst) { + callback(err, modelInst); + }); + } else { + process.nextTick(callback); + } + }; + var originalTrigger = inst.trigger; + inst.trigger = function(actionName, work, data, callback) { + if (typeof work === 'function') { + var originalWork = work; + work = function(next) { + originalWork.call(this, function(done) { + inst.triggerParent(actionName, function(err, inst) { + next(done); // TODO [fabien] - error handling? + }); + }); + }; + } + originalTrigger.call(this, actionName, work, data, callback); + }; + } +}; + EmbedsOne.prototype.embeddedValue = function(modelInstance) { modelInstance = modelInstance || this.modelInstance; - return modelInstance[this.definition.keyFrom]; + var embeddedValue = modelInstance[this.definition.keyFrom]; + this.prepareEmbeddedInstance(embeddedValue); + return embeddedValue; }; EmbedsOne.prototype.create = function(targetModelData, options, cb) { @@ -2186,6 +2233,8 @@ EmbedsOne.prototype.build = function(targetModelData) { var embeddedInstance = new modelTo(targetModelData); modelInstance[propertyName] = embeddedInstance; + this.prepareEmbeddedInstance(embeddedInstance); + return embeddedInstance; }; @@ -2203,7 +2252,7 @@ EmbedsOne.prototype.update = function(targetModelData, options, cb) { var isInst = targetModelData instanceof ModelBaseClass; var data = isInst ? targetModelData.toObject() : targetModelData; - var embeddedInstance = modelInstance[propertyName]; + var embeddedInstance = this.embeddedValue(); if (embeddedInstance instanceof modelTo) { cb = cb || utils.createPromiseCallback(); var hookState = {}; diff --git a/test/relations.test.js b/test/relations.test.js index 63c4d4c5..bf52a212 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -3775,6 +3775,53 @@ describe('relations', function() { done(); }).catch(done); }); + + it('should also save changes when directly saving the embedded model', function(done) { + // Passport should normally have an id for the direct save to work. For now override the check + var originalHasPK = Passport.definition.hasPK; + Passport.definition.hasPK = function() { return true; }; + Person.findById(personId) + .then(function(p) { + return p.passportItem.create({ name: 'Mitsos' }); + }) + .then(function(passport) { + passport.name = 'Jim'; + return passport.save(); + }) + .then(function() { + return Person.findById(personId); + }) + .then(function(person) { + person.passportItem().toObject().should.eql({ name: 'Jim' }); + // restore original hasPk + Passport.definition.hasPK = originalHasPK; + done(); + }) + .catch(function(err) { + Passport.definition.hasPK = originalHasPK; + done(err); + }); + }); + + it('should delete the embedded document and also update parent', function(done) { + var originalHasPK = Passport.definition.hasPK; + Passport.definition.hasPK = function() { return true; }; + Person.findById(personId) + .then(function(p) { + return p.passportItem().destroy(); + }) + .then(function() { + return Person.findById(personId); + }) + .then(function(person) { + person.should.have.property('passport', null); + done(); + }) + .catch(function(err) { + Passport.definition.hasPK = originalHasPK; + done(err); + }); + }); }); describe('embedsOne - persisted model', function() {