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 8193f402bb
commit 96a276a12b
2 changed files with 80 additions and 21 deletions

View File

@ -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;
};

View File

@ -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 () {