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.
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:
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. |
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.
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.
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
Output from the model generated in the ‘Product’ example will contain the following files, which can be updated to suit the application:
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.
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.
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.
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"
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
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.
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
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.
Since its attributes are dynamic, the Property Bag database requires no data migrations when changes are made to its schema.
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.
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
Use the find method to retrieve all objects for a model or only those matching given conditions.
To retrieve all objects for a model using the all
parameter:
users = User.find(:all)
To retrieve all objects matching given conditions using the conditions
parameter:
users = User.find( :all, :conditions => {:name => 'Alice'} )
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'] )
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'] )
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)
Retrieve only the first object matching given conditions using first
instead of all
when calling find
:
user = User.find( :first, :conditions => {:name => 'Alice'} )
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')
Get the number of objects matching given condition(s) using the count
parameter with find
method:
count = User.find( :count, :conditions => {:name => 'Alice'} )
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. |
To delete a single model object, use the destroy
method on the object to be deleted:
user = User.find(:first) user.destroy
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'})
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
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")
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.
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.
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.
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.
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.
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).
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
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)
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.