LoopBack DataSource Juggler

LoopBack DataSource Juggler is an ORM that provides a common set of interfaces for interacting with databases, REST APIs, and other data sources. It was initially forked from JugglingDB.

Overview

LoopBack DataSource Juggler consists of the following components:

  • LoopBack Definition Language
  • DataSource
  • Connector

loopback-datasource-juggler-overview

LoopBack Definition Language

To define model dataSource have single method dataSource.define. It accepts three arguments:

  • model name: String name in camel-case with first upper-case letter. This name will be used later to access model.
  • properties: Object with property type definitions. Key is property name, value is type definition. Type definition can be function representing type of property (String, Number, Date, Boolean), or object with {type: String|Number|..., index: true|false} format.
  • settings: Object with model-wide settings such as tableName or so.

Examples of model definition:

var User = dataSource.define('User', {
    email: String,
    password: String,
    birthDate: Date,
    activated: Boolean
});

var User = dataSource.define('User', {
    email: { type: String, limit: 150, index: true },
    password: { type: String, limit: 50 },
    birthDate: Date,
    registrationDate: {
        type: Date,
        default: function () { return new Date }
    },
    activated: { type: Boolean, default: false }
}, {
    tableName: 'users'
});

DataSource

DataSource is a factory for model classes. DataSource connected with specific database or other backend system using connector.

All model classes within single datasource shares same connector type and one database connection. But it's possible to use more than one datasource to connect with different databases.

Creating dataSource

DataSource constructor available on loopback-datasource-juggler module:

var DataSource = require('loopback-datasource-juggler').DataSource;

DataSource constructor accepts two arguments. First argument is connector. It could be connector name or connector package:

var dataSourceByConnectorName = new DataSource('memory');
var dataSourceByConnectorModule = new DataSource(require('redis'));

Settings

Second argument is optional settings. Settings object format and defaults depends on specific connector, but common fields are:

  • host: Database host
  • port: Database port
  • username: Username to connect to database
  • password: Password to connect to database
  • database: Database name
  • debug: Turn on verbose mode to debug db queries and lifecycle

For connector-specific settings refer to connector's readme file.

LoopBack Connectors

Type Package Name
MongoDB loopback-connector-mongodb
Oracle loopback-connector-oracle
MySQL loopback-connector-mysql

LoopBack connectors provide access to backend systems including databases, REST APIs and other services. Connectors are not used directly by application code. We create a DataSource to interact with the connector.

For example,

var DataSource = require('loopback-datasource-juggler').DataSource;
var oracleConnector = require('loopback-connector-oracle');

var ds = new DataSource(oracleConnector, {
    host : '127.0.0.1',
    database : 'XE',
    username : 'strongloop',
    password : 'password',
    debug : true
});

The connector argument passed the DataSource constructor can be one of the following:

  • The connector module from require(connectorName)
  • The full name of the connector module, such as 'loopback-connector-oracle'
  • The short name of the connector module, such as 'oracle', which will be converted to 'loopback-connector-'
  • A local module under ./connectors/ folder

Installation

npm install loopback-datasource-juggler

Also install the appropriated connector, for example for mongodb:

npm install loopback-connector-mongodb

check following list of available connectors

LoopBack DataSource API

Model and types

modelBuilder.define(className, properties, settings)

Define a model class

Arguments
Name Type Description
className String
properties Object

hash of class properties in format {property: Type, property2: Type2, ...} or {property: {type: Type}, property2: {type: Type2}, ...}

settings Object

other configuration of class

Example

more advanced case var User = dataSource.define('User', { email: { type: String, limit: 150, index: true }, password: { type: String, limit: 50 }, birthDate: Date, registrationDate: {type: Date, default: function () { return new Date }}, activated: { type: Boolean, default: false } });

ModelClass.registerProperty(propertyName)

Register a property for the model class

Arguments
Name Type Description
propertyName

modelBuilder.defineProperty(model, propertyName, propertyDefinition)

Define single property named propertyName on model

Arguments
Name Type Description
model String

name of model

propertyName String

name of property

propertyDefinition Object

property settings

modelBuilder.extendModel(model, props)

Extend existing model with bunch of properties

Arguments
Name Type Description
model String

name of model

props Object

hash of properties Example:

// Instead of doing this:

// amend the content model with competition attributes
db.defineProperty('Content', 'competitionType', { type: String });
db.defineProperty('Content', 'expiryDate', { type: Date, index: true });
db.defineProperty('Content', 'isExpired', { type: Boolean, index: true });

// dataSource.extend allows to
// extend the content model with competition attributes
db.extendModel('Content', {
  competitionType: String,
  expiryDate: { type: Date, index: true },
  isExpired: { type: Boolean, index: true }
});

modelBuilder.getSchemaName()

Get the schema name

modelBuilder.resolveType(type)

Resolve the type string to be a function, for example, 'String' to String

Arguments
Name Type Description
type String

The type string, such as 'number', 'Number', 'boolean', or 'String'. It's case insensitive

Returns
Name Type Description
result Function

if the type is resolved

modelBuilder.buildModels(schemas)

Build models from dataSource definitions

schemas can be one of the following:

  1. An array of named dataSource definition JSON objects
  2. A dataSource definition JSON object
  3. A list of property definitions (anonymous)
Arguments
Name Type Description
schemas

The schemas

Returns
Name Type Description
result Object

A map of model constructors keyed by model name

modelBuilder.buildModelFromInstance(name, json, [Object} options The options)

Introspect the json document to build a corresponding model

Arguments
Name Type Description
name String

The model name

json Object

The json object

[Object} options The options
Returns
Name Type Description
result

{}

exports.ModelBuilder

Export public API

this.models

Class Properties
Name Type Description
models Object

Model constructors

this.definitions

Class Properties
Name Type Description
definitions Object

Definitions of the models

Types.Text()

Schema types

GeoPoint.distanceBetween()

Determine the spherical distance between two geo points.

geoPoint.distanceTo()

Determine the spherical distance to the given point.

geoPoint.toString()

Simple serialization.

exports.GeoPoint

Export the GeoPoint class.

ModelBaseClass(data)

