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.
This commit is contained in:
Fabien Franzen 2014-08-15 15:12:02 +02:00
parent c3c2c85ce4
commit 21801058c9
2 changed files with 75 additions and 21 deletions

View File

@ -1607,11 +1607,48 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
scopeDefinition.related = scopeMethods.related; scopeDefinition.related = scopeMethods.related;
}; };
EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
if (inst && inst.triggerParent !== 'function') {
var relationName = this.definition.name;
var modelInstance = this.modelInstance;
inst.triggerParent = function(actionName, callback) {
if (actionName === 'save' || actionName === 'destroy') {
var embeddedList = modelInstance[relationName] || [];
modelInstance.updateAttribute(relationName,
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.name] || [];
embeddedList.forEach(this.prepareEmbeddedInstance.bind(this));
return embeddedList;
};
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var self = receiver;
var actualCond = {}; var actualCond = {};
var actualRefresh = false; var actualRefresh = false;
@ -1628,9 +1665,9 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
throw new Error('Method can be only called with one or two arguments'); throw new Error('Method can be only called with one or two arguments');
} }
var embeddedList = self[relationName] || []; var embeddedList = this.embeddedList(receiver);
this.definition.applyScope(modelInstance, actualCond); this.definition.applyScope(receiver, actualCond);
var params = mergeQuery(actualCond, scopeParams); var params = mergeQuery(actualCond, scopeParams);
@ -1652,10 +1689,9 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
EmbedsMany.prototype.findById = function (fkId, cb) { EmbedsMany.prototype.findById = function (fkId, cb) {
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
var find = function(id) { var find = function(id) {
for (var i = 0; i < embeddedList.length; i++) { for (var i = 0; i < embeddedList.length; i++) {
@ -1695,7 +1731,7 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
var relationName = this.definition.name; var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
var inst = this.findById(fkId); var inst = this.findById(fkId);
@ -1729,7 +1765,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) {
var relationName = this.definition.name; var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId); var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId);
@ -1754,10 +1790,9 @@ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById;
EmbedsMany.prototype.at = function (index, cb) { EmbedsMany.prototype.at = function (index, cb) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
var item = embeddedList[parseInt(index)]; var item = embeddedList[parseInt(index)];
item = (item instanceof modelTo) ? item : null; item = (item instanceof modelTo) ? item : null;
@ -1784,7 +1819,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
} }
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
var inst = this.build(targetModelData); var inst = this.build(targetModelData);
@ -1805,11 +1840,10 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
EmbedsMany.prototype.build = function(targetModelData) { EmbedsMany.prototype.build = function(targetModelData) {
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var autoId = this.definition.options.autoId !== false; var autoId = this.definition.options.autoId !== false;
var embeddedList = modelInstance[relationName] || []; var embeddedList = this.embeddedList();
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
@ -1834,6 +1868,8 @@ EmbedsMany.prototype.build = function(targetModelData) {
embeddedList.push(inst); embeddedList.push(inst);
} }
this.prepareEmbeddedInstance(inst);
return inst; return inst;
}; };

View File

@ -1758,14 +1758,12 @@ describe('relations', function () {
category = cat; category = cat;
var link = cat.items.build({ notes: 'Some notes...' }); var link = cat.items.build({ notes: 'Some notes...' });
link.product.create({ name: 'Product 1' }, function(err, p) { 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].id.should.eql(p.id); cat.links[0].name.should.equal('Product 1'); // denormalized
cat.links[0].name.should.equal('Product 1'); // denormalized cat.links[0].notes.should.equal('Some notes...');
cat.links[0].notes.should.equal('Some notes...'); cat.items.at(0).should.equal(cat.links[0]);
cat.items.at(0).should.equal(cat.links[0]); done();
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 () { describe('embedsMany - polymorphic relations', function () {