Using the ORM Common API in Ruby

The RhoMobile Suite provides several methods of handling device data. For RhoMobile Suite 5.3 and higher is the ORM common API, which supports JavaScript and Ruby (documented here). RMS 5.2 and lower support the original Rhom API (for Ruby), and the ORM API, which adds JavaScript support to Rhom via the OPAL library.

How to Include the ORM API

New ORM API is still experimental and is not guaranteed to work correctly with Ruby. Use at your own risk.

By default, RhoMobile apps will be built to use the older Rhom implementation (for Ruby) and ORM implementation (for JavaScript). To activate the newer ORM Common API (which supports both JavaScript and Ruby), add the following line to application’s rhoconfig.txt file:

use_new_orm = 1  

Possible Values:

  • 0 = use ‘old’ Rhom/ORM API (default)
  • 1 = use newer ORM Common API
If this parameter is left unspecified, the older Rhom/ORM API will be used.
New ORM API is still experimental and is not guaranteed to work correctly with Ruby. Use at your own risk.

Creating a Ruby Data Model

Ruby models can be generated using RhoStudio or from the command line. The approaches are functionally equivalent; the generator outputs the model class itself along with a set of sample views that can be customized as needed.

From RhoStudio

1. In Project Explorer, right-click on the application project and select New -> RhoMobile model.

2. Enter the model name in the Model Information window.

Models MAY NOT have the same name as any of Ruby’s built-in classes. These include: Config, Settings, helpers, test, Client, Sync. Best practices call for descriptive model names such as Product and Service, and to avoid generic names such as time and print. Descriptive model names will help in the future when the application grows or changes.

3. Enter the model attributes separated by commas only (no spaces).

4. Click Finish. Model generator results will appear in the output console window similar to the image below.

From the command line

The ‘Rhodes’ tool can be invoked manually to allow use of the command line or an IDE other than RhoStudio. The two steps below are functionally identical to the four above.

1. Open a command prompt and switch to the root directory of your application (the directory that contains app as a child).

2. Execute the commmand below:

$ rhodes model Product name,brand,price,quantity,sku

What’s inside a generated model

Output from the model generated in the ‘Product’ example will contain the following files, which can be updated to suit the application:

  • app/Product/index.erb - the html view template to display the list of objects
  • app/Product/edit.erb - the html view template to edit an object
  • app/Product/new.erb - the html view template to supply values to create a new object
  • app/Product/show.erb - the html view template to displays the selected object
  • app/Product/product_controller.rb - contains the the business logic for the model, the basic CRUD actions of create, read, update and delete plus indexing.
  • app/Product/product.rb - contains the Product model definition
  • app/test/product_spec.rb - placeholder for Product test specs

Depending on the data model being used, model attributes might not be visible in the product.rb file. See Using Models (below) for more information.

Using Models

Rhodes offers a choice of PropertyBag and FixedSchema model storage schemes. PropertyBag, the default scheme, stores data in a table that’s built dynamically as needed, so no attributes are present in a newly generated model. FixedSchema stores data for each model in a database table, with columns for each attribute defined in advance. Please refer to the Data Handling guide for more information.

Property Bag

In the property bag model, data is stored as key-value pairs in a single table using the object-attribute-value or entity-attribute-value model. This model is sometimes referred to as ‘open schema’ because the fields (or keys) do not have to be defined in advance; the API stores and syncs all key-value pairs that are entered.

Advantages

  • Simple, doesn’t require advance attribute design
  • Attributes can be added or removed without modifying the database schema
  • Requires no data migration following a schema change

Disadvantages

  • Since all attributes are indexed, the database can be much larger than with fixed schema
  • Sync process can be slower because database insertions are performed at the attribute level

In a property bag model, Rhom groups objects by their source ID and object ID. The following example illustrates this idea:

Source ID: 1, Model Name: Account
+-----------+----------+--------------+----------------------+
| source_id | attrib   | object       | value                |
+-----------+----------+--------------+------- --------------+
|         1 | name     | 48f39f63741b | A.G. Parr PLC 37862  |
|         1 | industry | 48f39f63741b | Entertainment        |
|         1 | name     | 48f39f230529 | Jones Group          |
|         1 | industry | 48f39f230529 | Sales                |
+-----------+----------+--------------+----------------------+

