Added option: reference to enable embedsMany add/remove
This commit is contained in:
parent
b18384459a
commit
60fd39d311
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
Loading…
Reference in New Issue