Model class - base class for all persist objects provides common API to access any database connector. This class describes only abstract behavior layer, refer to lib/connectors/*.js to learn more about specific connector implementations

ModelBaseClass mixes Validatable and Hookable classes methods

Arguments
Name Type Description
data Object

initial object data

ModelBaseClass.defineProperty(prop, params)

Arguments
Name Type Description
prop String

property name

params Object

various property configuration

ModelBaseClass.toString()

Return string representation of class

modelBaseClass.toObject(onlySchema)

Convert instance to Object

Arguments
Name Type Description
onlySchema Boolean

restrict properties to dataSource only, default false when onlySchema == true, only properties defined in dataSource returned, otherwise all enumerable properties returned

Returns
Name Type Description
result Object
  • canonical object representation (no getters and setters)

modelBaseClass.propertyChanged(propertyName)

Checks is property changed based on current property and initial value

Arguments
Name Type Description
propertyName String

property name

modelBaseClass.reset()

Reset dirty attributes

this method does not perform any database operation it just reset object to it's initial state

module.exports

Module exports class Model

DataSource

DataSource(name, settings)

DataSource - connector-specific classes factory.

All classes in single dataSource shares same connector type and one database connection

Arguments
Name Type Description
name String

type of dataSource connector (mysql, mongoose, oracle, redis)

settings Object

any database-specific settings which we need to establish connection (of course it depends on specific connector)

  • host
  • port
  • username
  • password
  • database
  • debug {Boolean} = false
Example

DataSource creation, waiting for connection callback var dataSource = new DataSource('mysql', { database: 'myapp_test' }); dataSource.define(...); dataSource.on('connected', function () { // work with database });

dataSource.createModel(className, properties, settings)

Define a model class

Arguments
Name Type Description
className String
properties Object

hash of class properties in format {property: Type, property2: Type2, ...} or {property: {type: Type}, property2: {type: Type2}, ...}

settings Object

other configuration of class

Example

more advanced case var User = dataSource.define('User', { email: { type: String, limit: 150, index: true }, password: { type: String, limit: 50 }, birthDate: Date, registrationDate: {type: Date, default: function () { return new Date }}, activated: { type: Boolean, default: false } });

dataSource.mixin(ModelCtor)

Mixin DataAccessObject methods.

Arguments
Name Type Description
ModelCtor Function

The model constructor

dataSource.attach(ModelCtor)

Attach an existing model to a data source.

Arguments
Name Type Description
ModelCtor Function

The model constructor

dataSource.defineProperty(model, prop, params)

Define single property named prop on model

Arguments
Name Type Description
model String

name of model

prop String

name of propery

params Object

property settings

dataSource.automigrate(or, [cb])

Drop each model table and re-create. This method make sense only for sql connectors.

Arguments
Name Type Description
or String

{[String]} Models to be migrated, if not present, apply to all models

[cb] Function

The callback function

dataSource.autoupdate(or, [cb])

Update existing database tables. This method make sense only for sql connectors.

Arguments
Name Type Description
or String

{[String]} Models to be migrated, if not present, apply to all models

[cb] Function

The callback function

dataSource.discoverModelDefinitions(options, [cb])

Discover existing database tables. This method returns an array of model objects, including {type, name, onwer}

options

 all: true - Discovering all models, false - Discovering the models owned by the current user
 views: true - Including views, false - only tables
 limit: The page size
 offset: The starting index
Arguments
Name Type Description
options Object

The options

[cb] Function

The callback function

dataSource.discoverModelDefinitionsSync(options)

The synchronous version of discoverModelDefinitions

Arguments
Name Type Description
options Object

The options

Returns
Name Type Description
result

dataSource.discoverModelProperties(modelName, options, [cb])

Discover properties for a given model.

property description

 owner {String} The database owner or schema
 tableName {String} The table/view name
 columnName {String} The column name
 dataType {String} The data type
 dataLength {Number} The data length
 dataPrecision {Number} The numeric data precision
 dataScale {Number} The numeric data scale
 nullable {Boolean} If the data can be null

options

 owner/schema The database owner/schema
Arguments
Name Type Description
modelName String

The table/view name

options Object

The options

[cb] Function

The callback function

dataSource.discoverModelPropertiesSync(modelName, options)

The synchronous version of discoverModelProperties

Arguments
Name Type Description
modelName String

The table/view name

options Object

The options

Returns
Name Type Description
result

dataSource.discoverPrimaryKeys(modelName, options, [cb])

Discover primary keys for a given owner/modelName

Each primary key column description has the following columns:

 owner {String} => table schema (may be null)
 tableName {String} => table name
 columnName {String} => column name
 keySeq {Number} => sequence number within primary key( a value of 1 represents the first column of the primary key, a value of 2 would represent the second column within the primary key).
 pkName {String} => primary key name (may be null)

 The owner, default to current user

options

 owner/schema The database owner/schema
Arguments
Name Type Description
modelName String

The model name

options Object

The options

[cb] Function

The callback function

dataSource.discoverPrimaryKeysSync(modelName, options)

The synchronous version of discoverPrimaryKeys

Arguments
Name Type Description
modelName String

The model name

options Object

The options

Returns
Name Type Description
result

dataSource.discoverForeignKeys(modelName, options, [cb])

Discover foreign keys for a given owner/modelName

foreign key description

 fkOwner String => foreign key table schema (may be null)
 fkName String => foreign key name (may be null)
 fkTableName String => foreign key table name
 fkColumnName String => foreign key column name
 keySeq Number => sequence number within a foreign key( a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
 pkOwner String => primary key table schema being imported (may be null)
 pkName String => primary key name (may be null)
 pkTableName String => primary key table name being imported
 pkColumnName String => primary key column name being imported

options

 owner/schema The database owner/schema
Arguments
Name Type Description
modelName String

The model name

options Object

The options

[cb] Function

The callback function

dataSource.discoverForeignKeysSync(modelName, options)

The synchronous version of discoverForeignKeys

Arguments
Name Type Description
modelName String

The model name

options Object

The options

Returns
Name Type Description
result

dataSource.discoverExportedForeignKeys(modelName, options, [cb])

Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). They are ordered by fkTableOwner, fkTableName, and keySeq.

foreign key description

 fkOwner {String} => foreign key table schema (may be null)
 fkName {String} => foreign key name (may be null)
 fkTableName {String} => foreign key table name
 fkColumnName {String} => foreign key column name
 keySeq {Number} => sequence number within a foreign key( a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
 pkOwner {String} => primary key table schema being imported (may be null)
 pkName {String} => primary key name (may be null)
 pkTableName {String} => primary key table name being imported
 pkColumnName {String} => primary key column name being imported

options

 owner/schema The database owner/schema
Arguments
Name Type Description
modelName String

The model name

options Object

The options

[cb] Function

The callback function

dataSource.discoverExportedForeignKeysSync(modelName, options)

The synchronous version of discoverExportedForeignKeys

Arguments
Name Type Description
modelName String

The model name

options Object

The options

Returns
Name Type Description
result

dataSource.discoverSchema(modelName, [options], [cb])

Discover one schema from the given model without following the associations

Arguments
Name Type Description
modelName String

The model name

[options] Object

The options

[cb] Function

The callback function

dataSource.discoverSchemas(modelName, [options], [cb])

Discover schema from a given modelName/view

options

 {String} owner/schema - The database owner/schema name
 {Boolean} associations - If relations (primary key/foreign key) are navigated
 {Boolean} all - If all owners are included
 {Boolean} views - If views are included
Arguments
Name Type Description
modelName String

The model name

[options] Object

The options

[cb] Function

The callback function

dataSource.discoverSchemasSync(modelName, [options])

Discover schema from a given table/view synchronously

options

 {String} owner/schema - The database owner/schema name
 {Boolean} associations - If relations (primary key/foreign key) are navigated
 {Boolean} all - If all owners are included
 {Boolean} views - If views are included
Arguments
Name Type Description
modelName String

The model name

[options] Object

The options

dataSource.discoverAndBuildModels(modelName, [options], [cb])

Discover and build models from the given owner/modelName

options

 {String} owner/schema - The database owner/schema name
 {Boolean} associations - If relations (primary key/foreign key) are navigated
 {Boolean} all - If all owners are included
 {Boolean} views - If views are included
Arguments
Name Type Description
modelName String

The model name

[options] Object

The options

[cb] Function

The callback function

dataSource.discoverAndBuildModelsSync(modelName, [options])

Discover and build models from the given owner/modelName synchronously

options

 {String} owner/schema - The database owner/schema name
 {Boolean} associations - If relations (primary key/foreign key) are navigated
 {Boolean} all - If all owners are included
 {Boolean} views - If views are included
Arguments
Name Type Description
modelName String

The model name

[options] Object

The options

dataSource.isActual([models])

Check whether migrations needed This method make sense only for sql connectors.

Arguments
Name Type Description
[models] Array.<String>

A model name or an array of model names. If not present, apply to all models

dataSource.freeze()

Freeze dataSource. Behavior depends on connector

dataSource.tableName(modelName)

Return table name for specified modelName

Arguments
Name Type Description
modelName String

The model name

dataSource.columnName(modelName, propertyName)

Return column name for specified modelName and propertyName

Arguments
Name Type Description
modelName String

The model name

propertyName

The property name

Returns
Name Type Description
result String

columnName

dataSource.columnMetadata(modelName, propertyName)

Return column metadata for specified modelName and propertyName

Arguments
Name Type Description
modelName String

The model name

propertyName

The property name

Returns
Name Type Description
result Object

column metadata

dataSource.columnNames(modelName)

Return column names for specified modelName

Arguments
Name Type Description
modelName String

The model name

Returns
Name Type Description
result Array.<String>

column names

dataSource.idColumnName(modelName)

Find the ID column name

Arguments
Name Type Description
modelName String

The model name

Returns
Name Type Description
result String

columnName for ID

dataSource.idName(modelName)

Find the ID property name

Arguments
Name Type Description
modelName String

The model name

Returns
Name Type Description
result String

property name for ID

dataSource.idNames(modelName)

Find the ID property names sorted by the index

Arguments
Name Type Description
modelName String

The model name

Returns
Name Type Description
result Array.<String>

property names for IDs

dataSource.defineForeignKey(className, key, foreignClassName)

Define foreign key to another model

Arguments
Name Type Description
className String

The model name that owns the key

key String

name of key field

foreignClassName String

The foreign model name

dataSource.disconnect([cb])

Close database connection

Arguments
Name Type Description
[cb] Fucntion

The callback function

dataSource.enableRemote(operation)

Enable a data source operation to be remote.

Arguments
Name Type Description
operation String

The operation name

dataSource.disableRemote(operation)

Disable a data source operation to be remote.

Arguments
Name Type Description
operation String

The operation name

dataSource.getOperation(operation)

Get an operation's metadata.

Arguments
Name Type Description
operation String

The operation name

dataSource.operations()

Get all operations.

dataSource.defineOperation(name, options, [Function} fn The function)

Define an operation to the data source

Arguments
Name Type Description
name String

The operation name

options Object

The options

[Function} fn The function

dataSource.isRelational()

Check if the backend is a relational DB

Returns
Name Type Description
result Boolean

dataSource.ready(obj, args)

Check if the data source is ready

Arguments
Name Type Description
obj
args
Returns
Name Type Description
result boolean

hiddenProperty(obj, key, value)

Define a hidden property

Arguments
Name Type Description
obj Object

The property owner

key String

The property name

value Mixed

The default value

defineReadonlyProp(obj, key, value)

Define readonly property on object

Arguments
Name Type Description
obj Object

The property owner

key String

The property name

value Mixed

The default value

exports.DataSource

Export public API

Data access mixins

DataAccessObject(data)

DAO class - base class for all persist objects provides common API to access any database connector. This class describes only abstract behavior layer, refer to lib/connectors/*.js to learn more about specific connector implementations

DataAccessObject mixes Inclusion classes methods

Arguments
Name Type Description
data Object

initial object data

DataAccessObject.create(data, callback(err,)

Create new instance of Model class, saved in database

Arguments
Name Type Description
data

[optional]

callback(err,

obj) callback called with arguments:

  • err (null or Error)
  • instance (null or Model)

DataAccessObject.upsert(data, [callback])

Update or insert a model instance

Arguments
Name Type Description
data Object

The model instance data

[callback] Function

The callback function

DataAccessObject.findOrCreate(query, data, cb)

Find one record, same as all, limited by 1 and return object, not collection, if not found, create using data provided as second argument

Arguments
Name Type Description
query Object

search conditions: {where: {test: 'me'}}.

data Object

object to create.

cb Function

callback called with (err, instance)

DataAccessObject.exists(id, cb)

Check whether a model instance exists in database

Arguments
Name Type Description
id id

identifier of object (primary key value)

cb Function

callbacl called with (err, exists: Bool)

DataAccessObject.findById(id, cb)

Find object by id

Arguments
Name Type Description
id

primary key value

cb Function

callback called with (err, instance)

DataAccessObject.find(params, callback)

Find all instances of Model, matched by query make sure you have marked as index: true fields for filter or sort

Arguments
Name Type Description
params Object

(optional)

  • where: Object { key: val, key2: {gt: 'val2'}}
  • include: String, Object or Array. See DataAccessObject.include documentation.
  • order: String
  • limit: Number
  • skip: Number
callback Function

(required) called with arguments:

  • err (null or Error)
  • Array of instances

DataAccessObject.findOne(params, cb)

Find one record, same as all, limited by 1 and return object, not collection

Arguments
Name Type Description
params Object

search conditions: {where: {test: 'me'}}

cb Function

callback called with (err, instance)

DataAccessObject.remove([where], [cb])

Destroy all matching records

Arguments
Name Type Description
[where] Object

An object that defines the criteria

[cb] Function

callback called with (err)

DataAccessObject.removeById(id, cb)

Destroy a record by id

Arguments
Name Type Description
id

The id value

cb Function

callback called with (err)

DataAccessObject.count(where, cb)

Return count of matched records

Arguments
Name Type Description
where Object

search conditions (optional)

cb Function

callback, called with (err, count)

dataAccessObject.save(options, callback(err,)

Save instance. When instance haven't id, create method called instead. Triggers: validate, save, update | create

Arguments
Name Type Description
options

{validate: true, throws: false} [optional]

callback(err,

obj)

dataAccessObject.updateAttribute(name, value, callback)

Update single attribute

equals to `updateAttributes({name: value}, cb)

Arguments
Name Type Description
name String

name of property

value Mixed

value of property

callback Function

callback called with (err, instance)

dataAccessObject.updateAttributes(data, callback)

Update set of attributes

this method performs validation before updating

Arguments
Name Type Description
data Object

data to update

callback Function

callback called with (err, instance)

dataAccessObject.reload(callback)

Reload object from persistence

Arguments
Name Type Description
callback Function

called with (err, instance) arguments

defineReadonlyProp(obj, key, value)

Define readonly property on object

Arguments
Name Type Description
obj Object
key String
value Mixed

DataAccessObject.scope()

Define scope

module.exports

Module exports class Model

DataAccessObject.prototype.remove

Delete object from persistence

Hookable()

Hooks mixins

module.exports

Module exports

Hookable.afterInitialize

List of hooks available

Inclusion.include(objects, {String},, cb)

Allows you to load relations of several objects and optimize numbers of requests.

Arguments
Name Type Description
objects Array

array of instances

{String},

{Object} or {Array} include - which relations you want to load.

cb Function

Callback called when relations are loaded Examples:

  • User.include(users, 'posts', function() {}); will load all users posts with only one additional request.
  • User.include(users, ['posts'], function() {}); // same
  • User.include(users, ['posts', 'passports'], function() {}); // will load all users posts and passports with two additional requests.
  • Passport.include(passports, {owner: 'posts'}, function() {}); // will load all passports owner (users), and all posts of each owner loaded
  • Passport.include(passports, {owner: ['posts', 'passports']}); // ...
  • Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...

module.exports

Include mixin for ./model.js

Relation.hasMany(anotherClass, params)

Declare hasMany relation

Arguments
Name Type Description
anotherClass Relation

class to has many

params Object

configuration {as:, foreignKey:}

Example

User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});

Relation.belongsTo(anotherClass, params)

Declare belongsTo relation

Arguments
Name Type Description
anotherClass Class

class to belong

params Object

configuration {as: 'propertyName', foreignKey: 'keyName'} Usage examples Suppose model Post have a belongsTo relationship with User (the author of the post). You could declare it this way: Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});

When a post is loaded, you can load the related author with: post.author(function(err, user) { // the user variable is your user object });

The related object is cached, so if later you try to get again the author, no additional request will be made. But there is an optional boolean parameter in first position that set whether or not you want to reload the cache: post.author(true, function(err, user) { // The user is reloaded, even if it was already cached. });

This optional parameter default value is false, so the related object will be loaded from cache if available.

validatePresence()

Presence validator

validateLength()

Length validator

validateNumericality()

Numericality validator

validateInclusion()

Inclusion validator

validateExclusion()

Exclusion validator

validateFormat()

Format validator

validateCustom()

Custom validator

validateUniqueness()

Uniqueness validator

validatable.isValid(callback)

This method performs validation, triggers validation hooks. Before validation obj.errors collection cleaned. Each validation can add errors to obj.errors collection. If collection is not blank, validation failed.

Arguments
Name Type Description
callback Function

called with (valid)

Example

ExpressJS controller: render user if valid, show flash otherwise user.isValid(function (valid) { if (valid) res.render({user: user}); else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users'); });

exports.ValidationError

Module exports

exports.Validatable

Validation mixins for model.js

Basically validation configurators is just class methods, which adds validations configs to AbstractClass._validations. Each of this validations run when obj.isValid() method called.

Each configurator can accept n params (n-1 field names and one config). Config is {Object} depends on specific validation, but all of them has one common part: message member. It can be just string, when only one situation possible, e.g. Post.validatesPresenceOf('title', { message: 'can not be blank' });

In more complicated cases it can be {Hash} of messages (for each case): User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});

Validatable.validatesPresenceOf

Validate presence. This validation fails when validated field is blank.

Default error message "can't be blank"

Example

with custom message Post.validatesPresenceOf('title', {message: 'Can not be blank'});

Validatable.validatesLengthOf

Validate length. Three kinds of validations: min, max, is.

Default error messages:

  • min: too short
  • max: too long
  • is: length is wrong
Example

length validations with custom error messages User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}}); User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});

Validatable.validatesNumericalityOf

Validate numericality.

Example
User.validatesNumericalityOf('age', { message: { number: '...' }});
User.validatesNumericalityOf('age', {int: true, message: { int: '...' }});

Default error messages:

  • number: is not a number
  • int: is not an integer

Validatable.validatesInclusionOf

Validate inclusion in set

Example
User.validatesInclusionOf('gender', {in: ['male', 'female']});
User.validatesInclusionOf('role', {
    in: ['admin', 'moderator', 'user'], message: 'is not allowed'
});

Default error message: is not included in the list

Validatable.validatesExclusionOf

Validate exclusion

Example

Company.validatesExclusionOf('domain', {in: ['www', 'admin']}); Default error message: is reserved

Validatable.validatesFormatOf

Validate format

Default error message: is invalid

Validatable.validate

Validate using custom validator

Default error message: is invalid

Example:

User.validate('name', customValidator, {message: 'Bad name'});
function customValidator(err) {
    if (this.name === 'bad') err();
});
var user = new User({name: 'Peter'});
user.isValid(); // true
user.name = 'bad';
user.isValid(); // false

Validatable.validateAsync

Validate using custom async validator

Default error message: is invalid

Example:

User.validateAsync('name', customValidator, {message: 'Bad name'});
function customValidator(err, done) {
    process.nextTick(function () {
        if (this.name === 'bad') err();
        done();
    });
});
var user = new User({name: 'Peter'});
user.isValid(); // false (because async validation setup)
user.isValid(function (isValid) {
    isValid; // true
})
user.name = 'bad';
user.isValid(); // false
user.isValid(function (isValid) {
    isValid; // false
})

Validatable.validatesUniquenessOf

Validate uniqueness

Default error message: is not unique

Base class for SQL connectors

LoopBack Definition Language Guide

LoopBack Definition Language (LDL) is simple DSL to define data models in JavaScript or plain JSON. With LoopBack, we often start with a model definition which describes the structure and types of data. The model establishes common knowledge of data in LoopBack.

Describing a simple model

Let's start with a simple example in plain JSON.

{
    "id": "number",
    "firstName": "string",
    "lastName": "string"
}

The model simply defines a user model that consists of three properties:

  • id - The user id. It's a number.
  • firstName - The first name. It's a string.
  • lastName - The last name. It's a string.

Each key in the JSON object defines a property in our model which will be cast to its associated type. The simplest form of a property definition is propertyName: type. The key is the name of the property and the value is the type of the property. We'll cover more advanced form later in this guide.

LDL supports a list of built-in types, including the basic types from JSON:

  • String
  • Number
  • Boolean
  • Array
  • Object

Note: The type name is case-insensitive, i.e., either "Number" or "number" can be used.

The same model can also be described in JavaScript code:

var UserDefinition = {
    id: Number,
    firstName: String,
    lastName: String
}

As we can see, the JavaScript version is less verbose as it doesn't require quotes for property names. The types are described using JavaScript constructors, for example, Number for "Number". String literals are also supported.

Now we have the definition of a model, how do we use it in LoopBack Node.js code? It's easy, LoopBack will build a JavaScript constructor (or class) for you.

Creating a model constructor

LDL compiles the model definition into a JavaScript constructor using ModelBuilder.define APIs. ModelBuilder is the basic factory to create model constructors.

ModelBuilder.define() method takes the following arguments:

  • name: The model name
  • properties: An object of property definitions
  • options: An object of options, optional

Here is an example,

var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;

// Create an instance of the ModelBuilder
var modelBuilder = new ModelBuilder();

// Describe the user model
var UserDefinition = {
    id: Number,
    firstName: String,
    lastName: String
}

// Compile the user model definition into a JavaScript constructor
var User = modelBuilder.define('User', UserDefinition);

// Create a new instance of User
var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});

console.log(user.id); // 1
console.log(user.firstName); // 'John'
console.log(user.lastName); // 'Smith'

That's it. Now you have a User constructor representing the user model.

At this point, the constructor only has a set of accessors to model properties. No behaviors have been introduced yet.

Adding logic to a model

Models describe the shape of data. To leverage the data, we'll add logic to the model for various purposes, such as:

  • Interact with the data store for CRUD
  • Add behavior around a model instance
  • Add service operations using the model as the context

There are a few ways to add methods to a model constructor:

Create the model constructor from a data source

A LoopBack data source injects methods on the model.

var DataSource = require('loopback-datasource-juggler').DataSource;
var ds = new DataSource('memory');

// Compile the user model definition into a JavaScript constructor
var User = ds.define('User', UserDefinition);

// Create a new instance of User
User.create({id: 1, firstName: 'John', lastName: 'Smith'}, function(err, user) {
    console.log(user); // The newly created user instance
    User.findById(1, function(err, user) {
        console.log(user); // The user instance for id 1
        user.firstName = 'John1'; // Change the property
        user.save(function(err, user) {
            console.log(user); // The modified user instance for id 1
        });
    };
});

Attach the model to a data source

A plain model constructor created from ModelBuilder can be attached a DataSource.

var DataSource = require('loopback-datasource-juggler').DataSource;
var ds = new DataSource('memory');

User.attachTo(ds); // The CRUD methods will be mixed into the User constructor

Manually add methods to the model constructor

Static methods can be added by declaring a function as a member of the model constructor. Within a class method, other class methods can be called using the model as usual.

// Define a static method
User.findByLastName = function(lastName, cb) {
    User.find({where: {lastName: lastName}, cb);
};

User.findByLastName('Smith', function(err, users) {
    console.log(users); // Print an array of user instances
});

Instance methods can be added to the prototype. Within instance methods, the model instance itself can be referenced with this keyword.

// Define a prototype method
User.prototype.getFullName = function () {
    return this.firstName + ' ' + this.lastName;
};

var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});
console.log(user.getFullName()); // 'John Smith'

Exploring advanced LDL features

As we mentioned before, a complete model definition is an object with three properties:

  • name: The model name
  • options: An object of options, optional
  • properties: An object of property definitions

Model level options

There are a set of options to control the model definition.

  • strict:
    • true: Only properties defined in the model are accepted. Use this mode if you want to make sure only predefined properties are accepted.
    • false: The model will be an open model. All properties are accepted, including the ones that not predefined with the model. This mode is useful if the mobile application just wants to store free form JSON data to a schema-less database such as MongoDB.
    • undefined: Default to false unless the data source is backed by a relational database such as Oracle or MySQL.
  • idInjection:
    • true: An id property will be added to the model automatically
    • false: No id property will be added to the model
  • plural: The plural form of the model name. If not present, it will be derived from the model name following English conventions.
  • Data source specific mappings The model can be decorated with connector-specific options to customize the mapping between the model and the connector. For example, we can define the corresponding schema/table names for Oracle as follows:

      {
        "name": "Location",
        "options": {
          "idInjection": false,
          "oracle": {
            "schema": "BLACKPOOL",
            "table": "LOCATION"
          }
        },
        ...
      }

Property definitions

A model consists of a list of properties. The basic example use propertyName: type to describe a property.

Properties can have options in addition to the type. LDL uses a JSON object to describe such properties, for example:

"id": {"type": "number", "id": true, "doc": "User ID"}

"firstName": {"type": "string", "required": true, "oracle": {"column": "FIRST_NAME", "type": "VARCHAR(32)"}}

Note "id": "number" is a short form of "id": {"type": "number"}.

Data types

LDL supports the following data types.

  • String/Text
  • Number
  • Date
  • Boolean
  • Buffer/Binary
  • Array
  • Any/Object/JSON
  • GeoPoint
Array types

LDL supports array types as follows:

  • {emails: [String]}
  • {"emails": ["String"]}
  • {emails: [{type: String, length: 64}]}
Object types

A model often has properties that consist of other properties. For example, the user model can have an address property that in turn has properties such as street, city, state, and zipCode.

LDL allows inline declaration of such properties, for example,

var UserModel = {
    firstName: String,
    lastName: String,
    address: {
        street: String,
        city: String,
        state: String,
        zipCode: String
    },
    ...
}

The value of the address is the definition of the address type, which can be also considered as an anonymous model.

If you intend to reuse the address model, we can define it independently and reference it in the user model. For example,

var AddressModel = {
    street: String,
    city: String,
    state: String,
    zipCode: String
};

var Address = ds.define('Address', AddressModel);

var UserModel = {
        firstName: String,
        lastName: String,
        address: 'Address',  // or address: Address
        ...
}

var User = ds.define('User', UserModel);

Note: The user model has to reference the Address constructor or the model name - 'Address'.

ID(s) for a model

A model representing data to be persisted in a database usually has one or more properties as an id to uniquely identify the model instance. For example, the user model can have user ids.

By default, if no id properties are defined and the idInjection of the model options is false, LDL will automatically add an id property to the model as follows:

id: {type: Number, generated: true, id: true}

To explicitly specify a property as id, LDL provides an id property for the option. The value can be true, false, or a number.

  • true: It's an id
  • false or any falsey values: It's not an id (default)
  • a positive number, such as 1 or 2: It's the index of the composite id

LDL supports the definition of a composite id that has more than one properties. For example,

var InventoryDefinition =
{
    productId: {type: String, id: 1},
    locationId: {type: String, id: 2},
    qty: Number
}

The composite id is (productId, locationId) for an inventory model.

Note: Composite ids are NOT supported as query parameters in REST APIs yet.

Property documentation

  • doc: Documentation of the property

Constraints

Constraints are modeled as options too, for example:

  • default: The default value of the property
  • required: Indicate if the property is required
  • pattern: A regular expression pattern that a string should match
  • min/max: The minimal and maximal value
  • length: The maximal length of a string

Conversion and formatting

Format conversions can also be declared as options, for example:

  • trim: Trim the string
  • lowercase: Convert the string to be lowercase
  • uppercase: Convert the string to be uppercase
  • format: Format a Date

Mapping

Data source specific mappings can be added to the property options, for example, to map a property to be a column in Oracle database table, you can use the following syntax:

"oracle": {"column": "FIRST_NAME", "type": "VARCHAR", "length": 32}

Relations between models

hasMany

A hasMany relation builds a one-to-many connection with another model. You'll often find this relation on the "other side" of a belongsTo relation. This relation indicates that each instance of the model has zero or more instances of another model. For example, in an application containing users and posts, a user has zero or more posts. For example,

// setup relationships
User.hasMany(Post,   {as: 'posts',  foreignKey: 'userId'});
// creates instance methods:
// user.posts(conds)
// user.posts.build(data) // like new Post({userId: user.id});
// user.posts.create(data) // build and save

Define all necessary stuff for one to many relation:

  • foreign key in many model
  • named scope in one model

Example:

var Book = db.define('Book');
var Chapter = db.define('Chapters');

// Style 1
Book.hasMany(Chapter, {as: 'chapters'});

// Style 2
Book.hasMany('chapters', {model: Chapter, foreignKey: 'chapter_id'});

Scope methods created on the base model by hasMany allows to build, create and query instances of other class. For example,

Book.create(function(err, book) {
    // using 'chapters' scope for build:
    var c = book.chapters.build({name: 'Chapter 1'});
    // same as:
    c = new Chapter({name: 'Chapter 1', bookId: book.id});
    // using 'chapters' scope for create:
    book.chapters.create();
    // same as:
    Chapter.create({bookId: book.id});

    // using scope for querying:
    book.chapters(function() {/* all chapters with bookId = book.id */ });
    book.chapters({where: {name: 'test'}, function(err, chapters) {
    // all chapters with bookId = book.id and name = 'test'
});

belongsTo

A belongsTo relation sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes users and posts, and each post can be written by exactly one user.

Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});

