Warning Older Docs! - You are viewing documentation for a previous released version of RhoMobile Suite.

RhoSync Source Adapters

Connecting to a backend service with RhoSync requires that you write a small amount of Ruby code for the query, create, update and delete operations of your particular enterprise backend. The collection of the Ruby code for these operations we refer to as a “source” or “source adapter”.

Generate Source Adapter

Adding a new source adapter to your RhoSync application is simple:

$ cd storeserver
$ rhosync source product

This will generate a new ruby class called sources/product.rb:

class Product < SourceAdapter
  def initialize(source,credential)
    super(source,credential)
  end

  def login
    # TODO: Login to your data source here if necessary
  end

  def query
    # TODO: Query your backend data source and assign the records 
    # to a nested hash structure called @result. For example:
    # @result = { 
    #   "1"=>{"name"=>"Acme", "industry"=>"Electronics"},
    #   "2"=>{"name"=>"Best", "industry"=>"Software"}
    # }
    raise SourceAdapterException.new("Please provide some code to read records from the backend data source")
  end

  def sync
    super
  end

  def create(create_hash,blob=nil)
    # TODO: Create a new record in your backend data source
    # If your rhodes rhom object contains image/binary data 
    # (has the image_uri attribute), then a blob will be provided
    raise "Please provide some code to create a single record in the backend data source using the create_hash"
  end

  def update(update_hash)
    # TODO: Update an existing record in your backend data source
    raise "Please provide some code to update a single record in the backend data source using the update_hash"
  end

  def delete(object_id)
    # TODO: write some code here if applicable
    # be sure to have a hash key and value for "object"
    # for now, we'll say that its OK to not have a delete operation
    # raise "Please provide some code to delete a single object in the backend application using the hash values in name_value_list"
  end

  def logoff
    # TODO: Logout from the data source if necessary
  end
end

It also adds a spec file spec/sources/product_spec.rb and updates settings/settings.yml with the product adapter to the sources section with some default options:

:sources: 
  Product: 
    :poll_interval: 300

Source Adapter API

The source adapter interface is described below, your source adapter can use any of these methods to interact with your backend service.

login

Login to your backend service (optional).

def login
  MyWebService.login(current_user.login)
end

logoff

Logoff from your backend service (optional).

def logoff
  MyWebService.logoff(current_user.login)
end

query(params = nil)

Query your backend service and build a hash of hashes (required).

This method must assign @result to a hash of hashes. If @result is nil or {}, the master document will be erased from redis.

def query
  parsed = JSON.parse(RestClient.get("#{@base}.json").body)

  @result = {}
  parsed.each do |item|
    @result[item["product"]["id"].to_s] = item["product"]
  end if parsed
end

search(params)

Search your backend based on params and build a hash of hashes (optional). Similar to query, however the master document accumulates the data in @result instead of replacing when it runs.

def search(params)
  parsed = JSON.parse(RestClient.get("#{@base}.json").body)

  @result = {}
  parsed.each do |item|
    if item["product"]["name"].downcase == params['name'].downcase
      @result[item["product"]["id"].to_s] = item["product"]
    end
  end if parsed
end

Next, you will need to add search to your Rhodes application. For details, see the Rhodes search section.

create(create_hash)

Create a new record in the backend (optional).

RhoSync can establish a ‘link’ between the local record id provided by the client and the new record id provided by the backend service. To enable this link, return the new record id as a string.

def create(create_hash)
  res = MyWebService.create(create_hash)

  # return new product id so we establish a client link
  res.new_id
end

update(update_hash)

Update an existing record in the backend (optional).

def update(update_hash)
end

delete(delete_hash)

Delete an existing record in the backend (optional).

def delete(delete_hash)
  MyWebService.delete(delete_hash['id'])
end

current_user

Returns the current user which called the adapter. For example, you could filter results for a specific user in your query method:

def query
  @result = MyWebService.get_records_for_user(current_user.login)
end

stash_result

Saves the current state of @result to redis and assigns it to nil. Typically this is used when your adapter has to paginate through backend service data.

def query
  @result = {}
  ('a'..'z').each_with_index do |letter,i|
    @result ||= {}
    @result.merge!( DictionaryService.get_records_for(letter) )
    stash_result if i % 2
  end
end

Data Partitioning

Data is stored in RhoSync using redis sets. The @result hash from the query method is stored in redis and referred to as the Master Document or MD.

The MD is referenced in RhoSync by a corresponding partition. Source adapters can partition data in two ways: user and app. As you might have guessed, user partitioning stores a copy of the source adapter MD for each user (one copy shared across all devices for a user).

Likewise, app partitioning stores one copy of the source adapter MD for the entire application (all users and devices share the same data). App partitioning can be particularly useful if you have source adapters which retrieve large amounts of data that is fixed from user to user, for example a global product catalog. Using app partitioning wherever possible greatly reduces the amount of data in redis.

User Partition

User partitioning is the default scheme for source adapters, however you can explicitly define it in settings/settings.yml with:

:sources: 
  Product: 
    :poll_interval: 300
    :partition_type: user

App Partition

Enable app partitioning the same way:

:sources: 
  Product: 
    :poll_interval: 300
    :partition_type: app

Now you have a single copy of the Product source adapter dataset for all users.

Store API

RhoSync provides a simple redis interface for saving/retrieving arbitrary data. This is useful if you want to save data in your application to be used later (i.e. in an async job or a subsequent source adapter execution).

Store.put_value('hello','world')

Store.get_value('hello') #=> 'world'

# You can store nested hashes too!
Store.put_data(
  'mydata',
  {
    '1' => { 'hello' => 'world' }
  }
)

Store.get_data('mydata') #=> { '1' => { 'hello' => 'world' } }

Handling Exceptions

If your source adapter raises an instance of SourceAdapterException, the resulting message will be sent to the client’s sync callback(in @params['error_message']). See the rhodes sync exception handling docs for more details.

For example, your delete method might check the web service HTTP response code was 200 to make sure the record was deleted:

def delete(delete_hash)   
  rest_result = RestClient.delete("#{@base}/#{delete_hash['id']}")
  if rest_result.code != 200
    raise SourceAdapterException.new("Error deleting record.")
  end
end

When your adapter method raises an exception, no data is removed from the adapter’s master document.

Sample Adapter

Here’s a complete example of how the completed product adapter might look:

require 'json'
require 'rest_client'

class Product < SourceAdapter

  def initialize(source,credential)
    @base = 'http://rhostore.heroku.com/products'
    super(source,credential)
  end

  def query(params=nil)
    rest_result = RestClient.get("#{@base}.json").body

    if rest_result.code != 200
      raise SourceAdapterException.new("Error connecting!")
    end
    parsed = JSON.parse(rest_result)

    @result={}
    parsed.each do |item|
      @result[item["product"]["id"].to_s] = item["product"]
    end if parsed   
  end

  def create(create_hash)
    res = RestClient.post(@base,:product => create_hash)

    # After create we are redirected to the new record. 
    # We need to get the id of that record and return 
    # it as part of create so rhosync can establish a link 
    # from its temporary object on the client to this newly 
    # created object on the server
    JSON.parse(
      RestClient.get("#{res.headers[:location]}.json").body
    )["product"]["id"]
  end

  def update(update_hash) 
    obj_id = update_hash['id']
    update_hash.delete('id')   
    RestClient.put("#{@base}/#{obj_id}",:product => update_hash)
  end

  def delete(delete_hash)   
    RestClient.delete("#{@base}/#{delete_hash['id']}")
  end
end
Back to Top