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:
parent
6ed7a0a5f2
commit
da303b72a5
37
lib/dao.js
37
lib/dao.js
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue