Would you like to contribute to the Titanium docs? To get started, sign up for an account on the Appcelerator Wiki and sign our CLA.

Skip to end of metadata
Go to start of metadata

Introduction

This guides covers the basics for creating Models. Models are data stored in either server memory or a backend service, such as ArrowDB or a MySQL database, using an Arrow Connector. Models are accessed like standard REST objects using predefined endpoints that Arrow automatically generates by default.  You can either create a model by defining your own schema, use an existing model defined by a connector, modify an existing model by either extending or reducing it, or create a composite model by joining two or more models together.

To programmatically create Models, see the Arrow.Model API reference.

Model definition

Place all Model files in the models folder.  You can only declare one model per file.  A Model file is a JavaScript file, which:

  1. Loads the arrow module
  2. Calls the module's createModel('name', schema)  method (or another Model method), passing in the name of the model as the first parameter and an object defining the model schema as the second parameter
  3. Exports the defined endpoint using the module.exports variable

Set the following keys in the object passed to the createModel() method to define the model:

NameRequiredDescription
fieldstrueAn object that represents the model’s schema define as key-value pairs. The key is the name of the field and the value is the fields object. See the next table for details.
connectortrueConnector to which the model is bound (string). Each model can only have one connector. Connectors are responsible for reading and writing data from/to their data source.
documentedfalseSince Release 5.0.0. Determines whether to generate API documentation (true) or not (false). The default value is true.
metadatafalseUsed to provide connector specific configuration (e.g., mapping the model to a specific database table for the MySQL connector or defining the join properties).
autogenfalseUsed to determine whether to generate API endpoints directly from the model. The default value is true. If the endpoint is auto-generated, you do not need to create an API endpoint definition.
actionsfalseAn array of data operations supported by the model. The valid values are: create, read, update, and delete. By default, all are supported by the model.
pluralfalseA string used as the property name when your API endpoint returns an array. By default, the plural value is the plural of the model name. For example, if your model is named car, the default plural would be cars. Note: this value can be set on an API or a model.
singularfalseA string used as the property name when your API endpoint returns a single record. By default, the singular value is the name of the model. Note: this value can be set on an API or a model.
beforefalseOne or more blocks to be executed before the request. Blocks are referenced by their name property. If you want to execute multiple blocks, you should specify them as an array of block names. If multiple blocks are specified, they are executed in the order specified.
afterfalseOne or more blocks to be executed after the request. Blocks are referenced by their name property. If you want to execute multiple blocks, you should specify them as an array of block names. If multiple blocks are specified, they are executed in the order specified.

Field definition

The fields property (mentioned above) supports a number of sub-properties as well. The table below outlines these properties.

NameRequiredDescription
typetrueThe field primitive type plus others (e.g., ‘string’, 'number', 'boolean', 'object', 'array', 'date'). Type can be any valid JavaScript primitive type. Type can be specified as a string (e.g., ‘string’) or by the type class (e.g., String).
requiredfalseSpecifies whether the field is required. The default value is false.
validatorfalseA function or regular expression that validates the value of the field. The function is passed the data to validate and should return either null or undefined if the validation succeeds. Any other return value means the validation failed, and the return value will be used in the exception message. If a regular expression is used, it should evaluate to either true or false.
namefalseUsed if the model field name is different than the field name in the connector’s model or the underlying data source for the field name. For example, if my model field is first_name and the column in a MySQL database is fname, the value of the name property should be 'fname'.
defaultfalseThe default value for the field.
descriptionfalseThe description of the field (used for API documentation).
readonlyfalseEither true or false. If true the field will be read-only and any attempt to write the field value will fail.
maxlengthfalseThe max length of the field (specified as an integer)
getfalseA function used to set the value of a property that will be sent to the client. This property is useful if you want to define a custom field where the value is derived.
setfalseA function used to set the value of a property that will be sent to the connector.
customfalseThis property should be specified and set to true if you are defining a custom field. A custom field is one that does not exist in the underlying data source for the connector you specified.
modelfalseModel name of the field property. This is either the logical name of a custom model or a connector model name in the form connector/model_name (e.g., appc.mysql/employee)

Model schema example

The example below creates the car model with the specified schema.  The car models will be stored in ArrowDB as CustomObjects.  Since the autogen property was not set to false, Arrow automatically generates the pre-define endpoints for the client to access the car models using the <SEVER_ADDRESS>/ api/car endpoints.

Modify an existing model

Besides creating a fully defined model, you can modify an existing model either by reducing or extending it.

Reduce a model

A reduced model is an existing model where you only use specific fields from it.  To create a reduced model, follow the same procedure when creating a regular model except invoke the module's Model.reduce() method instead of the createModel() method.  Pass the model you want to reduce as the first parameter, the name of the new model as the second parameter, and the new model schema as the last parameter.

Example

