Strongloop Model

strongloop

http://docs.strongloop.com/display/public/LB/Exposing+models+over+REST - done reading
http://docs.strongloop.com/display/LB/Exposing+models+over+REST
http://docs.strongloop.com/display/LB/Database+discovery+API
http://docs.strongloop.com/display/LB/Model+definition+JSON+file#ModeldefinitionJSONfile-Top-levelproperties
http://docs.strongloop.com/display/LB/Adding+application+logic
http://docs.strongloop.com/display/LB/Built-in+models+REST+API
http://docs.strongloop.com/display/LB/PersistedModel+REST+API
http://docs.strongloop.com/display/DOC/Model+definition+reference
http://docs.strongloop.com/display/LB/Working+with+models
http://docs.strongloop.com/display/LB/Model+definition+JSON+file
http://docs.strongloop.com/display/LB/model-config.json
http://docs.strongloop.com/display/LB/Model+hooks
http://docs.strongloop.com/display/LB/Creating+models+from+unstructured+data
http://docs.strongloop.com/display/LB/Discovering+models+from+relational+databases
http://docs.strongloop.com/display/LB/Creating+a+database+schema+from+models

How to add a model?

$ slc loopback:model

This command will prompt you to enter the name of the model, the data-source to attach the model to, whether you want to expose the model as a REST API. This command also allow you to defined various properties / fields of the model. After completing the prompts, a new model (a .json file) will be added to the /common/models folder

How can we define model relationships?

$ slc loopback:relation

A coffee shop has many reviews; No through model and no foreign key.

? Select the model to create the relationship from: CoffeeShop
? Relation type: has many
? Choose a model to create a relationship with: Review
? Enter the property name for the relation: reviews
? Optionally enter a custom foreign key:
? Require a through model? No

A coffee shop has many reviewers; No through model and no foreign key.

? Select the model to create the relationship from: CoffeeShop
? Relation type: has many
? Choose a model to create a relationship with: Reviewer
? Enter the property name for the relation: reviewers
? Optionally enter a custom foreign key:
? Require a through model? No

A review belongs to a coffee shop; No foreign key.

? Select the model to create the relationship from: Review
? Relation type: belongs to
? Choose a model to create a relationship with: CoffeeShop
? Enter the property name for the relation: coffeeShop
? Optionally enter a custom foreign key:

A review belongs to a reviewer; foreign key is publisherId.

? Select the model to create the relationship from: Review
? Relation type: belongs to
? Choose a model to create a relationship with: Reviewer
? Enter the property name for the relation: reviewer
? Optionally enter a custom foreign key: publisherId

A reviewer has many reviews; foreign key is publisherId.

? Select the model to create the relationship from: Reviewer
? Relation type: has many
? Choose a model to create a relationship with: Review
? Enter the property name for the relation: reviews
? Optionally enter a custom foreign key: publisherId
? Require a through model? No

How can we add a new property or field to an existing model?

slc loopback:property

What are the two files generated by "slc loopback:model"?

The LoopBack model generator (slc loopback:model) always creates two files in /common/models for each model:

  1. A JSON file named <model-name>.json describing its properties
  2. A JavaScript file named <model-name>.js where you can extend and override model behavior. You can extend the model programmatically; for example to add remote methods. See Adding application logic for more information.

How can we get a reference to a model?

The way that you get a reference (or "handle") to a model in JavaScript code depends on where the code is. In the model JavaScript file (for example) the models is passed into the top-level function, so the model object is available directly; for example:

