Chinese Documentation : Creating model relations

REVIEW COMMENT from Rand

From Fabien: Normally, properties are transferred from parent to child, but there are cases
where it makes sense to do the opposite. This was the case with the advanced example, and to enable this, a new option: invertProperties was introduced.

What's best place for this?

Overview of model relations

Individual models are easy to understand and work with. But in reality, models are often connected or related.  When you build a real-world application with multiple models, you'll typically need to define relations between models. For example:

  • A customer has many orders and each order is owned by a customer.
  • A user can be assigned to one or more roles and a role can have zero or more users.
  • A physician takes care of many patients through appointments. A patient can see many physicians too.

With connected models, LoopBack exposes as a set of APIs to interact with each of the model instances and query and filter the information based on the client’s needs.  

You can define the following relations between models:

You can define models relations in JSON in the Model definition JSON file file or in JavaScript code.  The end result is the same.

When you define a relation for a model, LoopBack adds a set of methods to the model, as detailed in the article on each type of relation.

Relation options

There are three options for most relation types:

  • Scope
  • Properties
  • Custom scope methods
REVIEW COMMENT from Rand
"Most"? Which ones DON'T they apply to?

Scope

The scope property can be an object or function, and applies to all filtering/conditions on the related scope.

The object or returned object (in case of the function call) can have all the usual filter options: whereorderincludelimitoffset, ...

These options are merged into the default filter, which means that the where part will be AND-ed. The other options usually override the defaults (standard mergeQuery behavior).

When scope is a function, it will receive the current instance, as well as the default filter object.

For example:

// only allow products of type: 'shoe', always include products
Category.hasMany(Product, 
                 { as: 'shoes', 
                   scope: { where: { type: 'shoe' }, 
                   include: 'products'
});
Product.hasMany(Image, 
                { scope: function(inst, filter) {
                           return { type: inst.type }; 
                         }
});  // inst is a category - match category type with product type.

Properties

You can specify the properties option in two ways:

  • As an object: the keys refer to the instance, the value will be the attribute key on the related model (mapping)
  • As a function: the resulting object (key/values) are merged into the related model directly.

For example, the following relation transfers the type to the product, and de-normalizes the category name into categoryName on creation:

Category.hasMany(Product, 
                 { as: 'shoes', 
                 properties: { type: 'type', category: 'categoryName' });

To accomplish the same thing with a callback function:

Product.hasMany(Image, 
                { properties: function(inst) { // inst is a category
                                return { type: inst.type, categoryName: inst.name };
                              }
});

Custom scope methods

Finally, you can add custom scope methods using the scopeMethods property.  Again, the option can be either an object or a function (advanced use).

Icon

By default custom scope methods are not exposed as remote methods; You must set functionName.shared = true.

For example:

var reorderFn = function(ids, cb) {
  // `this` refers to the RelationDefinition
  console.log(this.name); // `images` (relation name)
  // do some reordering here & save
  cb(null, [3, 2, 1]);
};

// manually declare remoting params
reorderFn.shared = true;
reorderFn.accepts = { arg: 'ids', type: 'array', http: { source: 'body' } };
reorderFn.returns = { arg: 'ids', type: 'array', root: true };
reorderFn.http = { verb: 'put', path: '/images/reorder' };

Product.hasMany(Image, { scopeMethods: { reorder: reorderFn } });

Exposing REST APIs for related models

The following example demonstrates how to access connected models via REST APIs.

The code is available at https://github.com/strongloop/loopback-example-relations-basic.

If you run the example application, the REST API is available at http://localhost:3000/api.  The home page at http://0.0.0.0:3000/ contains the links shown below.  Here are the example endpoints and queries:

EndpointDescription
/api/customers

List all customers

/api/customers?filter[fields][0]=nameList all customers with the name property only
/api/customers/1Return customer record for id of 1
/api/customers/youngFolksList a predefined scope ‘youngFolks’
/api/customers/1/reviewsList all reviews posted by a given customer
/api/customers/1/ordersList all orders placed by a given customer
/api/customers?filter[include]=reviewsList all customers including their reviews
/api/customers?filter[include][reviews]=authorList all customers including their reviews which also includes the author
/api/customers?filter[include][reviews]=author&filter[where][age]=21List all customers whose age is 21, including their reviews which also includes the author
/api/customers?filter[include][reviews]=author&filter[limit]=2List first two customers including their reviews which also includes the author
/api/customers?filter[include]=reviews&filter[include]=ordersList all customers including their reviews and orders