Introduce embedsMany persistent: true option

When set, instead of only embedding the model (on creation) it will be
persisted first, and subsequently embedded.
This commit is contained in:
Fabien Franzen 2014-09-07 12:59:47 +02:00
parent ef816d490a
commit 95764232b9
2 changed files with 53 additions and 23 deletions

View File

@ -2199,24 +2199,34 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
var inst = this.build(targetModelData); var inst = this.build(targetModelData);
var err = inst.isValid() ? null : new ValidationError(inst); var updateEmbeddedList = function() {
modelInstance.updateAttribute(propertyName,
if (err) { embeddedList, function(err, modelInst) {
return process.nextTick(function() { cb(err, err ? null : inst);
cb(err);
}); });
} }
modelInstance.updateAttribute(propertyName, if (this.definition.options.persistent) {
embeddedList, function(err, modelInst) { inst.save(function(err) { // will validate
cb(err, err ? null : inst); if (err) return cb(err, inst);
}); updateEmbeddedList();
});
} else {
var err = inst.isValid() ? null : new ValidationError(inst);
if (err) {
return process.nextTick(function() {
cb(err);
});
}
updateEmbeddedList();
}
}; };
EmbedsMany.prototype.build = function(targetModelData) { EmbedsMany.prototype.build = function(targetModelData) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var forceId = this.definition.options.forceId; var forceId = this.definition.options.forceId;
var persistent = this.definition.options.persistent;
var connector = modelTo.dataSource.connector; var connector = modelTo.dataSource.connector;
var pk = this.definition.keyTo; var pk = this.definition.keyTo;
@ -2228,6 +2238,7 @@ EmbedsMany.prototype.build = function(targetModelData) {
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
var assignId = (forceId || targetModelData[pk] === undefined); var assignId = (forceId || targetModelData[pk] === undefined);
assignId = assignId && !persistent;
if (assignId && pkType === Number) { if (assignId && pkType === Number) {
var ids = embeddedList.map(function(m) { var ids = embeddedList.map(function(m) {

View File

@ -13,6 +13,10 @@ var getTransientDataSource = function(settings) {
return new DataSource('transient', settings, db.modelBuilder); return new DataSource('transient', settings, db.modelBuilder);
}; };
var getMemoryDataSource = function(settings) {
return new DataSource('memory', settings, db.modelBuilder);
};
describe('relations', function () { describe('relations', function () {
describe('hasMany', function () { describe('hasMany', function () {
@ -2188,8 +2192,12 @@ describe('relations', function () {
var address0, address1, address2; var address0, address1, address2;
var person; var person;
// This test spefically uses the Memory connector
// in order to test the use of the auto-generated
// id, in the sequence of the related model.
before(function (done) { before(function (done) {
db = getSchema(); db = getMemoryDataSource();
Person = db.define('Person', {name: String}); Person = db.define('Person', {name: String});
Address = db.define('Address', {street: String}); Address = db.define('Address', {street: String});
Address.validatesPresenceOf('street'); Address.validatesPresenceOf('street');
@ -2200,12 +2208,18 @@ describe('relations', function () {
}); });
it('can be declared', function (done) { it('can be declared', function (done) {
Person.embedsMany(Address, {scope: {order: 'street'}}); // to save related model itself, set
// persistent: true
Person.embedsMany(Address, {
scope: {order: 'street'},
options: {persistent: true}
});
db.automigrate(done); db.automigrate(done);
}); });
it('should create individual items (0)', function(done) { it('should create individual items (0)', function(done) {
Address.create({ street: 'Street 0' }, function(err, inst) { Address.create({ street: 'Street 0' }, function(err, inst) {
inst.id.should.equal(1); // offset sequence
address0 = inst; address0 = inst;
done(); done();
}); });
@ -2213,6 +2227,7 @@ describe('relations', function () {
it('should create individual items (1)', function(done) { it('should create individual items (1)', function(done) {
Address.create({ street: 'Street 1' }, function(err, inst) { Address.create({ street: 'Street 1' }, function(err, inst) {
inst.id.should.equal(2);
address1 = inst; address1 = inst;
done(); done();
}); });
@ -2220,21 +2235,29 @@ describe('relations', function () {
it('should create individual items (2)', function(done) { it('should create individual items (2)', function(done) {
Address.create({ street: 'Street 2' }, function(err, inst) { Address.create({ street: 'Street 2' }, function(err, inst) {
inst.id.should.equal(3);
address2 = inst; address2 = inst;
done(); done();
}); });
}); });
it('should create individual items (3)', function(done) {
Address.create({ street: 'Street 3' }, function(err, inst) {
inst.id.should.equal(4); // offset sequence
done();
});
});
it('should add embedded items on scope', function(done) { it('should add embedded items on scope', function(done) {
Person.create({ name: 'Fred' }, function(err, p) { Person.create({ name: 'Fred' }, function(err, p) {
person = p; person = p;
p.addressList.create(address1.toObject(), function(err, address) { p.addressList.create(address1.toObject(), function(err, address) {
should.not.exist(err); should.not.exist(err);
address.id.should.eql(address1.id); address.id.should.eql(2);
address.street.should.equal('Street 1'); address.street.should.equal('Street 1');
p.addressList.create(address2.toObject(), function(err, address) { p.addressList.create(address2.toObject(), function(err, address) {
should.not.exist(err); should.not.exist(err);
address.id.should.eql(address2.id); address.id.should.eql(3);
address.street.should.equal('Street 2'); address.street.should.equal('Street 2');
done(); done();
}); });
@ -2244,10 +2267,10 @@ describe('relations', function () {
it('should create embedded items on scope', function(done) { it('should create embedded items on scope', function(done) {
Person.findById(person.id, function(err, p) { Person.findById(person.id, function(err, p) {
p.addressList.create({ street: 'Street 3' }, function(err, address) { p.addressList.create({ street: 'Street 4' }, function(err, address) {
should.not.exist(err); should.not.exist(err);
address.should.have.property('id'); // not within Address seq! address.id.should.equal(5); // in Address sequence, correct offset
address.street.should.equal('Street 3'); address.street.should.equal('Street 4');
done(); done();
}); });
}); });
@ -2260,7 +2283,7 @@ describe('relations', function () {
addresses.should.have.length(3); addresses.should.have.length(3);
addresses[0].street.should.equal('Street 1'); addresses[0].street.should.equal('Street 1');
addresses[1].street.should.equal('Street 2'); addresses[1].street.should.equal('Street 2');
addresses[2].street.should.equal('Street 3'); addresses[2].street.should.equal('Street 4');
done(); done();
}); });
}); });
@ -2269,12 +2292,8 @@ describe('relations', function () {
it('should validate embedded items on scope - id', function(done) { it('should validate embedded items on scope - id', function(done) {
Person.create({ name: 'Wilma' }, function(err, p) { Person.create({ name: 'Wilma' }, function(err, p) {
p.addressList.create({ id: null, street: 'Street 1' }, function(err, address) { p.addressList.create({ id: null, street: 'Street 1' }, function(err, address) {
should.exist(err); should.not.exist(err);
err.name.should.equal('ValidationError'); address.street.should.equal('Street 1');
err.details.codes.addresses.should.eql(['invalid']);
var expected = 'The `Person` instance is not valid. ';
expected += 'Details: `addresses` contains invalid item at index `0`: `id` is blank.';
err.message.should.equal(expected);
done(); done();
}); });
}); });