The code above basically says Post has a reference called author to User using the userId property of Post as the foreign key. Now we can access the author in one of the following styles:

post.author(callback); // Get the User object for the post author asynchronously
post.author(); // Get the User object for the post author synchronously
post.author(user) // Set the author to be the given user

hasAndBelongsToMany

A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes users and groups, with each group having many users and each user appearing in many groups, you could declare the models this way,

User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
user.groups(callback); // get groups of the user
user.groups.create(data, callback); // create a new group and connect it with the user
user.groups.add(group, callback); // connect an existing group with the user
user.groups.remove(group, callback); // remove the user from the group

Extend from a base model

LDL allows a new model to extend from an existing model. For example, Customer can extend from User as follows. The Customer model will inherit properties and methods from the User model.

var Customer = User.extend('customer', {
    accountId: String,
    vip: Boolean
});

Mix in model definitions

Some models share the common set of properties and logic around. LDL allows a model to mix in one or more other models. For example,

var TimeStamp = modelBuilder.define('TimeStamp', {created: Date, modified: Date});
var Group = modelBuilder.define('Group', {groups: [String]});
User.mixin(Group, TimeStamp);

LoopBack DataSource and Connector Guide

Overview

LoopBack is centered around models, which represent data and behaviors. The concept of DataSource is introduced to encapsulate business logic to exchange data between models and various data sources. Data sources are typically databases that provide create, retrieve, update, and delete (CRUD) functions. LoopBack also generalize other backend services, such as REST APIs, SOAP Web Services, and Storage Services, as data sources.