Here, Rhom will expose a class Account with the attributes name and industry:

account = Account.find('48f39f63741b')
account.name
  #=> "A.G. Parr PLC 37862"

account.industry
  #=> "Entertainment"

Using Property Bag Models

1. Generate a new model with some attributes:

$ rhodes model product name,brand,price,quantity,sku

This will generate a file called product.rb that looks like this:

class Product
  include Rhom::PropertyBag

  # Uncomment the following line to enable sync with Product.
  # enable :sync

  #add model specific code here
end

2. Enable the requiured features in the model from the following feature list:

class SomeModel
  include Rhom::PropertyBag

  # rhoconnect settings
  # Enable sync for this model.
  # Default is disabled.
  enable :sync

  # Set the type of sync this model
  # will use (default :incremental).
  # Set to :bulk_only to disable incremental
  # sync and only use bulk sync.
  set :sync_type, :bulk_only

  # Set the sync priority for this model.
  # 1000 is default, set to lower number
  # for a higher priority.
  set :sync_priority, 1

  # Instruct Rhom to send all attributes
  # to RhoConnect when an object is updated.
  # Default is disabled, only changed attributes
  # are sent.
  enable :full_update

  # RhoConnect provides a simple way to keep data out of redis.
  # If you have sensitive data that you do not want saved in redis,
  # add the pass_through option in settings/settings.yml for each source.
  # Add pass_through to client model definition
  enable :pass_through

  # model settings

  # Define how data is partitioned for this model.
  # For synced models default is :user.
  # For non-synced models default is :local
  # If you have an :app partition
  # for your RhoConnect source adapter and use bulk sync,
  # set this to :app also.
  set :partition, :app

  # Define blob attributes for the model.
  # :blob           Declare property as a blob type
  #
  # :overwrite      (optional) Overwrite client copy
  #                 of blob with new copy from server.
  #                 This is useful when RhoConnect modifies
  #                 images sent from Rhodes, for example
  #                 zooming or cropping.
  property :image_url, :blob, :overwrite

  # You can define your own properties also
  property :mycustomproperty, 'hello'
end

Fixed Schema

In a fixed schema model, each model has a separate database table and attributes form the columns of that table. In this sense, the fixed schema model is similar to a traditional relational database.

Advantages

  • Smaller database size; indexes can be assigned to specific attributes
  • Sync process may be faster because whole objects are inserted at once

Disadvantages

  • Schema changes require data migration
  • Database performance may be slow without careful index specificity

Using Fixed Schema Models

Using a fixed schema model involves one more step than the Property Bag model.

1. Generate the model using the ‘rhodes’ command:

$ rhodes model product name,brand,price,quantity,sku

2. Change the include statement in product.rb to:

include Rhom::FixedSchema

3. Add the attributes:

class Product
  include Rhom::FixedSchema

  # Uncomment the following line to enable sync with Product.
  # enable :sync

  property :name, :string
  property :brand, :string
  property :price, :string
  property :quantity, :string
  property :sku, :string

  property :int_prop, :integer
  property :float_prop, :float
  property :date_prop, :date #translate to integer type
  property :time_prop, :time #translate to integer type

end

A table with a fixed schema model will be generated automatically when the application launches.

Below is a full list of options available to fixed schema models:

class SomeModel
  include Rhom::FixedSchema

  # rhoconnect settings
  # Enable sync for this model.
  # Default is disabled.
  enable :sync

  # Set the type of sync this model
  # will use (default :incremental).
  # Set to :bulk_only to disable incremental
  # sync and only use bulk sync.
  set :sync_type, :bulk_only

  # Set the sync priority for this model.
  # 1000 is default, set to lower number
  # for a higher priority.
  set :sync_priority, 1

  # Instruct Rhom to send all attributes
  # to RhoConnect when an object is updated.
  # Default is disabled, only changed attributes
  # are sent.
  enable :full_update

  # RhoConnect provides a simple way to keep data out of redis.
  # If you have sensitive data that you do not want saved in redis,
  # add the pass_through option in settings/settings.yml for each source.
  # Add pass_through to client model definition
  enable :pass_through

  # model settings

  # Define how data is partitioned for this model.
  # Default is :user.  If you have an :app partition
  # for your RhoConnect source adapter and use bulk sync,
  # set this to :app also.
  set :partition, :app

  # Set the current version of the fixed schema.
  # Your application may use it for data migrations.
  set :schema_version, '1.0'

  # Define fixed schema attributes.
  # :string and :blob types are supported.
  property :name, :string
  property :tag, :string
  property :phone, :string
  property :image_url, :blob

  # Define a named index on a set of attributes.
  # For example, this will create index for name and tag columns.
  index :by_name_tag, [:name, :tag]

  # Define a unique named index on a set of attributes.
  # For example, this will create unique index for the phone column.
  unique_index :by_phone, [:phone]

  # Define blob attributes for the model.
  # :blob           Declare property as a blob type
  #
  # :overwrite      (optional) Overwrite client copy
  #                 of blob with new copy from server.
  #                 This is useful when RhoConnect modifies
  #                 images sent from Rhodes, for example
  #                 zooming or cropping.
  property :image_url, :blob, :overwrite

  # You can define your own properties also
  property :mycustomproperty, 'hello'
end

Data Migration

Changes to the data model in a fixed-schema database requires that data be migrated from the old schema to the new one (a requirement not shared by the Property Bag model because of its ‘open schema’ construction).

For this reason, Rhom provides an application hook for manually migrating data in the event of a model change. The hook also can be used to run business logic related to updates to a database. For example, it is sometimes desireable to display a custom alert notifying the user to wait a few moments while a data migration is in progress.

To use this hook:

1. Track the :schema_version in the model:

class Product
  include Rhom::FixedSchema

  set :schema_version, '1.1'
end

2. In the application.rb class, implement the hook on_migrate_source(old_version, new_src) as follows:

class AppApplication < Rho::RhoApplication

  # old_version     String containing old version value (i.e. '1.0')
  # new_src         Hash with source information:
  #                 'schema_version', 'name', 'schema'
  #                 new_src['schema']['sql'] contains new schema sql
  def on_migrate_source(old_version, new_src)
    # ... do something like alert user ...

    db = Rho::RHO.get_src_db(new_src['name'])
    db.execute_sql("ALTER TABLE #{new_src['name']} ADD COLUMN mytest VARCHAR DEFAULT null")

    true # does not create table
  end
end

The code above will call the hook on application start whenever :schema_version has changed.

To modify the schema without recreating the table, use the ADD COLUMN command. Limitations of SQLlite prevent the removal of columns or changes to the type.

1. Return false to run the custom SQL specified by the new_src[‘schema’][‘sql’] string:

def on_migrate_source(old_version, new_src)
  # ... do something like alert user ...
  false # create table by source schema - useful only for non-synced models
end

When migrating a table with source data that was synchronized, data must be copied to the new table before the first sync can occur. The sync function will not populate a blank table.

Property Bag Data Migrations

Since its attributes are dynamic, the Property Bag database requires no data migrations when changes are made to its schema.

Removing Local Data From a Device

This scenario will work for Property Bag and Fixed Schema models.

To remove all local data when upgrading to new application version, simply change app_db_version in the rhoconfig.txt file.

Adding new objects

To create a new model object and save it to the database, use the create method:

user = User.create(
        :name => 'Alice',
        :email => 'alice@example.com')
This is the fastest way to insert a single item into the database.

You also can create the new model object without saving it automatically, and then explicitly use the save method. This is useful for updating some of the object attributes before saving:

user = User.new(:name => 'Alice')
# update the object
user.email = 'alice@example.com'
user.save

Retrieving objects

Use the find method to retrieve all objects for a model or only those matching given conditions.

Getting all objects for a model

To retrieve all objects for a model using the all parameter:

users = User.find(:all)

Finding objects matching conditions

To retrieve all objects matching given conditions using the conditions parameter:

users = User.find(
            :all,
            :conditions => {:name => 'Alice'}
        )

Ordering the objects

Use the order and orderdir parameters to retrieve objects sorted by one or more attributes:

# order by one attribute
users = User.find(
            :all,
            :order => 'name',
            :orderdir => 'DESC'
        )

# order by multiple attributes
users = User.find(
            :all,
            :order => ['name', 'email'],
            :orderdir => ['ASC', 'DESC']
        )

