Added option: reference to enable embedsMany add/remove

This commit is contained in:
Fabien Franzen 2014-07-29 17:43:30 +02:00
parent b18384459a
commit 60fd39d311
3 changed files with 156 additions and 8 deletions

View File

@ -133,7 +133,7 @@ Inclusion.include = function (objects, include, cb) {
obj.__cachedRelations[relationName] = result; obj.__cachedRelations[relationName] = result;
if(obj === inst) { if(obj === inst) {
obj.__data[relationName] = result; obj.__data[relationName] = result;
obj.__strict = false; obj.setStrict(false);
} else { } else {
obj[relationName] = result; obj[relationName] = result;
} }

View File

@ -65,6 +65,7 @@ function RelationDefinition(definition) {
} }
definition = definition || {}; definition = definition || {};
this.name = definition.name; this.name = definition.name;
this.accessor = definition.accessor || this.name;
assert(this.name, 'Relation name is missing'); assert(this.name, 'Relation name is missing');
this.type = normalizeType(definition.type); this.type = normalizeType(definition.type);
assert(this.type, 'Invalid relation type: ' + definition.type); assert(this.type, 'Invalid relation type: ' + definition.type);
@ -1444,6 +1445,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id'; var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
var definition = modelFrom.relations[accessorName] = new RelationDefinition({ var definition = modelFrom.relations[accessorName] = new RelationDefinition({
accessor: accessorName,
name: relationName, name: relationName,
type: RelationTypes.embedsMany, type: RelationTypes.embedsMany,
modelFrom: modelFrom, modelFrom: modelFrom,
@ -1507,8 +1509,11 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
destroy: scopeMethod(definition, 'destroyById'), destroy: scopeMethod(definition, 'destroyById'),
updateById: scopeMethod(definition, 'updateById'), updateById: scopeMethod(definition, 'updateById'),
exists: scopeMethod(definition, 'exists'), exists: scopeMethod(definition, 'exists'),
add: scopeMethod(definition, 'add'),
remove: scopeMethod(definition, 'remove'),
get: scopeMethod(definition, 'get'), get: scopeMethod(definition, 'get'),
set: scopeMethod(definition, 'set'), set: scopeMethod(definition, 'set'),
unset: scopeMethod(definition, 'unset'),
at: scopeMethod(definition, 'at') at: scopeMethod(definition, 'at')
}; };
@ -1521,6 +1526,12 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
var updateByIdFunc = scopeMethods.updateById; var updateByIdFunc = scopeMethods.updateById;
modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc; modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc;
var addFunc = scopeMethods.add;
modelFrom.prototype['__link__' + relationName] = addFunc;
var removeFunc = scopeMethods.remove;
modelFrom.prototype['__unlink__' + relationName] = removeFunc;
scopeMethods.create = scopeMethod(definition, 'create'); scopeMethods.create = scopeMethod(definition, 'create');
scopeMethods.build = scopeMethod(definition, 'build'); scopeMethods.build = scopeMethod(definition, 'build');
@ -1660,7 +1671,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) {
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[relationName] || [];
var inst = this.findById(fkId); var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId);
if (inst instanceof modelTo) { if (inst instanceof modelTo) {
var index = embeddedList.indexOf(inst); var index = embeddedList.indexOf(inst);
@ -1681,6 +1692,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) {
EmbedsMany.prototype.get = EmbedsMany.prototype.findById; EmbedsMany.prototype.get = EmbedsMany.prototype.findById;
EmbedsMany.prototype.set = EmbedsMany.prototype.updateById; EmbedsMany.prototype.set = EmbedsMany.prototype.updateById;
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;
@ -1734,7 +1746,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
}); });
}; };
EmbedsMany.prototype.build = HasOne.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 relationName = this.definition.name;
@ -1765,6 +1777,88 @@ EmbedsMany.prototype.build = HasOne.prototype.build = function(targetModelData)
return inst; return inst;
}; };
/**
* Add the target model instance to the 'embedsMany' relation
* @param {Object|ID} acInst The actual instance or id value
*/
EmbedsMany.prototype.add = function (acInst, cb) {
var self = this;
var definition = this.definition;
var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance;
var options = definition.options;
var referenceDef = options.reference && modelTo.relations[options.reference];
if (!referenceDef) {
throw new Error('Invalid reference: ' + options.reference || '(none)');
}
var fk2 = referenceDef.keyTo;
var pk2 = referenceDef.modelTo.definition.idName() || 'id';
var query = {};
query[fk2] = (acInst instanceof referenceDef.modelTo) ? acInst[pk2] : acInst;
var filter = { where: query };
referenceDef.applyScope(modelInstance, filter);
referenceDef.modelTo.findOne(filter, function(err, ref) {
if (ref instanceof referenceDef.modelTo) {
var inst = self.build();
inst[options.reference](ref);
modelInstance.save(function(err) {
cb(err, err ? null : inst);
});
} else {
cb(null, null);
}
});
};
/**
* Remove the target model instance from the 'embedsMany' relation
* @param {Object|ID) acInst The actual instance or id value
*/
EmbedsMany.prototype.remove = function (acInst, cb) {
var self = this;
var definition = this.definition;
var modelTo = this.definition.modelTo;
var modelInstance = this.modelInstance;
var options = definition.options;
var referenceDef = options.reference && modelTo.relations[options.reference];
if (!referenceDef) {
throw new Error('Invalid reference: ' + options.reference || '(none)');
}
var fk2 = referenceDef.keyTo;
var pk2 = referenceDef.modelTo.definition.idName() || 'id';
var query = {};
query[fk2] = (acInst instanceof referenceDef.modelTo) ? acInst[pk2] : acInst;
var filter = { where: query };
referenceDef.applyScope(modelInstance, filter);
modelInstance[definition.accessor](filter, function(err, items) {
if (err) return cb(err);
items.forEach(function(item) {
self.unset(item);
});
modelInstance.save(function(err) {
cb(err, err ? [] : items);
});
});
};
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) { RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) {
}; };