Data sources are backed by connectors which implement the data exchange logic using database drivers or other client APIs. In general, connectors are not used directly by application code. The DataSource class provides APIs to configure the underlying connector and exposes functions via DataSource or model classes.

model-datasource-connector

The diagram above illustrates the relationship between LoopBack Model, DataSource, and Connector.

  1. Define the Model using LoopBack Definition Language (LDL). Now we have a model definition in plain JSON or JavaScript object.

  2. Create an instance of ModelBuilder or DataSource. Please note that DataSource extends from ModelBuilder. ModelBuilder is responsible for compiling model definitions to JavaScript constructors representing model classes. DataSource inherits that function from ModelBuilder. In addition, DataSource adds behaviors to model classes by mixing in methods from the DataAccessObject into the model class.

  3. Use ModelBuilder or DataSource to build a JavaScript constructor (i.e, the model class) from the model definition. Model classes built from ModelBuilder can be later attached to a DataSource to receive the mixin of data access functions.

  4. As part of step 2, DataSource initializes the underlying Connector with a settings object which provides configurations to the connector instance. Connector collaborates with DataSource to define the functions as DataAccessObject to be mixed into the model class. The DataAccessObject consists of a list of static and prototype methods. It can be CRUD operations or other specific functions depending on the connector's capabilities.