The Model file below extracts three fields from the employee table of the appc.mysql connector, indicated by the appc.mysql/employee parameter, and renames the fields for the baseEmp model, for example, email_address in the MySQL employee table maps to email in the new model.

models/baseemp.js

Extend a model

An extended model is an existing model where you modify the fields or add more fields.  To create an extended model, follow the same procedure when creating a regular model except invoke the module's Model.extend() method instead of the createModel() method. Pass the model you want to extend as the first parameter, the name of the new model as the second parameter, and the new model schema as the last parameter.

Example

The Model below extends the employee model by adding the headquarters field to it.

models/fullemp.js

Create a composite model

Composite models allow you to create a single model that is composed of one or more models based on the same or different connectors. Composite models can be joined together via a common set of properties, such as primary keys or foreign keys, or they can have no properties in common at all. The power of composite models is that you can represent multiple data sources and entities as a single API endpoint, which is ideal for many mobile use cases.

To create a composite model, follow the same procedure when creating a regular model except the connector property must be set to appc.composite, each field in the definition object must specify the model property to indicate which model the field originates from, and the metadata property must define the join operation to combine the models or leave it undefined to perform no join operations.

The following terms are used to refer to models:

  • Model definition: The composite model which is being created
  • Main model: The main source of data for the composite model. This is the left table in SQL terminology. It is implicitly defined.
  • Secondary model: Any model other than the main model. This will be the right table in SQL terminology.

The composite connector can either perform a left join or inner join:

  • left join: all records from the main model are returned regardless if it found a match in the secondary models
  • inner join: only records that match both models are returned

The composite connector can also perform either a one-to-one join or one-to-many join:

  • one-to-one: only one record from the secondary model matches a record in the main model
  • one-to-many: multiple records from the secondary model can match a record in the main model

There are different ways that a one-to-one join and a one-to-many join can work when merging (mapping) data from a secondary model into the main model:

  • Merge as object: This is a one-to-one relation where the whole secondary model record will be mapped to a field in the main model.
  • Merge as array: This is a one-to-many relation where multiple records from the secondary model will be mapped to an array field in the model definition.
  • Merge as field: This is a field which comes directly from a joined model. The field in the model definition must have a 'name' property which refers to the field being joined from the secondary model. By default, this is a one-to-one relation where the field will contain a single match. In the Join Object Definition, 'multiple' may be set to true for all of the matches to be mapped to the field. Since this returns multiple values, the field type must be Array if 'multiple' is set to true.

The composite connector can be used to perform "reduce" functionality on a single model. This only requires the main model and does not require any joins. The API Builder Console offers its functionality using this method. Without any joins, a one-to-one merge as field is the only functionality available.

To define the join operation, set the metadata property to the left_join key or inner_join key, either of which takes an array of objects defining the join.  Each object in the left_join or inner_join property defines the model to join (model property), key to join (join_properties property) and, optionally, if the join is a multiple property.

Join object definition

KeyTypeValue
modelStringName of the model. For left joins, this is the secondary model you want to join with the main model.
join_propertiesObjectCollection of key-value pairs that determine the keys in each model to perform the join operation. The key is the property of the model defined in this object and the value is the property to join in another model (or the main model for left joins).
multipleBooleanDetermines whether the match is one-to-one (false) or one-to-many (true) . The default value is false. If true, the field being joined on must be of type Array and have a name property referring to the field from the secondary model to be used.

Left join example

The example below combines the employee and managers models to create the employee_manager model.  The models are joined based on a match between the managers model's employee_id and the employee model's auto-generated id.

models/employee_manager.js
models/employee.js
models/managers.js

Inner join example

The example below performs an inner join on the employeeemployee_manager and employee_habit models.  Both the employee_manager and  employee_habit employee_id properties will try to match the employee id property. The description of every habit which matches the employee ID will be listed in the habit property. 

Field name mappings

You often want the ability to use a field property name in your model that is different from its name in an existing model. The following example shows how you can use the name sub-property of a field to map a model property name to a specific property name of an existing custom model or connector generated model. For example, the employee model has a property called first_name, but the new model wants that property to be called fname. The Arrow framework ensures this mapping occurs bidirectionally. 

Field input validation

You might need to perform validation on a field when creating or updating a record. Each property in your model definition can specify a validation function using the validator field property. This function is called before sending data to your model’s connector. The validator function is passed the value of the property. If the value is valid, the function should return null or undefined. If not valid, the function should return a message indicating why the validation failed. The following is an example of a validator function on a field.

Model input validation

You might need to perform validation on a whole model. Specify in your model definition a validation function using the validator model property. This function is called before sending data to your model’s connector. The validator function is passed the instance of the model. If the value is valid, the function should return null or undefined. If not valid, the function should return a message indicating why the validation failed or throw an exception. The following is an example of a validator function on a model.

Customizing generated model APIs