module.exports = function(Customer) {
  Customer.create( ... );  // Customer object is available 
  ...

In a boot script, use the app.models object to get a reference to any model; for example:

module.exports = function(app) {
  var User = app.models.user;
  var Role = app.models.Role;
  var RoleMapping = app.models.RoleMapping;
  var Team = app.models.Team;
  ...

How can we expose or hide a model over REST?

To expose a model over REST, set the public property to true in /server/model-config.json:

...
  "Role": {
    "dataSource": "db",
    "public": false
  },
...

If you don't want to expose certain CRUD operations, you can easily hide them by calling disableRemoteMethod() on the model. For example, following the previous example, by convention custom model code would go in the file common/models/location.js. You would add the following lines to "hide" one of the predefined remote methods:

// common/models/locations.js
var isStatic = true;
MyModel.disableRemoteMethod('deleteById', isStatic);

Now the deleteById() operation and the corresponding REST endpoint will not be publicly available. For a method on the prototype object, such as updateAttributes():

// common/models/locations.js
var isStatic = false;
MyModel.disableRemoteMethod('updateAttributes', isStatic);

To disable a REST endpoints for related model methods, use disableRemoteMethod(). For more information, see Restricting access to related models. For example, if there are post and tag models, where a post hasMany tags, add the following code to /common/models/post.js to disable the remote methods for the related model and the corresponding REST endpoints:

module.exports = function(Post) {
  Post.disableRemoteMethod('__get__tags', false);
  Post.disableRemoteMethod('__create__tags', false);
  Post.disableRemoteMethod('__delete__tags', false);
};

How can we automatically discover and expose database tables?

Connectors for Oracle, MySQL, PostgreSQL, and SQL Server provide APIs that enable you to "discover" model definition information from existing databases, including schema information such as column names and datatypes and relation information such as primary and foreign keys.

var ds = require('../data-sources/db.js')('oracle');
// Discover and build models from INVENTORY table
ds.discoverAndBuildModels('INVENTORY',
    {visited: {}, owner: 'LOOPBACK', associations: true},
    function (err, models) {
        models.Inventory.findOne({}, function (err, inv) {
            console.log("Inventory: ", inv);
            inv.product(function (err, prod) {
                console.log("Product: ", prod);
            });
        });
    }
)

Consider an Oracle database. First, the code sets up the Oracle data source. Then the call to discoverAndBuildModels() creates models from the database tables. Calling it with the associations: true option makes the discovery follow primary/foreign key relations.

// server/bin/script.js
var loopback = require('loopback');
var ds = loopback.createDataSource('oracle', {
  "host": "demo.strongloop.com",
  "port": 1521,
  "database": "XE",
  "username": "demo",
  "password": "L00pBack"
});

// Discover and build models from INVENTORY table
ds.discoverAndBuildModels('INVENTORY', {visited: {}, associations: true},
function (err, models) {
  // Now we have a list of models keyed by the model name
  // Find the first record from the inventory
  models.Inventory.findOne({}, function (err, inv) {
    if(err) {
      console.error(err);
      return;
    }
    console.log("\nInventory: ", inv);
    // Navigate to the product model
    inv.product(function (err, prod) {
      console.log("\nProduct: ", prod);
      console.log("\n ------------- ");
    });
  });
});
// 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);

What are the built-in models generated by StrongLoop / Loopback?

  1. Application model - contains metadata for a client application that has its own identity and associated configuration with the LoopBack server.
  2. User model - register and authenticate users of your app locally or against third-party services.
  3. Access control models - ACL, AccessToken, Scope, Role, and RoleMapping models for controlling access to applications, resources, and methods.
  4. Email model - send emails to your app users using SMTP or third-party services.

The built-in models (except for Email) extend PersistedModel, so they have automatically have a full complement of create, update, and delete (CRUD) operations.

How can we run queries?

Node:

MyProduct.find({
    where: {price: {lt: 100}},
    order: 'price ASC',
    limit: 3
}, function(err, products) {
    ...
});

Angular:

$scope.products = MyProduct.find({
  filter: {
    where: {price: {lt: 100}},
    order: 'price ASC',
    limit: 3
  }
});

Loopback:

MyProduct.find({
  where: {price: {lt: 100}},
  order: 'price ASC',
  limit: 3
}, function(err, products) {
  console.log(products); // => Array of MyProduct
});

How to connect a model to a data-source?

Now you've created a MySQL data source and you have a CoffeeShop model; you just need to connect them. LoopBack applications use the model-config.json file to link models to data sources. Edit /server/model-config.json and look for the CoffeeShop entry:

...
"CoffeeShop": {
  "dataSource": "mysqlDs",
  "public": true
}
...

Change the dataSource property from db to mysqlDs (the name of the data-source that you just created). This attaches the CoffeeShop model to the MySQL datasource you just created and configured.

How to create the database tables from the models?

Now you have a CoffeeShop model in LoopBack, how do you create the corresponding table in MySQL database?

You could try executing some SQL statements directly…but LoopBack provides a Node API to do it for you automatically using a process called auto-migration. For more information, see Creating a database schema from models.

The loopback-getting-started module contains the create-sample-models.js script to demonstrate auto-migration. If you've been following along from the beginning (and didn't just clone this module), then you'll need to copy it from below or from GitHub . Put it in the application's /server/boot directory so it will get executed when the application starts.

The auto-migration script below is an example of a boot script that LoopBack executes when an application initially starts up. Use boot scripts for initialization and to perform any other logic your application needs to perform when it starts. See Defining boot scripts for more information.

module.exports = function(app) {
  app.dataSources.mysqlDs.automigrate('CoffeeShop', function(err) {
    if (err) throw err;

    app.models.CoffeeShop.create([
      {name: 'Bel Cafe', city: 'Vancouver'},
      {name: 'Three Bees Coffee House', city: 'San Mateo'},
      {name: 'Caffe Artigiano', city: 'Vancouver'},
    ], function(err, coffeeShops) {
      if (err) throw err;

      console.log('Models created: \n', coffeeShops);
    });
  });
};
var async = require('async');
module.exports = function(app) {
  //data sources
  var mongoDs = app.dataSources.mongoDs;
  var mysqlDs = app.dataSources.mysqlDs;
  //create all models
  async.parallel({
    reviewers: async.apply(createReviewers),
    coffeeShops: async.apply(createCoffeeShops),
  }, function(err, results) {
    if (err) throw err;
    createReviews(results.reviewers, results.coffeeShops, function(err) {
      console.log('> models created sucessfully');
    });
  });
  //create reviewers
  function createReviewers(cb) {
    mongoDs.automigrate('Reviewer', function(err) {
      if (err) return cb(err);
      var Reviewer = app.models.Reviewer;
      Reviewer.create([
        {email: 'foo@bar.com', password: 'foobar'},
        {email: 'john@doe.com', password: 'johndoe'},
        {email: 'jane@doe.com', password: 'janedoe'}
      ], cb);
    });
  }
  //create coffee shops
  function createCoffeeShops(cb) {
    mysqlDs.automigrate('CoffeeShop', function(err) {
      if (err) return cb(err);
      var CoffeeShop = app.models.CoffeeShop;
      CoffeeShop.create([
        {name: 'Bel Cafe', city: 'Vancouver'},
        {name: 'Three Bees Coffee House', city: 'San Mateo'},
        {name: 'Caffe Artigiano', city: 'Vancouver'},
      ], cb);
    });
  }
  //create reviews
  function createReviews(reviewers, coffeeShops, cb) {
    mongoDs.automigrate('Review', function(err) {
      if (err) return cb(err);
      var Review = app.models.Review;
      var DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
      Review.create([
        {
          date: Date.now() - (DAY_IN_MILLISECONDS * 4),
          rating: 5,
          comments: 'A very good coffee shop.',
          publisherId: reviewers[0].id,
          coffeeShopId: coffeeShops[0].id,
        },
        {
          date: Date.now() - (DAY_IN_MILLISECONDS * 3),
          rating: 5,
          comments: 'Quite pleasant.',
          publisherId: reviewers[1].id,
          coffeeShopId: coffeeShops[0].id,
        },
        {
          date: Date.now() - (DAY_IN_MILLISECONDS * 2),
          rating: 4,
          comments: 'It was ok.',
          publisherId: reviewers[1].id,
          coffeeShopId: coffeeShops[1].id,
        },
        {
          date: Date.now() - (DAY_IN_MILLISECONDS),
          rating: 4,
          comments: 'I go here everyday.',
          publisherId: reviewers[2].id,
          coffeeShopId: reviewers[2].id,
        }
      ], cb);
    });
  }
};

This will save some test data to the data source.

What do we have to be aware of when we use auto-migration?

automigrate() first drops tables before trying to create new ones, it won't create duplicate tables.

How can we specify default value for a property?

One way to set a default value for a property is to set the "default" property in the models JSON file. See the General property properties section.

*How can we create models from unstructured data?**

For unstructured data such as that in NoSQL databases and REST services, you can create models using instance introspection. Instance introspection creates a model from a single model instance using buildModelFromInstance().

// server/boot/script.js
module.exports = function(app) {
  var db = app.dataSources.db;

  // Instance JSON document
  var user = {
    name: 'Joe',
    age: 30,
    birthday: new Date(),
    vip: true,
    address: {
      street: '1 Main St',
      city: 'San Jose',
      state: 'CA',
      zipcode: '95131',
      country: 'US'
    },
    friends: ['John', 'Mary'],
    emails: [
      {label: 'work', id: 'x@sample.com'},
      {label: 'home', id: 'x@home.com'}
    ],
    tags: []
  };

  // Create a model from the user instance
  var User = db.buildModelFromInstance('User', user, {idInjection: true});

  // Use the model for CRUD
  var obj = new User(user);

  console.log(obj.toObject());

  User.create(user, function (err, u1) {
    console.log('Created: ', u1.toObject());
    User.findById(u1.id, function (err, u2) {
      console.log('Found: ', u2.toObject());
    });
  });
});

