strongloop/loopback-datasource-juggler — pull request #296 by fabien (2014-09-07 12:35:12+00:00)
Title: Feature/default scope

See #276 - this loosely follows http://guides.rubyonrails.org/active_record_querying.html#applying-a-default-scope

It consists of two parts, both static methods:

*applyProperties* - which, as its relation's namesake, will apply property values from the default scope.

The values will be gathered from either a `properties` object (or function) on the model definition's settings, or straight from the default scope's `where` clause.

Unlike default values, these values are always enforced, and are regarded as immutable. None of the manipulation methods can override these values (as this could pose security holes and other inconsistencies). Set `properties: false` to disable this enforcing of properties.

In order to be compatible with LDL notation (model.json), `properties` has been aliased to `attributes`.

*applyScope* - this will apply the base conditions (`where, order, skip, limit, fields, ...`) for any operation on the model, and cannot be circumvented in any way, except for the `order` which is to be considered harmless.

They both feed off the `defaultScope` static method by default, for the scope to apply. In turn it uses the `scope` specified on the model definition's settings (as available to LDL as well), which can be a function as well, to dynamically generate the scope at runtime (think of: conditions involving a timestamp, eg. 'recent items').

Overriding any of these methods is also considered a viable and very powerful technique. Especially whenever https://github.com/strongloop/loopback/pull/337 lands.

Note that the properties will be applied even on a new instance, and enforced whenever persisting data. This could be the basis for true `immutable` properties (the current instance will be provided as a context to a function, where appropriate).

----

In order to test this in a more real-world scenario, I adapted the Memory connector to support the concept of `collections`, that can be set by the user. The MongoDB connector supports this as well, whereas SQL connectors can use a `table` setting for this purpose (both mongo as well as postgresql have been tested with default-scope.test.js). In essence, the test case mimics a Single Table Inheritance setup (see http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html).

Each model is persisted to the same table, and the model type is stored in a `kind` property using default scope/enforced properties. If we were to take this further, and wanted individual model instance types to return from the base model (Product in this case), additional steps are necessary though. For now, they will all be Product instances, unless specifically fetched through their actual model (Tool, Widget, Thing).

----

Finally, by introducing this feature, some connector methods are becoming more or less obsolete - lookups just by their `id` are no longer sufficient to cover the additional scope conditions that might be set. It has to be seen if this needs further optimization, but for most connectors there's little to no difference in the way the final query will be performed. And thus, performance should not suffer at all, except for the default scope incurred filtering, if any (make sure you use indexes).