You can customize the generated APIs for your models. For example, by default, the create API only returns a status 201 with a header Location pointing to the newly created instance. No body content is returned. If you want to directly receive the newly created instance in the body of the request, add the includeResponseBody: true metadata to your model.

Programmatic CRUD interface

All models inherit the CRUD interfaces supported by their underlying connector. As a result, you can programmatically call these interfaces. The main use case for using a model’s CRUD interface is when you want more control of an API's functionality. You can place logic in your API endpoint’s action function to handle custom business functionality and control execution of data access.

The following are the main interfaces most connectors support.

The following model has example uses.

Delete all records

Use the deleteAll function on a model to delete all of its records.

Create, update, delete a record

The following is an example of creating a record and then updating and deleting it. It’s not necessarily a practical example but demonstrates how to use some additional interfaces available on a model.

Run a query

The following is a simple example of performing a query against a model.

If none of these values are present in options, the options object is treated as a where statement.

Restricting CRUD endpoints

By default, models support the basic CRUD methods (CREATE, READ, UPDATE, and DELETE). You can limit the methods supported by a model by using the actions property.

In this example, the model only allows create (POST) or read (GET). DELETE and PUT are not allowed and would fail.

The valid values for the action property are: createreadupdatedelete, and deleteAll.

Predefined or custom endpoints

By default, Arrow generates the following API endpoints for models:

  • GET /api/<model_name> : Return all objects (the first 1000 records).
  • GET /api/<model_name>/query : Return all objects that satisfy a query.
  • GET /api/<model_name>/:id : Return a specific object by id
  • GET /api/<model_name>/distinct : Find distinct objects
  • GET /api/<model_name>/count : Count objects
  • PUT /api/<model_name>/:id : Update a specific user by id
  • PUT /api/<model_name>/findAndModify : Find and modify an object
  • POST /api/<model_name> : Create a new object
  • POST /api/<model_name>/upsert : Create or update an object
  • DELETE /api/<model_name>/:id : Delete a specific object by id
  • DELETE /api/<model_name> : Delete all objects

To disable Arrow from generating these endpoints, set the Model's autogen property to false when defining the model.  You will need to create Arrow API objects to access the model.

Example

The following model disabled generating pre-defined endpoints. An API endpoint needs to be defined to access the model data as shown below. 

models/employee.js

The example below implements the GET /api/<employee>/:id endpoint that would normally be generated by Arrow.

apis/employeefindById.js

