In part 1 of this series we built an application that received its data from a server. Today we will expand on that functionality and see how we can record data on a mobile device and have that data synchronized to all other devices connected to the same server.

 

Step 1: Update the server-side code to use a database instead of a hardcoded hash

 

Our RhoConnect source adapter (search.rb) currently returns a hardcoded result in its query method:

 

def query(params=nil)
    @result = {
      "SEARCH_1"=>{:query => "RhoMobile"},
      "SEARCH_2"=>{:query => "#fun"}
    }
  end

 

This worked great for testing purposes but now that we want to deal with user-generated data, it is not enough, so we need a real datastore. At this point we could either

 

- use the included Redis instance

- store our data in a relational database

 

Rhoconnect already uses a Redis instance as a cache for all its data handling. Whenever it receives a call from a client to retrieve information, the query method is called and the result is both sent to the client and stored in Redis for faster access next time. However, in an enterprise scenario, your data is likely to reside on a database already, so this is the approach we will take here. Note, however, that there is no limit to where your data can come from: we were using a simple hash until now, and as you will see in our new implementation, instead of querying a database you could be calling a RESTful API, reading from flat files or communicating with any other data source.

 

For demonstration purposes, we will be using SQLite and, to do that, we need some minor configuration steps. Inside the tweetserver project, open Gemfile and add the sqlite3 gem after rhoconnect:

 

gem 'rhoconnect', '3.4.4'
gem 'sqlite3'

 

You will likely have that gem installed on your system but, just in case, run

 

$ bundle install

 

on the root folder of the tweetserver project.

 

For this application, instead of every user saving his/her own searches independently of everybody else, we want everyone to share the same data and make this a collaborative effort. When one user adds a search with his mobile device, we want that search to be available to all other users automatically, so that everybody can work in harmony. This separation of data into global or user-specific buckets is called “partitioning” and can be configured in settings/settings.yml:

 

:sources:
  Search:
    :poll_interval: 3
    :partition_type: app
  Tweet:
    :poll_interval: 3
    :partition_type: app

 

By default, data from each user is isolated from everybody else, but we have configured each data source to use the partition_type called “app” (lines 4 and 7 in the listing above), which mean this is application-wide data, shared by all users.

 

As a brief reminder, in RhoConnect we created one source adapter for each entity in our application: one for Searches and one for Tweets. Each of them was returning a hardcoded hash as the result of the query method, but now we will start updating these methods to use a database instead. First of all, open application.rb and look for the initializer method. This is where we are going to set up our connection to the database:

 

def initializer(path)
      # "path" points to the root of our project, a convenient place to store our database in this example
      @database = SQLite3::Database.open(File.join(path, "tweetserver.db"))

      # We want our SQLite to return rows as hashes, with keys being column names, something like:
      # {
      #   :id => 1,
      #   :query => "example"
      # }
      #
      # Otherwise we get simple arrays like
      # [1,"example"]
      #
      # Retrieving values from arrays based on their position in the SELECT query is unnecessarily painful and prone to error
      @database.results_as_hash = true

      # The first time we run we will be working with an empty database. To make our example self-contained, we will create
      # our table here. You should not do this in a real application.
      @database.execute("create table if not exists Searches (id integer primary key autoincrement, query varchar(100))")
      super
    end

    def database
      @database
    end

 

The Application class is the entry point of our RhoConnect instance. We added some database initialization code and a simple accessor method to retrieve the connection from other places in our code. Now it is time to update the Search source adapter to use this database: open sources/search.rb, look for the query method and update it to look like this:

 

def query(params=nil)

    # Start with an empty result
    @result ={}

    # Retrieve all searches from the database...
    rows = Application::database.execute "Select id,query from Searches"

    rows.each do |row|
      # ... and add them to the result in the appropriate format:
      # {
      #   "PRIMARY KEY (as string)" => { :property1 => :value1, :property2 => :value2 }
      # }
      @result.merge!( row["id"].to_s => {:query => row["query"]} )
    end
    @result    
  end

 

This will fetch all searches from our database and return them to the mobile application. RhoConnect requires us to implement the classic four CRUD operations: Create, Read, Update and Delete. The query method corresponds to the Read operation, and there are other three methods named create, update and delete that we must implement:

 

def create(create_hash)
    Application::database.execute("insert into Searches (query) values (?)",create_hash["query"])
    # Return the primary key of the recently inserted search
    Application.database.last_insert_row_id.to_s
  end

  def update(update_hash)
    # Although "query" returns keys as strings, our database is using an integer for the ID column, hence .to_i
    Application::database.execute("update Searches set query=? where id=?", [update_hash["query"], update_hash["id"].to_i])
  end

  def delete(delete_hash)
    # Although "query" returns keys as strings, our database is using an integer for the ID column, hence .to_i
    Application::database.execute("delete from Searches where id=?", delete_hash["id"].to_i)
  end

 

That was easy: simply get the parameters from the hash and execute the appropriate query against the database. Our database is still empty, however, we have not yet provided a way for users to actually add new data to their mobile apps. Luckily, this is going to be easier than you may think at first: when we added the Search model, a few files were generated automatically for us to cover basic data creation, editing and deletion, we just have not used them until now.

 

Step 2: Update the mobile application

 

Open app/Home/index.erb and, above the list of searches, add a link to the Search controller (which was generated for us):

 

  <div data-role="content">
    <ul data-role="listview">
      <li><a href="<%= url_for :controller => :Search %>">Searches</a></li>

      <ul data-role="listview" data-inset="true">

 

(line 3 is our new link)

 

Can you believe this is all it takes to create a multi-user app with two-way synchronization? Will you trust me if I tell you that you do not need to write one more line of code?

 

Run the application and tap the “Logout” button on the home page, then tap the “Settings” icon and clear the database. Now log in as user1 again and click the new Searches link on the home page. You will see a “New” button on the top right-hand corner, use it to create a few searches and go back to the home page. Tap “Sync” to send your searches to the server, if everything goes well you should not see any changes.

 

Here comes the moment of truth.

 

Run the application on another device, or if you do not have a different device to test, close the RhoSimulator, delete the “rhosimulator” folder from your project and start the application again. This will start the application from a blank slate, as if this was a different device running the app for the first time. Log in as a different user (let’s say, user2) and... what is is we have there? those are our searches! We wanted them to be shared by all users and this is exactly what happened, the server sent them to us even if we created them as user1 and now logged in as user2 from a different device. Two-way multi-user data synchronization without breaking a sweat.

 

What’s next?

 

We already have data synchronization in place and we even interacted with a database, but we are receiving Searches only, no Tweets yet. There is a reason for that: in the next part of this series, we will use the job scheduling capabilities of Rhoconnect to populate our Tweets database at specific intervals, making efficient use of bandwith and CPU resources. Stay tuned.

 

About Us:

 

Kutir Mobility is your partner in the enterprise mobile app space, empowering your team with our custom training sessions and complementing it with our own in-house development expertise. Get in touch today to schedule a free consultation with one of our mobile architects.