LoopBack DataSource

DataSource is the unified interface for LoopBack applications to integrate with backend systems. It's a factory for data access logic around model classes. With the ability to plug in various connectors, DataSource provides the necessary abstraction to interact with databases or services to decouple the business logic from plumbing technologies.

Creating a DataSource

The DataSource constructor is available from loopback-datasource-juggler module:

var DataSource = require('loopback-datasource-juggler').DataSource;

DataSource constructor accepts two arguments:

  • connector: The name or instance of the connector module
  • settings: An object of properties to configure the connector

    var dataSource = new DataSource({

      connector: require('loopback-connector-mongodb'),
      host: 'localhost',
      port: 27017,
      database: 'mydb'

    });

connector

The connector argument passed the DataSource constructor can be one of the following:

  • The connector module from require(connectorName)
  • The full name of the connector module, such as 'loopback-connector-oracle'
  • The short name of the connector module, such as 'oracle', which will be converted to 'loopback-connector-'
  • A local module under ./connectors/ folder
var ds1 = new DataSource('memory');
var ds2 = new DataSource('loopback-connector-mongodb'));
var ds3 = new DataSource(require('loopback-connector-oracle'));

Note: LoopBack provides a built-in connector named as memory to use in-memory store for CRUD operations.

settings

The settings argument configures the connector. Settings object format and defaults depends on specific connector, but common fields are:

  • host: Database host
  • port: Database port
  • username: Username to connect to database
  • password: Password to connect to database
  • database: Database name
  • debug: Turn on verbose mode to debug db queries and lifecycle

