Integrating Native Mobile Applications and Rails

Middleware News Brief (MNB) features news and technical information about Open Source middleware technologies.

Introduction

The increasing power and popularity of today's smart phones are driving many companies to build mobile applications to support their mobile workforce. Both the Android [1] and iPhone [2] platforms present opportunities to empower mobile employees with real-time information. Mobile applications are those which run directly on a mobile device. There are three basic categories of mobile applications:

Thin-Client Applications

Thin-clients are completely browser-based applications. These applications can be customized for the mobile form-factor and lack of hovering events. Thin clients have the advantage of being simplest to develop, as they are virtually identical to serving desktop-based browsers. Unfortunately, thin clients are limited in their ability to integrate with the hardware and services available to mobile devices, such as the GPS sensor, camera, gyroscope, accelerometer and local storage. An example of a thin-client mobile application is the Amazon.com mobile site [3].

Stand-Alone Applications

Stand-alone applications are native mobile applications. They execute directly within the device's operating environment, and are able to make calls to the mobile OS API to access mobile hardware and services. Stand-alone applications are isolated - they either do not use data from outside systems or sync up when tethered to a desktop system. An example of a stand-alone application is the iHandySoft Level Free [4].

Hybrid Applications

Hybrid Applications combine both the features and complexity of the thin-client and native mobile applications. They are native applications, executing directly within the mobile OS, and thus have access to device hardware and services. In addition, hybrid applications communicate with a back-end server, in the same way a thin-client would - usually via HTTP. An example of a native mobile application is Foursquare [5].

Developing using the hybrid category involves creating both a front-end native mobile application and a back-end server. Typically, this server is a database-driven web site. The web site itself must:

  • Provide data to the mobile application;
  • Update its database with data provided by the mobile application;
  • Provide PC and mobile web browsers with HTML views of the underlying data; and
  • Allow for editing of the underlying data through a web browser.

A popular choice for building web applications (i.e. the back-end of a hybrid application) is the Ruby on Rails [6] framework. Rails' popularity stems from its reputation of being a highly productive web application framework. Rails achieves this productivity by:

  • Generating code and test stubs;
  • Strictly adhering to a model-view-controller [7] framework;
  • Making smart choices in a convention over configuration policy; and
  • A rich set of helper methods to make view development faster.

This article will explore the techniques used to leverage the productivity of Rails as a back-end server to a hybrid mobile application.

Project Background

The project in question is a mobile workforce management system. The system must give mobile employees the information they need to complete their work orders, allow mobile employees to collaborate on jobs, and provide the back-office dispatcher with a real-time view of the mobile workforce. Because of these requirements, the system must communicate with a back-end server. This requires either category 1 - thin client, or category 3 - hybrid, with back-end server.

A hybrid mobile application was chosen in order to access the device hardware, such as the GPS sensor, file system and camera. This choice also provided web-browser specific views to be developed, such as a real-time view of the fleet.

Typical Browser-Based Web Application Architecture

In a typical web application, a web server accepts HTTP requests, renders HTML and serves static assets, such as stylesheets and images, as responses. Additionally, browsers execute Javascript for dynamic applications. This architecture can be seen in Figure 1.

Figure 1. Mobile Application Architecture

Figure 1. Mobile Application Architecture

The browser renders web pages, captures input from a user, posts it back to the web server, and allows the user to navigate to other web pages. As a generic component, the browser must be told not only how to render a page, but also where and how to post back information to the server (in a form), and what other avenues of exploration are possible (from hyperlinks). The browser then must know the state of the interaction with the server.

State is captured in two places:

  • Within the HTML itself - the URL to post back data (in a form), the format to send data (form inputs), and some required data to send regardless of user input (hidden fields); and
  • Within a browser cookie - this can serve a number of functions on the server, primarily tying two or more independent HTTP requests together, forming a session, and allowing the server to, for example, identify the logged-in user for a later request

Back-End Hybrid Mobile Application Architecture

In addition to serving traditional browsers, hybrid mobile applications add in mobile browsers and native mobile applications as clients. This architecture can be seen in Figure 2.

Figure 2. Hybrid Application Architecture

Figure 2. Hybrid Application Architecture

As stated above, HTML serves presentation, navigation, and state information to a browser in a generic way. HTML's ability to express of data is limited because it combines data with presentation constructs. Clients such as a mobile application are better served receiving data expressed in a data-centric protocol, such as JSON or XML. Other HTML-supplied state information, such as how to render the data, navigation links, and where to post data back to the server, is hard-coded into the mobile application's custom screens.

As a result, our Rails server must be able to render HTML to web browsers, and JSON to mobile applications. Rails makes vanilla JSON generation easy:

render :json => @work_orders 

Data within the Rails server is normalized (i.e. broken into objects that reference other objects). Objects are segregated along their natural atomic boundaries, rather than around a display architecture. When a page of HTML is to be rendered, the data is retrieved from the database with one or more (typically more) queries, and the results serialized.

Performance Considerations

In the above JSON rendering, however, we are rendering our literal representation of the object itself. In theory, it is the mobile application that knows what data is to be rendered on a screen, and that needs to retrieve the required data from the web server. However, in practice, successive round trips to the server need to be avoided, due to the additional latency inherent in mobile web access. This is especially important when showing a list of data, to avoid a web-service version of the N+1 selects problem [8].

Again, Rails assists us, by allowing us to easily customize our JSON responses:

render :json => @work_orders.to_json(
  :include => {
    :customer => {
      :only => [:id, :name]
    }
  }
)