How can we extend a model?

You can make a model extend or "inherit from" an existing model, either one of the built-in models such as User.

{                                                                                                           
  "name": "Customer",
  "base": "User",
  "idInjection": false,
...

In general, you can extend any model this way, not just the built-in models. Currently you cannot modify a built-in model's required properties. If you need to do this, then create your own custom model as a replacement instead. You can create custom models that extend from a single base cutom model. For example, to define a model called MyModel that extends from a custom model you defined called mMyBaseModel, create MyModel using slc loopback:model, then edit the JSON file common/models/MyModel.json as follows:

{
  "name": "Example",
  "base": "MyBaseModel",
}

You can add new properties when you extend a model, for example:

{
   "name": "Customer",
   "base": "User",
   "properties": {
      "favoriteMovie": {
        "type": "string"
      }
   }
}

Here are some of the most important settings you can customize:

  • plural - set to a custom string value to use, instead of the default standard plural form.
  • strict - set to true to make the model accept instances that have the predefined set of properties. False by default
  • idInjection - Whether to automatically add an id property to the model. True by default.
  • http.path - customized HTTP path of REST endpoints.

How can we customize a model using JavaScript code?

The basic way to extend a model programmatically is to edit the model's JavaScript file in the common/models/ directory. For example, a "customer" model will have a /common/models/customer.js file (if you create the model using slc loopback:model, the Model generator). The script is executed immediately after the model is defined. Treat the script as part of the model definition; use it for model configuration and registration. You could also add model relationships, complex validations, or default functions for certain properties: Basically, anything you cannot do in JSON. However, note that at this point the script doesn't have access to the app instance.

You can also extend a model by adding a remote method or a model hook. If you don't want to expose the method over REST, then just omit the remoteMethod() call. See Adding application logic for more information on customizing a model using JavaScript.

How can we change the implementation of a built-in method?

When you attach a model to a persistent data source, it becomes a persisted model that extends PersistedModel, and LoopBack automatically adds a set of built-in methods for CRUD operations. In some cases, you might want to change the implementation; use a JavaScript file in the /server/boot directory to do this. For example, the following code shows how to reimplement Note.find() to override the built-in find() method.

module.exports = function(app) {
  var Note = app.models.Note;
  var find = Note.find;
  var cache = {};

  Note.find = function(filter, cb) {
    var key = '';
    if(filter) {
      key = JSON.stringify(filter);
    }
    var cachedResults = cache[key];
    if(cachedResults) {
      console.log('serving from cache');
      process.nextTick(function() {
        cb(null, cachedResults);
      });
    } else {
      console.log('serving from db');
      find.call(Note, function(err, results) {
        if(!err) {
          cache[key] = results;
        }
        cb(err, results);
      });;
    }
  }
}

How can we specify that a particular model should use a particular datasource?

"book": {
   "properties": {
     ...
   },
   "public": true,
   "dataSource": "corp1",
   "plural": "books"
 }

How can we hide the REST endpoint of a particular model?

"book": {
   "properties": {
     ...
   },
   "public": true,
   "dataSource": "corp1",
   "plural": "books"
 }

LoopBack models automaticaly have a standard set of HTTP endpoints that provide REST APIs for create, read, update, and delete (CRUD) operations on model data. The public property in model-config.json specifies whether to expose the model's REST APIs, for example:

// server/model-config.json
...
  "MyModel": {
    "public": true,
    "dataSource": "db"
  },
...

To "hide" the model's REST API, simply change public to false.

How can we manually expose a model over REST?

By default, scaffolded applications expose models over REST using the loopback.rest router. If your application is scaffolded using slc loopback, LoopBack will automatically set up REST middleware and register public models. You don't need to do anything additional. To manually expose a model over REST with the loopback.rest router, use the following code, for example:

// server/server.js
var app = loopback();
app.use(loopback.rest());

// Expose the `Product` model
app.model(Product);

After this, the Product model will have create, read, update, and delete (CRUD) functions working remotely from mobile. At this point, the model is schema-less and the data are not checked. You can then view generated REST documentation at http://localhost:3000/explorer.

How can we disable the API Explorer?

LoopBack API Explorer is great when you're developing your application, but for security reasons you may not want to expose it in production. To disable API Explorer entirely, if you created your application with the Application generator, simply delete or rename server/boot/explorer.js (perhaps using your build/deploy tool).

How can we hide a particular method or REST endpoint?

If you don't want to expose certain CRUD operations, you can easily hide them by calling disableRemoteMethod() on the model. For example, following the previous example, by convention custom model code would go in the file common/models/location.js. You would add the following lines to "hide" one of the predefined remote methods:

// common/models/location.js
var isStatic = true;
MyModel.disableRemoteMethod('deleteById', isStatic);

Now the deleteById() operation and the corresponding REST endpoint will not be publicly available. For a method on the prototype object, such as updateAttributes():

// common/models/location.js
var isStatic = false;
MyModel.disableRemoteMethod('updateAttributes', isStatic);

How can we hide endpoints for related models?

To disable a REST endpoints for related model methods, use disableRemoteMethod(). For more information, see Restricting access to related models.

For example, if there are post and tag models, where a post hasMany tags, add the following code to /common/models/post.js to disable the remote methods for the related model and the corresponding REST endpoints:

// common/models/model.js

module.exports = function(Post) {
  Post.disableRemoteMethod('__get__tags', false);
  Post.disableRemoteMethod('__create__tags', false);
  Post.disableRemoteMethod('__delete__tags', false);
};
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License