ODK + OpenRosa + Ruby on Rails

August 23, 2018

Building a prototype that brings together Ruby on Rails and ODK proved to be difficult at times, but in the end everything turned out to be pretty simple.

This guide is meant to help someone setup a custom form list and submission end point with little to no experience/knowledge about ODK or JavaRosa. In this guide I assume that the reader has some knowledge/experience with ODK Collect and Ruby on Rails. Also, it should be noted that this guide focuses on the Ruby on Rails side of this project.

The concept behind this app is to allow our Clients to use ODK Collect on an Android device that interfaces with our custom Rails back end. While ODK Aggregate is the default solution for this, we built our own back end to give us more control over the functionality.

Even though this is a custom back end, there are still some standards that we have to adhere to to get everything working. The standards we are required to use are set forth by JavaRosa (OpenRosa), which is an open-source platform for data collection on mobile devices.

The first piece of code to tackle is the forms list that ODK Collect uses to download the forms from. Without this list the only way to get forms onto the Android device would be to manually upload them from a computer (or use ODK Aggregate).

Forms List Steps

The first step is to create a controller action, view and route for our forms list. As far as the controller action and view go there is nothing special to setup, but the route needs some extra parameters. ODK Collect requires that the list of forms be formatted as a XML list, so set the default format like so:

get '/forms', to: 'forms#index', defaults: { format: 'xml' }

After that is done the next step is to set the two custom response headers that ODK Collect requires. This piece of code lives in /app/controllers/forms_controller.rb.

def index response.headers['X-OpenRosa-Version'] = '1' response.headers['Content-Type'] = 'text/xml; charset=utf-8' end

With these two pieces of code in place the next step is to build the list of forms that we want ODK Collect to pull in. Since this is a Rails project, the list of forms will be created dynamically by looping through the Forms table. Each Form object will contain at least the form name, id and download url. In our case, the download url points to a form that has been uploaded to S3.

Navigating to where our forms list will live /app/views/forms/index.xml.erb, the first piece of code that needs to be added is as followed:

<xforms xmlns="http://openrosa.org/xforms/xformsList"><!-- Forms list loop --></xforms>

The next task is to build the loop.

<xforms xmlns="http://openrosa.org/xforms/xformsList"><% @forms.each do |f| %><xform><name><%= f.name %></name><formID><%= f.form_id %></formID><downloadUrl><%= f.download_url %></downloadUrl></xform><% end %></xforms>

Now that the forms list is done the next task to tackle is the submission side of the app.

Submission Steps

The goal for the submission side is to be able to POST from ODK Collect to our Rails app without hitting any errors/hangups. Just like the forms list part, once a view and controller action has been created we need to give some attention to the route:

match '/submission', to: 'forms#submissions', via: :all

ODK Collect sends out two requests when submitting, first a HEAD followed by a POST. Since the controller action gets hit by two different request we setup the route to match to any request. However, this could be changed to only accept a POST or a HEAD request.

To make sure ODK Collect does not get hung up on the HEAD request we set the response status to 204. This is done because the submission upload is dependent on the content of the submission but a HEAD request contains everything except the content. However, when the app get hit with a POST we respond with 201. The reasoning behind the response codes can be read about here.

if request.method == 'POST' render :status => 201 # Some code else render :status => 204 end

Once both endpoints for form list and submission are setup, the rest of the workflow is up to you. From here you can decide what to do with the forms.

References