For connector-specific settings refer to connector's readme file.

Creating a Model

DataSource extends from ModelBuilder, which is a factory for plain model classes that only have properties. DataSource connected with specific databases or other backend systems using Connector.

var DataSource = require('loopback-datasource-juggler').DataSource;
var ds = new DataSource('memory');

var User = ds.define('User', {
    name: String,
    bio: String,
    approved: Boolean,
    joinedAt: Date,
    age: Number
});

All model classes within single data source shares same connector type and one database connection or connection pool. But it's possible to use more than one data source to connect with different databases.

Alternatively, a plain model constructor created from ModelBuilder can be attached a DataSource.

var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
var builder = new ModelBuilder();

var User = builder.define('User', {
        name: String,
        bio: String,
        approved: Boolean,
        joinedAt: Date,
        age: Number
});

var DataSource = require('loopback-datasource-juggler').DataSource;
var ds = new DataSource('memory');

User.attachTo(ds); // The CRUD methods will be mixed into the User constructor

More DataSource Features

In addition to data access functions mixed into the model class, DataSource also provides APIs to interact with the underlying backend system.

Discovering model definitions from the data source

Some connectors provide discovery capability so that we can use DataSource to discover model definitions from existing database schema.

The following APIs allow UI or code to discover database schema definitions that can be used to build LoopBack models.

