Embedded relations are conceptually similar to sub-documents in MongooseJS.
Embedded models are usually not persisted separately, however, you can do so to de-normalize or "freeze" data.
Example models: TodoList, TodoItem
// the accessor name defaults to: 'singular + List' (todoItemList) TodoList.embedsMany(TodoItem, { as: 'items' }); TodoList.create({ name: 'Work' }, function(err, list) { list.items.build({ content: 'Do this', priority: 5 }); list.items.build({ content: 'Do that', priority: 1 }); list.save(function(err, list) { console.log(err, list); }); }); TodoList.findOne(function(err, list) { console.log(list.todoItems[0].content); // `Do this` console.log(list.items.at(1).content); // `Do that` list.items.find({ where: { priority: 1 } }, function(err, item) { console.log(item.content); // `Do that` }); });
Advanced example: embed with belongsTo
Embedded models can have relations, just like any other model. This is a powerful technique that can simplify certain setups, and enables de-normalization of data. An added benefit is that the internal array explicitly defines the order of items.
The following example creates a relation similar to a HasManyThrough relation. However, it uses an embedded model called Link
instead of a separately-persisted through-model. The models involved are Category, Product, and Link.
The new relation options scope
and properties
come in to play here as well.
var Category = db.define('Category', { name: String }); var Product = db.define('Product', { name: String }); var Link = db.define('Link', { notes: String }); Category.embedsMany(Link, { as: 'items', // rename (default: productList) scope: { include: 'product' } }); Link.belongsTo(Product, { foreignKey: 'id', // re-use the actual product id properties: { id: 'id', name: 'name' }, // denormalize, transfer id // For info on invertProperties see https://github.com/strongloop/loopback-datasource-juggler/pull/219 options: { invertProperties: true } }); Category.create({ name: 'Category B' }, function(err, cat) { var category = cat; var link = cat.items.build({ notes: 'Some notes...' }); link.product.create({ name: 'Product 1' }, function(err, p) { //cat.links[0].id.should.eql(p.id); console.log(cat.links[0].id); // 1 console.log(cat.links[0].name); // Product 1 console.log(cat.links[0].notes); // Some notes... console.log(cat.items.at(0));// Should equal(cat.links[0]); Category.findById(category.id, function(err, cat) { console.log(cat.name); // Should equal('Category B'); console.log(cat.links); // Should equal "{id: 1, name: 'Product 1', notes: 'Some notes...'}" console.log(cat.items.at(0)); // Should Equal(cat.links[0]); cat.items(function(err, items) { // alternative access console.log(items); /* Should equal: [{ notes: 'Some notes...', id: 1, name: 'Product 1', product: { name: 'Product 1', id: 1 }}] */ items[0].product(function(err, p) { console.log(p.name); // Should equal ('Product 1'); }); }); }); }) });
Another example
The following example creates a relation similar to a hasManyThrough relation. However, it uses an embedded model called Link
instead of a separately persisted "through" model. The example uses models named Category, Product, and Link
The example also uses relation options scope
and properties
as well.
var Category = db.define('Category', { name: String }); var Product = db.define('Product', { name: String }); var Link = db.define('Link', { notes: String }); Category.embedsMany(Link, { as: 'items', // rename (default: productList) scope: { include: 'product' } }); Link.belongsTo(Product, { foreignKey: 'id', // re-use the actual product id properties: { id: 'id', name: 'name' }, // denormalize, transfer id options: { invertProperties: true } }); Category.create({ name: 'Category B' }, function(err, cat) { category = cat; var link = cat.items.build({ notes: 'Some notes...' }); link.product.create({ name: 'Product 1' }, function(err, p) { cat.links[0].id.should.eql(p.id); cat.links[0].name.should.equal('Product 1'); // denormalized cat.links[0].notes.should.equal('Some notes...'); cat.items.at(0).should.equal(cat.links[0]); done(); }) }); Category.findById(category.id, function(err, cat) { cat.name.should.equal('Category B'); cat.links.toObject().should.eql([ {id: 5, name: 'Product 1', notes: 'Some notes...'} ]); cat.items.at(0).should.equal(cat.links[0]); cat.items(function(err, items) { // alternative access items.should.be.an.array; items.should.have.length(1); items[0].product(function(err, p) { p.name.should.equal('Product 1'); // actual value done(); }); }); });
Defining in model JSON (LDL)
{ "name": "Person", "plural": "people", "base": "PersistedModel", "properties": { "firstName": { "type": "string", "required": true }, "lastName": { "type": "string" } }, "validations": [], "relations": { "addresses": { "type": "embedsMany", "model": "Location", "options": { "validate": true, "autoId": false } }, "pictures": { "type": "hasMany", "model": "Picture", "polymorphic": "imageable" } }, "acls": [], "methods": [], "mixins": { "ObjectId": true } }