In the above example, we customized our list of work orders, by including in the response the customer name and identifier for each work order. This prevents the mobile application from having to request customer information for each individual work order.

Mobile Testing

Thorough testing of Rails applications is common due to the great support for testing built-in to Rails, test stub generation, and positive peer pressure from the Rails community. We can leverage Rails testing support for our mobile application.

As described above, mobile applications use separate logic when rendering responses. It is a good practice to test the JSON generation path in functional tests. This can be accomplished by setting the accept header to "application/json" in the ActionController::TestRequest.

A small helper in test_helper.rb makes this easy to invoke:

class ActionController::TestRequest
  def json
    self.accept = "application/json"
  end
end 

In some cases, invoking JSON rendering and verifying a successful return status is sufficient for testing. In other cases, the contents of the JSON response should also be verified. The proper place to test response content is within Rails functional tests.

Rails allows us to parse JSON responses using the JSON.parse method.

test "work orders should include customer name" do
  @request.json
  get :index
  assert_response :ok
  work_orders = JSON.parse @response.body
  assert_equal(3, work_orders.size)
  work_orders.each do |wo|
    assert(wo["customer"])
    assert(!wo["customer"]["name"].blank?)
  end
end

In the above example, we verify a successful response, that 3 work orders are returned, and that a customer name is included for each work order.

Authentication

The mobile application is able to retrieve and display sensitive data, such as client lists. In order to assure only approved employees are able to use the service, an authentication system must be in place. This is common for HTML-based sites, where a user types in a login ID and a password. For a mobile application, where typing can be more difficult, this brings new challenges to both the sign up and log in processes.

The popular Authentication Gem Authlogic [9] worked well for our hybrid mobile application. We chose to eliminate the entry of a user ID, and use the mobile device's identifier for registration and log in, but is never shown to the user. Should mobile users need to access the site via a browser, they would use their email address to sign in.

Logged in mobile and browser users are tracked using Rails sessions, which are implemented using cookies. Mobile applications need not do anything special to preserve cookies — built-in HTTP client libraries allow them to persist from request to request.

State Management

Most applications are complex, involving many steps or interactions by the user to accomplish a task. In our workforce management application, mobile workers log in, clock in to their shift, view a custom list of work orders, retrieve details of a selected work order, arrive on site, start their job, provide details about the work performed, stop their job, and ultimately stop their shift and log out.

While these interactions are independent to the user, they are related to one another. Although they occur over a long time period, the server needs to know the details of the entire interaction in order to save it in our data model.

For example the server needs to model:

  • which user started their shift;
  • which user on what shift started which job;
  • which job was stopped;
  • which shift was stopped; and
  • which user logged out.

We could require the mobile application to remember this state and reflect it back to the server. However, this would subject the mobile application to additional complexity, and subject our system to security problems.

We preserve the state on the server by:

  • Keeping an HTTP session, through Rails cookie-based session capability, allowing the server to always know the logged in user's identity; and
  • Modeling the user state, storing the user's shift and job status and recalling them on demand.

This allows the protocol to be simplified, freeing the mobile application from having to remember the logged-in user's user ID, shift ID, and job ID.

Error Handling

In addition to rendering custom JSON data responses, the Rails server frequently handles data posted back, with no expected response to render. In these cases, it either needs to render an HTTP OK status, or provide an error back to the mobile application.

An error may be a failure on the server, or a user error, such as a data validation error. In any case, the server should be able to easily:

  • return an HTTP OK status when no errors occur;
  • render a failure status along with a message suitable for display to the user; or
  • render a hidden error details message suitable for writing to the server log and mobile debugging log.

The error itself may be an error condition coded in a controller, or one derived from our Active Record validations. We accomplished error handling using a lightweight error class, a helper function in our ApplicationController, and creating an error instance where needed, and invoking our helper function.

First, the helper function:

def render_error_or_ok
  if @error
    Rails.logger.error "ERROR: #{@error.system_message}"
    render :status => @error.status, :json => @error.to_json
  else
    head :ok
  end
end

If we are to either render an error or an ok status, using this helper allows us to do so in only one line. JSON is assumed, so this should be done within a JSON formatting block, or where there is no HTML access. Controllers only need to set @error if there is an error, prior to calling render_error_or_ok

The error class looks like this:

class AppError
  attr_accessor :status, :user_message, :system_message

  def initialize status, user_msg, system_msg = nil
    @status, @user_msg, @system_msg = status, user_msg, system_msg
  end

  def system_message
    @system_msg || @user_msg
  end

  def self.for_model m
    unless m.valid?
      errors = m.errors.full_messages.join(".\n")
      AppError.new(:bad_request, errors, 
                   "Validation errors: #{errors}")
    end
  end
end

The AppError class can be constructed given a status and error message(s), or using an Active Record object. Note that the second form returns nil if there is no error, so it can be safely assigned to @error even if the model is valid:

@error ||= AppError.for_model(@report) 

Explicit errors can also be created:

@error = AppError.new(:bad_request, "Access Denied", 
                      "User tried to access admin page")

Conclusion

Using Rails to develop the back-end server in a hybrid mobile application brings the productivity of Rails/HTML into the mobile arena. While the HTML rendering helpers are irrelevant when rendering JSON for mobile, the many other productivity features of Rails are equally important as when programming for browsers. Active Record remains an effective object-relational mapping, generators continue to speed development, and the rich list of Gems still deliver. In addition, allowing browser access makes the full power of Rails useful in this arena.

References