// List database tables and/or views
ds.discoverModelDefinitions({views: true, limit: 20}, cb);

// List database columns for a given table/view
ds.discoverModelProperties('PRODUCT', cb);
ds.discoverModelProperties('INVENTORY_VIEW', {owner: 'STRONGLOOP'}, cb);

// List primary keys for a given table
ds.discoverPrimaryKeys('INVENTORY',  cb);

// List foreign keys for a given table
ds.discoverForeignKeys('INVENTORY',  cb);

// List foreign keys that reference the primary key of the given table
ds.discoverExportedForeignKeys('PRODUCT',  cb);

// Create a model definition by discovering the given table
ds.discoverSchema(table, {owner: 'STRONGLOOP'}, cb);

You can also discover and build model classes in one shot:

// Start with INVENTORY table and follow the primary/foreign relationships to discover associated tables
ds.discoverAndBuildModels('INVENTORY', {visited: {}, associations: true}, function (err, models) {

    // Now we have an object of models keyed by the model name
    // Find the 1st record for Inventory
    models.Inventory.findOne({}, function (err, inv) {
        if(err) {
            console.error(err);
            return;
        }
        console.log("\nInventory: ", inv);

        // Follow the product relation to get information about the product
        inv.product(function (err, prod) {
            console.log("\nProduct: ", prod);
            console.log("\n ------------- ");
        });
    });
});