View File

@ -1545,13 +1545,13 @@ describe('relations', function () {
describe('embedsMany - relations, scope and properties', function () { describe('embedsMany - relations, scope and properties', function () {
var product1, product2; var product1, product2, product3;
before(function (done) { before(function (done) {
db = getSchema(); db = getSchema();
Category = db.define('Category', {name: String}); Category = db.define('Category', {name: String});
Product = db.define('Product', {name: String}); Product = db.define('Product', {name: String});
Link = db.define('Link'); Link = db.define('Link', {name: String});
db.automigrate(function () { db.automigrate(function () {
Person.destroyAll(done); Person.destroyAll(done);
@ -1561,13 +1561,16 @@ describe('relations', function () {
it('can be declared', function (done) { it('can be declared', function (done) {
Category.embedsMany(Link, { Category.embedsMany(Link, {
as: 'items', // rename as: 'items', // rename
scope: { include: 'product' } // always include scope: { include: 'product' }, // always include
options: { reference: 'product' } // optional, for add()/remove()
}); });
Link.belongsTo(Product, { Link.belongsTo(Product, {
foreignKey: 'id', // re-use the actual product id foreignKey: 'id', // re-use the actual product id
properties: { id: 'id', name: 'name' }, // denormalize, transfer id properties: { id: 'id', name: 'name' }, // denormalize, transfer id
}); });
db.automigrate(done); db.automigrate(function() {
Product.create({ name: 'Product 0' }, done); // offset ids for tests
});
}); });
it('should setup related items', function(done) { it('should setup related items', function(done) {
@ -1575,10 +1578,13 @@ describe('relations', function () {
product1 = p; product1 = p;
Product.create({ name: 'Product 2' }, function(err, p) { Product.create({ name: 'Product 2' }, function(err, p) {
product2 = p; product2 = p;
Product.create({ name: 'Product 3' }, function(err, p) {
product3 = p;
done(); done();
}); });
}); });
}); });
});
it('should create items on scope', function(done) { it('should create items on scope', function(done) {
Category.create({ name: 'Category A' }, function(err, cat) { Category.create({ name: 'Category A' }, function(err, cat) {
@ -1588,6 +1594,7 @@ describe('relations', function () {
link.product(product2); link.product(product2);
cat.save(function(err, cat) { cat.save(function(err, cat) {
var product = cat.items.at(0); var product = cat.items.at(0);
product.should.be.instanceof(Link);
product.should.not.have.property('productId'); product.should.not.have.property('productId');
product.id.should.eql(product1.id); product.id.should.eql(product1.id);
product.name.should.equal(product1.name); product.name.should.equal(product1.name);
@ -1604,6 +1611,7 @@ describe('relations', function () {
cat.links.should.have.length(2); cat.links.should.have.length(2);
// denormalized properties: // denormalized properties:
cat.items.at(0).should.be.instanceof(Link);
cat.items.at(0).id.should.eql(product1.id); cat.items.at(0).id.should.eql(product1.id);
cat.items.at(0).name.should.equal(product1.name); cat.items.at(0).name.should.equal(product1.name);
cat.items.at(1).id.should.eql(product2.id); cat.items.at(1).id.should.eql(product2.id);
@ -1650,6 +1658,52 @@ describe('relations', function () {
}); });
}); });
it('should add related items to scope', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(1);
cat.items.add(product3, function(err, link) {
link.should.be.instanceof(Link);
link.id.should.equal(product3.id);
link.name.should.equal('Product 3');
cat.links.should.have.length(2);
done();
});
});
});
it('should find items on scope', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(2);
cat.items.at(0).should.be.instanceof(Link);
cat.items.at(0).id.should.eql(product2.id);
cat.items.at(0).name.should.equal(product2.name);
cat.items.at(1).id.should.eql(product3.id);
cat.items.at(1).name.should.equal(product3.name);
done();
});
});
it('should remove embedded items by reference id', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(2);
cat.items.remove(product2.id, function(err) {
should.not.exist(err);
cat.links.should.have.length(1);
done();
});
});
});
it('should remove embedded items by reference id', function(done) {
Category.findOne(function(err, cat) {
cat.links.should.have.length(1);
done();
});
});
}); });
describe('embedsMany - polymorphic relations', function () { describe('embedsMany - polymorphic relations', function () {