From 96a276a12b4a75a85b1092ccbd4ad0b31c5fd056 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Fri, 15 Aug 2014 15:12:02 +0200 Subject: [PATCH] Refactor embedsMany - auto-save parent With this change, saving an embedded model now correctly updates the parent model. Before, a separate `save()` call on the parent was required, contrary to other relation types. --- lib/relation-definition.js | 67 ++++++++++++++++++++++++++++++-------- test/relations.test.js | 34 ++++++++++++++----- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index f824a8a5..e11b28bc 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -1652,11 +1652,53 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) return definition; }; +EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) { + if (inst && inst.triggerParent !== 'function') { + var self = this; + var propertyName = this.definition.keyFrom; + var modelInstance = this.modelInstance; + inst.triggerParent = function(actionName, callback) { + if (actionName === 'save' || actionName === 'destroy') { + var embeddedList = self.embeddedList(); + if (actionName === 'destroy') { + var index = embeddedList.indexOf(inst); + if (index > -1) embeddedList.splice(index, 1); + } + modelInstance.updateAttribute(propertyName, + embeddedList, function(err, modelInst) { + callback(err, err ? null : 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); + }; + } +}; + +EmbedsMany.prototype.embeddedList = function(modelInstance) { + modelInstance = modelInstance || this.modelInstance; + var embeddedList = modelInstance[this.definition.keyFrom] || []; + embeddedList.forEach(this.prepareEmbeddedInstance.bind(this)); + return embeddedList; +}; + EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { var modelTo = this.definition.modelTo; - var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var self = receiver; var actualCond = {}; var actualRefresh = false; @@ -1673,9 +1715,9 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb throw new Error('Method can be only called with one or two arguments'); } - var embeddedList = self[propertyName] || []; + var embeddedList = this.embeddedList(receiver); - this.definition.applyScope(modelInstance, actualCond); + this.definition.applyScope(receiver, actualCond); var params = mergeQuery(actualCond, scopeParams); @@ -1697,10 +1739,9 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb EmbedsMany.prototype.findById = function (fkId, cb) { var pk = this.definition.keyTo; var modelTo = this.definition.modelTo; - var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); var find = function(id) { for (var i = 0; i < embeddedList.length; i++) { @@ -1740,7 +1781,7 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) { var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); var inst = this.findById(fkId); @@ -1774,7 +1815,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) { var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId); @@ -1799,10 +1840,9 @@ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById; EmbedsMany.prototype.at = function (index, cb) { var modelTo = this.definition.modelTo; - var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); var item = embeddedList[parseInt(index)]; item = (item instanceof modelTo) ? item : null; @@ -1829,7 +1869,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) { } targetModelData = targetModelData || {}; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); var inst = this.build(targetModelData); @@ -1850,11 +1890,10 @@ EmbedsMany.prototype.create = function (targetModelData, cb) { EmbedsMany.prototype.build = function(targetModelData) { var pk = this.definition.keyTo; var modelTo = this.definition.modelTo; - var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; var autoId = this.definition.options.autoId !== false; - var embeddedList = modelInstance[propertyName] || []; + var embeddedList = this.embeddedList(); targetModelData = targetModelData || {}; @@ -1879,6 +1918,8 @@ EmbedsMany.prototype.build = function(targetModelData) { embeddedList.push(inst); } + this.prepareEmbeddedInstance(inst); + return inst; }; diff --git a/test/relations.test.js b/test/relations.test.js index 7db9957f..55a8e4cf 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -1758,14 +1758,12 @@ describe('relations', function () { category = cat; var link = cat.items.build({ notes: 'Some notes...' }); link.product.create({ name: 'Product 1' }, function(err, p) { - cat.save(function(err, cat) { // save parent object! - cat.links[0].id.should.eql(p.id); - cat.links[0].name.should.equal('Product 1'); // denormalized - cat.links[0].notes.should.equal('Some notes...'); - cat.items.at(0).should.equal(cat.links[0]); - done(); - }); - }) + cat.links[0].id.should.eql(p.id); + cat.links[0].name.should.equal('Product 1'); // denormalized + cat.links[0].notes.should.equal('Some notes...'); + cat.items.at(0).should.equal(cat.links[0]); + done(); + }); }); }); @@ -1787,6 +1785,26 @@ describe('relations', function () { }); }); + it('should update items on scope - and save parent', function(done) { + Category.findById(category.id, function(err, cat) { + var link = cat.items.at(0); + link.updateAttributes({notes: 'Updated notes...'}, function(err, link) { + link.notes.should.equal('Updated notes...'); + done(); + }); + }); + }); + + it('should find items on scope - verify update', function(done) { + Category.findById(category.id, function(err, cat) { + cat.name.should.equal('Category B'); + cat.links.toObject().should.eql([ + {id: 5, name: 'Product 1', notes: 'Updated notes...'} + ]); + done(); + }); + }); + }); describe('embedsMany - polymorphic relations', function () {