In addition to the asynchronous APIs, DataSource also provides the synchronous ones. Please refer to the DataSource API references.

Synchronizing model definitions against the data source

DataSource instance have two methods for updating db structure: automigrate and autoupdate for relational databases.

The automigrate method drop table (if exists) and create it again, autoupdate method generates ALTER TABLE query. Both method accepts an optional array of model names and a callback function to be called when migration/update done. If the models argument is not present, all models are checked.

In the following example, we create first version of the CustomerTest model, use automigrate to create the database table, redefine the model with second version, and use autoupdate to alter the database table.

// Create the 1st version of 'CustomerTest'
ds.createModel(schema_v1.name, schema_v1.properties, schema_v1.options);

// Create DB table for the model
ds.automigrate(schema_v1.name, function () {

    // Discover the model properties from DB table
    ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) {
        console.log(props);

        // Redefine the 2nd version of 'CustomerTest'
        ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options);

        // Alter DB table
        ds.autoupdate(schema_v2.name, function (err, result) {
            ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) {
                console.log(props);
            });
        });
    });
});

To check if any db changes required use isActual method. It accepts and a callback argument, which receive boolean value depending on db state:

  • false if db structure outdated
  • true when dataSource and db is in sync
dataSource.isActual(models, function(err, actual) {
    if (!actual) {
        dataSource.autoupdate(models, function(err, result) {
            ...
        });
    }
});

LoopBack Connector

Connectors implement the logic to integrate with specific backend systems, such as databases or REST services.

LoopBack Connector Modules

Type Package Name
Memory Built-in
MongoDB loopback-connector-mongodb
Oracle loopback-connector-oracle
REST loopback-connector-rest
MySQL loopback-connector-mysql

Interaction between DataSource and Connector

Initializing connector

The connector module can export an initialize function to be called by the owning DataSource instance.

exports.initialize = function (dataSource, postInit) {

    var settings = dataSource.settings || {}; // The settings is passed in from the dataSource

    var connector = new MyConnector(settings); // Construct the connector instance
    dataSource.connector = connector; // Attach connector to dataSource
    connector.dataSource = dataSource; // Hold a reference to dataSource
    ...
};

The DataSource calls the initialize method with itself and an optional postInit callback function. The connector receives the settings from the dataSource argument and use it to configure connections to backend systems.

Please note connector and dataSource set up a reference to each other.

Upon initialization, the connector might connect to database automatically. Once connection established dataSource object emit 'connected' event, and set connected flag to true, but it is not necessary to wait for 'connected' event because all queries cached and executed when dataSource emit 'connected' event.

To disconnect from database server call dataSource.disconnect method. This call is forwarded to the connector if the connector have ability to connect/disconnect.

Accessing data/services

The connector instance can have an optional property named as DataAccessObject that provides static and prototype methods to be mixed into the model constructor. DataSource has a built-in DataAccessObject to support CRUD operations. The connector can choose to use the CRUD DataAccessObject or define its own.

When a method is invoked from the model class or instance, it's delegated to the DataAccessObject which is backed by the connector.

For example,

User.create() --> dataSource.connector.create() --> Oracle.prototype.create()

Building your own connectors

LoopBack connectors provide access to backend systems including databases, REST APIs and other services. Connectors are not used directly by application code. We create a DataSource to interact with the connector.

For example,

var DataSource = require('loopback-datasource-juggler').DataSource;
var oracleConnector = require('loopback-connector-oracle');

var ds = new DataSource(oracleConnector, {
    host : 'localhost',
    database : 'XE',
    username : 'username',
    password : 'password',
    debug : true
});

Implementing a generic connector

A connector module can implement the following methods to interact with the data source.

exports.initialize = function (dataSource, postInit) {

    var settings = dataSource.settings || {}; // The settings is passed in from the dataSource

    var connector = new MyConnector(settings); // Construct the connector instance
    dataSource.connector = connector; // Attach connector to dataSource
    connector.dataSource = dataSource; // Hold a reference to dataSource

    /**
     * Connector instance can have an optional property named as DataAccessObject that provides
     * static and prototype methods to be mixed into the model constructor. The property can be defined
     * on the prototype.
     */
    connector.DataAccessObject = function {};

    /**
     * Connector instance can have an optional function to be called to handle data model definitions.
     * The function can be defined on the prototype too.
     * @param model The name of the model
     * @param properties An object for property definitions keyed by propery names
     * @param settings An object for the model settings
     */
    connector.define = function(model, properties, settings) {
        ...
    };

    connector.connect(..., postInit); // Run some async code for initialization
    // process.nextTick(postInit);
}

Another way is to directly export the connection function which takes a settings object.

module.exports = function(settings) {
    ...
}

Implementing a CRUD connector

To support CRUD operations for a model class that is attached to the dataSource/connector, the connector needs to provide the following functions:

/**
 * Create a new model instance
 */
CRUDConnector.prototype.create = function (model, data, callback) {
};

/**
 * Save a model instance
 */
CRUDConnector.prototype.save = function (model, data, callback) {
};

/**
 * Check if a model instance exists by id
 */
CRUDConnector.prototype.exists = function (model, id, callback) {
};

/**
 * Find a model instance by id
 */
CRUDConnector.prototype.find = function find(model, id, callback) {
};

/**
 * Update a model instance or create a new model instance if it doesn't exist
 */
CRUDConnector.prototype.updateOrCreate = function updateOrCreate(model, data, callback) {
};

/**
 * Delete a model instance by id
 */
CRUDConnector.prototype.destroy = function destroy(model, id, callback) {
};

/**
 * Query model instances by the filter
 */
CRUDConnector.prototype.all = function all(model, filter, callback) {
};

/**
 * Delete all model instances
 */
CRUDConnector.prototype.destroyAll = function destroyAll(model, callback) {
};

/**
 * Count the model instances by the where criteria
 */
CRUDConnector.prototype.count = function count(model, callback, where) {
};

/**
 * Update the attributes for a model instance by id
 */
CRUDConnector.prototype.updateAttributes = function updateAttrs(model, id, data, callback) {
};