Implemented more complex scenaro: embedsMany + relations

The test case will denormalize data into the embedded object,
and re-use the actual related object id as its own id.
This commit is contained in:
Fabien Franzen 2014-07-29 10:54:28 +02:00
parent 6ed7a0a5f2
commit da303b72a5
3 changed files with 169 additions and 12 deletions

View File

@ -1047,6 +1047,17 @@ DataAccessObject.prototype.remove =
}, null, cb); }, null, cb);
}; };
/**
* Set a single attribute.
* Equivalent to `setAttributes({name: value})`
*
* @param {String} name Name of property
* @param {Mixed} value Value of property
*/
DataAccessObject.prototype.setAttribute = function setAttribute(name, value) {
this[name] = value;
};
/** /**
* Update a single attribute. * Update a single attribute.
* Equivalent to `updateAttributes({name: value}, cb)` * Equivalent to `updateAttributes({name: value}, cb)`
@ -1062,7 +1073,27 @@ DataAccessObject.prototype.updateAttribute = function updateAttribute(name, valu
}; };
/** /**
* Update saet of attributes. * Update set of attributes.
*
* @trigger `change` hook
* @param {Object} data Data to update
*/
DataAccessObject.prototype.setAttributes = function setAttributes(data) {
if (typeof data !== 'object') return;
var Model = this.constructor;
var inst = this;
// update instance's properties
for (var key in data) {
inst.setAttribute(key, data[key]);
}
Model.emit('set', inst);
};
/**
* Update set of attributes.
* Performs validation before updating. * Performs validation before updating.
* *
* @trigger `validation`, `save` and `update` hooks * @trigger `validation`, `save` and `update` hooks
@ -1086,9 +1117,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
} }
// update instance's properties // update instance's properties
for (var key in data) { inst.setAttributes(data);
inst[key] = data[key];
}
inst.isValid(function (valid) { inst.isValid(function (valid) {
if (!valid) { if (!valid) {

View File

@ -1057,6 +1057,11 @@ BelongsTo.prototype.related = function (refresh, params) {
modelTo = params.constructor; modelTo = params.constructor;
modelInstance[fk] = params[pk]; modelInstance[fk] = params[pk];
if (discriminator) modelInstance[discriminator] = params.constructor.modelName; if (discriminator) modelInstance[discriminator] = params.constructor.modelName;
var data = {};
this.definition.applyProperties(params, data);
modelInstance.setAttributes(data);
self.resetCache(params); self.resetCache(params);
} else if (typeof params === 'function') { // acts as async getter } else if (typeof params === 'function') { // acts as async getter
@ -1525,7 +1530,9 @@ RelationDefinition.embedsMany = function hasMany(modelFrom, modelTo, params) {
}; };
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
var name = this.definition.name; var modelTo = this.definition.modelTo;
var relationName = this.definition.name;
var modelInstance = this.modelInstance;
var self = receiver; var self = receiver;
var actualCond = {}; var actualCond = {};
@ -1543,14 +1550,29 @@ 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[name] || []; var embeddedList = self[relationName] || [];
this.definition.applyScope(modelInstance, actualCond);
var params = mergeQuery(actualCond, scopeParams); var params = mergeQuery(actualCond, scopeParams);
if (params.where) { if (params.where) {
embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList; embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
} }
process.nextTick(function() { cb(null, embeddedList); }); var returnRelated = function(list) {
if (params.include) {
modelTo.include(list, params.include, cb);
} else {
process.nextTick(function() { cb(null, list); });
}
};
if (actualRefresh) {
}
returnRelated(embeddedList);
}; };
EmbedsMany.prototype.findById = function (fkId, cb) { EmbedsMany.prototype.findById = function (fkId, cb) {
@ -1722,10 +1744,6 @@ EmbedsMany.prototype.build = HasOne.prototype.build = function(targetModelData)
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[relationName] || [];
if (typeof targetModelData === 'function' && !cb) {
cb = targetModelData;
targetModelData = {};
}
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
if (typeof targetModelData[pk] !== 'number' && autoId) { if (typeof targetModelData[pk] !== 'number' && autoId) {

View File

@ -1221,6 +1221,9 @@ describe('relations', function () {
}); });
describe('embedsMany', function () { describe('embedsMany', function () {
var address1, address2;
before(function (done) { before(function (done) {
db = getSchema(); db = getSchema();
Person = db.define('Person', {name: String}); Person = db.define('Person', {name: String});
@ -1250,7 +1253,6 @@ describe('relations', function () {
p.addressList.build.should.be.a.function; p.addressList.build.should.be.a.function;
}); });
var address1, address2;
it('should create embedded items on scope', function(done) { it('should create embedded items on scope', function(done) {
Person.create({ name: 'Fred' }, function(err, p) { Person.create({ name: 'Fred' }, function(err, p) {
p.addressList.create({ street: 'Street 1' }, function(err, addresses) { p.addressList.create({ street: 'Street 1' }, function(err, addresses) {
@ -1541,4 +1543,112 @@ describe('relations', function () {
}); });
describe('embedsMany - relations, scope and properties', function () {
var product1, product2;
before(function (done) {
db = getSchema();
Category = db.define('Category', {name: String});
Product = db.define('Product', {name: String});
Link = db.define('Link');
db.automigrate(function () {
Person.destroyAll(done);
});
});
it('can be declared', function (done) {
Category.embedsMany(Link, {
as: 'items', // rename
scope: { include: 'product' } // always include
});
Link.belongsTo(Product, {
foreignKey: 'id', // re-use the actual product id
properties: { id: 'id', name: 'name' }, // denormalize, transfer id
});
db.automigrate(done);
});
it('should setup related items', function(done) {
Product.create({ name: 'Product 1' }, function(err, p) {
product1 = p;
Product.create({ name: 'Product 2' }, function(err, p) {
product2 = p;
done();
});
});
});
it('should create item on scope', function(done) {
Category.create({ name: 'Category A' }, function(err, cat) {
var link = cat.items.build();
link.product(product1);
var link = cat.items.build();
link.product(product2);
cat.save(function(err, cat) {
var product = cat.items.at(0);
product.id.should.equal(product1.id);
product.name.should.equal(product1.name);
var product = cat.items.at(1);
product.id.should.equal(product2.id);
product.name.should.equal(product2.name);
done();
});
});
});
it('should include related items on scope', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(2);
// denormalized properties:
cat.items.at(0).id.should.equal(product1.id);
cat.items.at(0).name.should.equal(product1.name);
cat.items.at(1).id.should.equal(product2.id);
cat.items.at(1).name.should.equal(product2.name);
// lazy-loaded relations
should.not.exist(cat.items.at(0).product());
should.not.exist(cat.items.at(1).product());
cat.items(function(err, items) {
cat.items.at(0).product().should.be.instanceof(Product);
cat.items.at(1).product().should.be.instanceof(Product);
cat.items.at(1).product().name.should.equal('Product 2');
done();
});
});
});
it('should remove embedded items by id', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(2);
cat.items.destroy(product1.id, function(err) {
should.not.exist(err);
cat.links.should.have.length(1);
done();
});
});
});
it('should find items on scope', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(1);
cat.items.at(0).id.should.equal(product2.id);
cat.items.at(0).name.should.equal(product2.name);
// lazy-loaded relations
should.not.exist(cat.items.at(0).product());
cat.items(function(err, items) {
cat.items.at(0).product().should.be.instanceof(Product);
cat.items.at(0).product().name.should.equal('Product 2');
done();
});
});
});
});
}); });