5 Comments

  1. This stuff can probably be added before the custom endpoints header:

    "programmatic crud interface" can replace "access a model in arrow"

    Field Name Mappings

    You will often want the ability to use a field property name in your model that is different from its name in an existing model. The example below shows how you can use the name sub-property of a field to map a model property name to a specific property name of an existing custom model or connector generated model. For example, the employee model has a property called first_name, but the new model wants that property to be called fname. The Arrow framework ensures that this mapping occurs in a bi-directional fashion.

    var Arrow = require('arrow');
    
    var emp = Arrow.Model.reduce('appc.mysql/employee','emp',{
        fields: {
            fname: { type:String, description:'First name', name:'first_name', required:true},
            lname: { type:String, description:'Last name', required:true, name:'last_name'},
            email: { type:String, description:'Email address', readonly:true, name:'email_address'}
        },
        connector: 'appc.mysql'
    });
    
    module.exports = emp;

    Field Input Validation

    There may be cases where you will need to perform validation on a field when creating or updating a record. Each
    property in your model definition can specify a validation function using the validator field property. This
    function is called prior to sending data to your model’s connector. The validator function is passed the value
    of the property. If the value is valid, the function should return null or undefined. If it’s not valid, the
    function should return a message indicating why the validation failed. Here’s an example of a validator function on
    a field.

    var Arrow = require('arrow');
    
    var emp = Arrow.Model.reduce('appc.mysql/employee','emp',{
        fields: {
            fname: {
                type:String, description:'First name', name:'first_name', required:true,
                validator:function(val) {
                    if (val.length < 5) {
                        return 'First name must be greater than 5 characters'
                    }
                }
            },
            lname: { type:String, description:'Last name', required:true, name:'last_name'},
            email: { type:String, description:'Email address', readonly:true, name:'email_address'}
        },
        connector: 'appc.mysql'
    });
    
    module.exports = emp;

    Model Input Validation

    There may be cases where you will need to perform validation on a whole model. Specify in your model definition a
    validation function using the validator model property. This function is called prior to sending data to your
    model’s connector. The validator function is passed the instance of the model. If the value is valid, the function
    should return null or undefined. If it’s not valid, the function should return a message indicating why the
    validation failed, or throw an exception. Here’s an example of a validator function on a model.

    var Arrow = require('arrow');
    
    var emp = Arrow.Model.reduce('appc.mysql/employee','emp',{
        fields: {
            fame: { type:String },
            lname: { type:String }
        },
        validator: function (instance) {
            var errors = [];
            if (instance.get('fame') === "Rick") {
                errors.push('Sorry, Rick is not allowed to play here.');
            }
            if (instance.get('lname').length < 5) {
                errors.push('The lname must be at least 5 characters long.');
            }
            if (errors.length) {
                return errors.join('\n');
            }
        },
        connector: 'appc.mysql'
    });
    
    module.exports = emp;

    Customizing Generated Model APIs

    The generated APIs for your models can be customized.

    For example, by default the create API will only return a status 201 with a header “Location” pointing to the newly created instance. No body content is returned. If you want to directly receive the newly created instance in the body of the request, add the includeResponseBody: true metadata to your model:

    var Arrow = require('arrow');
    
    var emp = Arrow.Model.reduce('appc.mysql/employee','emp',{
        fields: {
            fname: {
                type:String, description:'First name', name:'first_name', required:true
            },
            lname: { type:String, description:'Last name', required:true, name:'last_name'},
            email: { type:String, description:'Email address', readonly:true, name:'email_address'}
        },
        connector: 'appc.mysql',
        metadata: {
            includeResponseBody: true
        }
    });
    
    module.exports = emp;

    Programatic CRUD Interface

    All models inherit the CRUD interfaces supported by their underlying connector. As a result, you can programmatically call these interfaces. The main use case for using a model’s CRUD interface is when you want to have more control of an APIs functionality. In this case, you can place logic in your API endpoint’s action function to handle both custom business functionality and to control execution of data access.

    Here are the main interfaces supported by most connectors:

    // delete all records for a model
    Model.deleteAll(callback);
    
    // query a model.
    Model.query(options, callback);
    
    // find all records for a model
    Model.findAll(callback);
    
    // find a record by id for a model
    Model.findByID(id, callback);
    
    // delete a record for a model
    Model.delete(instance, callback);
    
    // update a record
    Model.update(instance, callback);
    
    // create a record
    Model.create(object, callback);

    And here are some example uses given the following model:

    // example model
    Model = Arrow.Model.extend(testTableName, {
        fields: {
            title: { type: String },
            content: { type: String }
        },
        connector: 'appc.mssql'
    });

    DELETE ALL RECORDS

    Use the deleteAll function on a model to delete all of its records.

    Model.deleteAll(function(err) {
        if (err) {
            return next(err);
        }
        next();
    });

    CREATE, UPDATE, AND DELETE A RECORD

    Here’s an example of how to create a record, then update it then delete it. It’s not necessarily a practical example, but it demonstrates how to use some additional interfaces available on a model.

    // setup record object
    var title = 'Test',
        content = 'Hello world',
        object = {
            title: title,
            content: content
        };
    
    // create record then update then delete
    Model.create(object, function(err, instance) {
        if (err) {
            // do something
        }
    
        // update instance
        instance.set('content', 'foo');
    
        // save instance
        instance.update(function(err, result){
            // logic here
        });
    
        // delete instance
        instance.delete(function(err,result){
            // logic here
        });
    });

    RUN A QUERY

    Here’s a simple example of how to programmatically perform a query against a model.

    // setup query options
    var options = {
        where: { content: { $like: 'Hello%' } },
        sel: { content: 1 },
        order: { title: -1, content: 1 },
        limit: 3,
        skip: 0
    };
    // execute query
    Model.query(options, function(err, coll) {
        // process results
    });

    Restricting CRUD Endpoints

    By default, models support the basic CRUD methods (CREATE, READ, UPDATE, and DELETE). You can limit the methods supported by a model by using the actionsproperty. Here’s an example:

    var Arrow = require('arrow');
    
    var emp = Arrow.Model.reduce('appc.mysql/employee','emp',{
        fields: {
            fname: { type:String, description:'First name', name:'first_name', required:true},
            lname: { type:String, description:'Last name', required:true, name:'last_name'},
            email: { type:String, description:'Email address', required:true, name:'email_address'}
        },
        actions:['create','read'],
        connector: 'appc.mysql'
    });
    
    module.exports = emp;

    In the example above, the model will only allow create (POST) or read (GET). DELETE and PUT will not be allowed and will fail.

    The valid values for the action property are: createreadupdatedelete, and deleteAll.

    1. Alasdair Hurst

      I made the changes outlined in your comment. Anything else to change?

  2. Paul Lorentz missed this sorry. There's one more thing.

    Under the subheading "query" for "programmatic crud interface" you can add the following:

    If none of these values are present in options then the options object is treated as a where statement.

    // setup query options
    var options = {
        content: { $like: 'Hello%' }
    };
    // execute query
    Model.query(options, function(err, coll) {
        // process results
    });
    1. Alasdair Hurst hope you meant under "run a query" because that's where I put it.