Merge pull request #419 from fabien/feature/new-relations

Integration of the new juggler relations: embedsMany, referencesMany
This commit is contained in:
Raymond Feng 2014-07-30 16:32:33 -07:00
commit 0590eaf278
2 changed files with 452 additions and 3 deletions

View File

@ -192,7 +192,10 @@ Model.setup = function () {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define)
} else if (relation.type === 'hasMany') {
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
ModelCtor.scopeRemoting(relationName, relation, define);
} else {
@ -387,7 +390,9 @@ Model.hasManyRemoting = function (relationName, relation, define) {
returns: {arg: 'result', type: toModelName, root: true}
}, updateByIdFunc);
if (relation.modelThrough) {
if (relation.modelThrough || relation.type === 'referencesMany') {
var modelThrough = relation.modelThrough || relation.modelTo;
var addFunc = this.prototype['__link__' + relationName];
define('__link__' + relationName, {
isStatic: false,
@ -396,7 +401,7 @@ Model.hasManyRemoting = function (relationName, relation, define) {
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
description: 'Add a related item by id for ' + relationName,
returns: {arg: relationName, type: relation.modelThrough.modelName, root: true}
returns: {arg: relationName, type: modelThrough.modelName, root: true}
}, addFunc);
var removeFunc = this.prototype['__unlink__' + relationName];

View File

@ -482,4 +482,448 @@ describe('relations - integration', function () {
});
});
});
describe('embedsMany', function() {
before(function defineProductAndCategoryModels() {
var todoList = app.model(
'todoList',
{ properties: { name: 'string' },
dataSource: 'db',
plural: 'todo-lists'
}
);
var todoItem = app.model(
'todoItem',
{ properties: { content: 'string' }, dataSource: 'db' }
);
todoList.embedsMany(todoItem, { as: 'items' });
});
before(function createTodoList(done) {
var test = this;
app.models.todoList.create({ name: 'List A' },
function(err, list) {
if (err) return done(err);
test.todoList = list;
list.items.build({ content: 'Todo 1' });
list.items.build({ content: 'Todo 2' });
list.save(done);
});
});
after(function(done) {
this.app.models.todoList.destroyAll(done);
});
it('includes the embedded models', function(done) {
var url = '/api/todo-lists/' + this.todoList.id;
this.get(url)
.expect(200, function(err, res) {
expect(res.body.name).to.be.equal('List A');
expect(res.body.todoItems).to.be.eql([
{ content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 }
]);
done();
});
});
it('returns the embedded models', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items';
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 }
]);
done();
});
});
it('filters the embedded models', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items';
url += '?filter[where][id]=2';
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ content: 'Todo 2', id: 2 }
]);
done();
});
});
it('creates embedded models', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items';
var expected = { content: 'Todo 3', id: 3 };
this.post(url)
.send({ content: 'Todo 3' })
.expect(200, function(err, res) {
expect(res.body).to.be.eql(expected);
done();
});
});
it('returns the embedded models', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items';
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 },
{ content: 'Todo 3', id: 3 }
]);
done();
});
});
it('returns an embedded model by (internal) id', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items/3';
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql(
{ content: 'Todo 3', id: 3 }
);
done();
});
});
it('removes an embedded model', function(done) {
var expectedProduct = this.product;
var url = '/api/todo-lists/' + this.todoList.id + '/items/2';
this.del(url)
.expect(200, function(err, res) {
done();
});
});
it('returns the embedded models - verify', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items';
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 },
{ content: 'Todo 3', id: 3 }
]);
done();
});
});
// TODO - this.head is undefined
it.skip('checks if an embedded model exists - ok', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items/3';
this.head(url)
.expect(200, function(err, res) {
done();
});
});
it.skip('checks if an embedded model exists - fail', function(done) {
var url = '/api/todo-lists/' + this.todoList.id + '/items/2';
this.head(url)
.expect(404, function(err, res) {
done();
});
});
});
describe('referencesMany', function() {
before(function defineProductAndCategoryModels() {
var recipe = app.model(
'recipe',
{ properties: { name: 'string' }, dataSource: 'db' }
);
var ingredient = app.model(
'ingredient',
{ properties: { name: 'string' }, dataSource: 'db' }
);
recipe.referencesMany(ingredient);
});
before(function createRecipe(done) {
var test = this;
app.models.recipe.create({ name: 'Recipe' },
function(err, recipe) {
if (err) return done(err);
test.recipe = recipe;
recipe.ingredients.create({
name: 'Chocolate' },
function(err, ing) {
test.ingredient1 = ing.id;
done();
});
});
});
before(function createIngredient(done) {
var test = this;
app.models.ingredient.create({ name: 'Sugar' }, function(err, ing) {
test.ingredient2 = ing.id;
done();
});
});
after(function(done) {
var app = this.app;
app.models.recipe.destroyAll(function() {
app.models.ingredient.destroyAll(done);
});
});
it('keeps an array of ids', function(done) {
var url = '/api/recipes/' + this.recipe.id;
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body.ingredientIds).to.eql([test.ingredient1]);
expect(res.body).to.not.have.property('ingredients');
done();
});
});
it('creates referenced models', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
var test = this;
this.post(url)
.send({ name: 'Butter' })
.expect(200, function(err, res) {
expect(res.body.name).to.be.eql('Butter');
test.ingredient3 = res.body.id;
done();
});
});
it('has created models', function(done) {
var url = '/api/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 },
{ name: 'Butter', id: test.ingredient3 }
]);
done();
});
});
it('returns the referenced models', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Butter', id: test.ingredient3 }
]);
done();
});
});
it('filters the referenced models', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
url += '?filter[where][name]=Butter';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Butter', id: test.ingredient3 }
]);
done();
});
});
it('includes the referenced models', function(done) {
var url = '/api/recipes/findOne?filter[where][id]=' + this.recipe.id;
url += '&filter[include]=ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body.ingredientIds).to.eql([
test.ingredient1, test.ingredient3
]);
expect(res.body.ingredients).to.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Butter', id: test.ingredient3 }
]);
done();
});
});
it('returns a referenced model by id', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
url += this.ingredient3;
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql(
{ name: 'Butter', id: test.ingredient3 }
);
done();
});
});
it('keeps an array of ids - verify', function(done) {
var url = '/api/recipes/' + this.recipe.id;
var test = this;
var expected = [test.ingredient1, test.ingredient3];
this.get(url)
.expect(200, function(err, res) {
expect(res.body.ingredientIds).to.eql(expected);
expect(res.body).to.not.have.property('ingredients');
done();
});
});
it('destroys a referenced model', function(done) {
var expectedProduct = this.product;
var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
url += this.ingredient3;
this.del(url)
.expect(200, function(err, res) {
done();
});
});
it('has destroyed a referenced model', function(done) {
var url = '/api/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 }
]);
done();
});
});
it('returns the referenced models - verify', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }
]);
done();
});
});
it('creates/links a reference by id', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
url += '/rel/' + this.ingredient2;
var test = this;
this.put(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql(
{ name: 'Sugar', id: test.ingredient2 }
);
done();
});
});
it('returns the referenced models - verify', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 }
]);
done();
});
});
it('removes/unlinks a reference by id', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
url += '/rel/' + this.ingredient1;
var test = this;
this.del(url)
.expect(200, function(err, res) {
done();
});
});
it('returns the referenced models - verify', function(done) {
var url = '/api/recipes/' + this.recipe.id + '/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Sugar', id: test.ingredient2 }
]);
done();
});
});
it('has not destroyed an unlinked model', function(done) {
var url = '/api/ingredients';
var test = this;
this.get(url)
.expect(200, function(err, res) {
expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 }
]);
done();
});
});
// TODO - this.head is undefined
// it.skip('checks if a referenced model exists - ok', function(done) {
// var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
// url += this.ingredient1;
//
// this.head(url)
// .expect(200, function(err, res) {
// done();
// });
// });
// it.skip('checks if an referenced model exists - fail', function(done) {
// var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
// url += this.ingredient3;
//
// this.head(url)
// .expect(404, function(err, res) {
// done();
// });
// });
});
});