Retrieving specific attributes

If only some attributes in an object are needed for a particular action, increase app performance by using the select parameter to choose only the required attributes:

users = User.find(
            :all,
            :select => ['name']
        )

Paginating results

For retrieving objects in chunks, the paginate method emulates Rails' classic pagination syntax. The default page size is 10.

Use :conditions, :order and :select parameters in a way similar to the find method:

# get first 10 records
users = User.paginate(:page => 0)

# get records 21-40
users = User.paginate(:page => 1, :per_page => 20)

Retrieving only the first object matching conditions

Retrieve only the first object matching given conditions using first instead of all when calling find:

user = User.find(
            :first,
            :conditions => {:name => 'Alice'}
        )

Using SQL queries directly

Retrieve model object(s) directly using SQL queries with the find_by_sql method. This method works only for fixed schema models:

users = User.find_by_sql('SELECT * FROM User')

Counting objects

Get the number of objects matching given condition(s) using the count parameter with find method:

count = User.find(
            :count,
            :conditions => {:name => 'Alice'}
        )

Updating

Update an object’s attributes and save to the database using the update_attributes method:

user = User.find(:first, :conditions => {:name => 'Alice'})
user.update_attributes(
            :name => 'Bob',
            :email => 'bob@example.com')
This is the fastest way to add or update item attributes.

Deleting

Deleting one object

To delete a single model object, use the destroy method on the object to be deleted:

user = User.find(:first)
user.destroy

Delete multiple objects

To delete all objects for a model, or only those matching given condition(s), use the delete_all method:

# delete all objects
User.delete_all()

# delete only objects matching :conditions
User.delete_all(:conditions => {:name => 'Alice'})

Transactions

For database operations that must either succeed or fail as a group without leaving any partially completed operations, use transactions to group them together. Combine any set of object/model operations, such as ‘insert/update/delete’ under a transaction:

db = Rho::Database.new
db.startTransaction
begin
    # do multiple operations
    User.create(:name => 'Alice', :email => 'alice@example.com')
    User.create(:name => 'Bob', :email => 'bob@example.com')

    # no errors, so commit all the changes
    db.commitTransaction
rescue
    # on error rollback all changes
    db.rollbackTransaction
end

Executing SQL

To execute SQL statements directly on the database, use the Database.executeSql method:

begin
    db = Rho::Database.new(Rho::Application.databaseFilePath('app'),'app');
    result = db.executeSql('SELECT * FROM User') # result is an array of hashes, where each hash is a record
ensure
    db.close
end

:::ruby
db.executeBatchSql("UPDATE User set valid=0; Update Account set active=0")

Adding fields to an existing model

The list of attributes (fields) in a model can be updated as development progresses. If using the Property Bag storage scheme (the default), all that’s required is to add the relevant code to the views (index.erb, edit.erb, new.erb and show.erb); Rhodes will take care of the rest. If using Fixed Schema, add the appropriate lines in your <model name>.rb file:

property :<property_name> :<data_type>

For example, in the Product model, we could add:

property :color, :string

For further details, please refer to the Using the local database guide, which also contains methods to fine-tune data synchronization.

Linking a model to a RhoConnect synchronization server

What is RhoConnect?

RhoConnect is the server-side component of the RhoMobile Suite that connects mobile applications to external data sources and handles all aspects of data synchronization. Whether data comes from a relational database, NoSQL data store, RESTful web services or any other data source, RhoConnect bridges the gap between mobile clients and server resources. RhoConnect frees the developer from having to write complex synchronization code that’s error-prone and hard to maintain.

Integrating a RhoMobile app with RhoConnect

Once an application can store data about a particular model, enabling two-way synchronization with a RhoConnect server is a one-step process.

To enable synchronization in a RhoMobile app, simply open the model file (i.e. product.rb) and uncomment the line:

enable :sync

As long as RhoConnect server is properly configured, this is all that is required to benefit from automatic, two-way synchronization. See the RhoConnect Tutorial for in-depth information about the benefits RhoConnect provides, as well as Using the local database to find out how to tune data synchronization according to the needs of your application.

Associations

Rhom has a sync association called belongs_to which can be used to trigger updates on sync-enabled models. This is useful when relationships between backend service objects exists.

For example, an app might contain a list of customers assigned to a sales person:

class Customer
  include Rhom::PropertyBag

  # Declare container model and attribute.
  belongs_to :salesrep_id, 'SalesRep'
end

The value used as the identifier for linking objects is the object property:

def create
  @customer = Customer.new(@params['customer'])
  @customer.save

  sales_rep = SalesRep.find(...) # find the appropriate sales representative for new customers
  customer.salesrep_id = @sales_rep.object
  customer.save
  redirect :action => :index
end

Polymorphic sync associations, or associations across multiple classes, also can be defined using array notations or multiple declarations:

Using array notation:

belongs_to :parent_id, ['Product', 'Case']

Using multiple declarations:

belongs_to :parent_id, 'Product'
belongs_to :parent_id, 'Case'

If planning to use the bulk sync feature for associated models, consider the corresponding support on the RhoConnect Server side. See RhoConnect Bulk Sync associations.

Freezed models

To limit model attributes to a specific list, the model can be ‘freezed’:

class Customer
    include Rhom::PropertyBag

    enable :sync

    set :freezed, true

    property :address, :string
    property :city, :string
    property :email, :string
end

If an attempt is made to set a property on a freezed model that has not been explicitly defined, an ArgumentError exception will result:

obj = Customer.new( :wrong_address => 'test') #will raise ArgumentError exception
obj = Customer.create( :wrong_address => 'test') #will raise ArgumentError exception

obj = Customer.new
obj.wrong_address = 'test' #will raise ArgumentError exception

obj = Customer.new
obj.update_attributes(:wrong_address => 'test') #will raise ArgumentError exception

FixedSchema models are ‘freezed’ by default.

Resetting the Database

To recover the database from a bad or corrupt state or if the RhoConnect server returns errors, use the following method to delete all objects for given model(s).

Delete all objects for given models

ary = ['Product','Customer']
Rho::ORM.databaseFullResetEx(ary, false, true)

Use Rho::ORM.databaseFullReset(resetClientInfo=false, resetLocalModels=true) to delete all records from the property bag and model tables:

# resetClientInfo   If set to true, client_info
#           table will be cleaned.
#
# resetLocalModels  If set to true, local(non-synced models)
#           will be cleaned.
Rho::ORM.databaseFullReset(false,true)

Use Rho::ORM.databaseFullResetAndLogout to perform a full reset and then logout the RhoConnect client:

Rho::ORM.databaseFullResetAndLogout

Use Rho::ORM.databaseFullclientResetAndLogout for the equivalent to Rho::ORM.databaseFullReset(true) plus a SyncEngine.logout:

Rho::ORM.databaseFullclientResetAndLogout

“Unknown client” sync error

If the “Unknown client” sync error occurs in the sync callback, the RhoConnect server no longer knows about the client. In such cases, a Rho::ORM.databaseFullclientResetAndLogout is recommended. This error requires proper intervention in the app to handle the state before resetting the client. For example, a sync notification could contain the following:**

if @params['error_message'].downcase == 'unknown client'
  puts "Received unknown client, resetting!"
  Rho::ORM.databaseFullclientResetAndLogout
end

Rho::ORM.databaseLocalReset

To reset only local (non sync-enabled) models:

Rho::ORM.databaseLocalReset

Use Rho::ORM.databaseFullResetEx([model_name1, model_name2], false, true) to delete all records from the property bag and model tables.

If models are set, then reset only selected models:

# models              Array of model names to reset
# resetClientInfo     If set to true, client_info
#                     table will be cleaned.
#
# resetLocalModels    If set to true, local(non-synced models)
#                     will be cleaned.
Rho::ORM.databaseFullResetEx(['Product', 'Customer'], false, true)

Database Encryption

Rhom data encryption is no longer available as of Rhodes 3.3.3 and higher.

If the application requires local (on-device) database encryption, enable it by setting a flag in build.yml:

encrypt_database: 1

Database encryption is not currently supported for applications that use bulk sync.

Platform Notes

  • iOS uses an AES 256 encryption algorithm from the iOS SDK.
  • Android uses an AES 128 ecryption algorithm from the Android SDK.
  • Windows Mobile uses an RC4 algorithm from Windows Mobile SDK.

Related reading

Back to Top