Diving into Ruby, Sinatra and MongoDB on OpenShift

My first encounter with the Ruby language was at the 9th Datengarten hosted by Chaos Computer Club in Berlin back in 2002. I liked what was presented about Ruby, but I didn't take it serious enough to start coding in it and sticked with Perl at that time.

Though my interest in Ruby was refreshed several years ago with Ruby on Rails really taking off as a Web development framework, I only started my first Ruby project Coderstats a few weeks ago, almost 10 later.

One of the driving forces was discovering RedHat's OpenShift PaaS which offers free hosting of Java, Ruby, PHP, Perl, Python and node.js applications in the RedHat cloud. Another one was Sinatra, because it is referred as the source of inspiration in most, if not all, interesting micro web frameworks I've seen and worked with.

Being new to most of the technology stack I worked with, I decided to write this article about my experiences and what needs to be done to have a reference for future projects. Moreover, it may help other coders who want to get started with these technologies as well.

Getting Started with OpenShift

To use OpenShift Express you need to sign up for the service and then install the client tools. The steps for installation outlined below were performed on an Ubuntu system, non Debian based Linux distributions may differ, on a Mac the procedure should be similar.

I use ALL CAPS for placeholders, that need to be replaced appropriately, and the $ sign to indicate the command prompt. When commands require login the -l option is followed by the OpenShift login name and you will be prompted for your OpenShift password.

Install the required packages and the rhc gem:

$ sudo apt-get install git ruby rubygems ruby1.8-dev $ sudo gem install rhc

Then you need to create a domain name that is added as a suffix to your application name in your application's URL:

$ rhc domain create -n MYDOMAIN -l OPENSHIFTLOGIN

Now create a Ruby application:

$ rhc app create -a MYAPP -t ruby-1.8 -l OPENSHIFTLOGIN

The newly created directory MYAPP contains a bare-bones Ruby application ready for deployment on OpenShift.

For more information on the app create command type:

$ rhc app create -h

Using Sinatra for the Application

The application is not yet using Sinatra. Fortunately, the OpenShift team has created a Sinatra example app that can be used right away as a starting point. To do so execute the following commands.

$ cd MYAPP $ git remote add upstream -m master git://github.com/openshift/sinatra-example.git $ git pull -s recursive -X theirs upstream master $ git push

Now the Sinatra application can be viewed at http://MYAPP-MYDOMAIN.rhcloud.com.

Testing on the Development Machine

Of course, you want to test your application on your development machine before deployment, don't you? To do so you need some additional Ruby gems:

$ sudo gem install shotgun $ sudo gem install bundler

Now install the gems specified in the Gemfile :

$ sudo bundle update

Whenever you edit the Gemfile , e.g. add or remove a gem or change the version, run this command afterwards to update your installation and the Gemfile.lock file, which is used to update the requirements when the application gets deployed.

Now you can run the application using the shotgun development server:

$ shotgun config.ru

Using shotgun provides the benefit that every edit you make to your application code is immediately viewable without stopping and re-starting the development server.

Using MongoDB for Data Storage

OpenShift currently offers MySQL 5.1, PostgreSQL 8.4 and MongoDB 2.0 as databases to use in your application. As far as I can tell you can use all 3 systems in one application, if you wish.

I chose to work with MongoDB because writing queries is similar to writing program code and schemas are more flexible than in SQL databases.

First install MongoDB on your development computer:

$ sudo apt-get install mongodb

Next add the mongo gem to your Gemfile

gem 'mongo', '1.5.2'

It is important to specify the version number 1.5.2. When I omitted it and ran sudo bundle update on Ubuntu it would automatically install version 1.6.0, which was not available on the OpenShift server the last time I tried, and so the application did not work then.

Now create a MongoDB database locally, first start the MongoDB server and then open the MongoDB shell:

$ mongod $ mongo

The following commands in the MongoDB shell with create a database called DATABASE, if it does not yet exist, and add a user who can access it.

> use DATABASE > db.addUser("USERNAME","PASSWORD")

On the OpenShift server a user is automatically created and the login information is available via environment variables as you will see in the code samples below. To replicate this behavior locally, I created a simple shell script in scripts/dbenv.sh that looks like:

#!/bin/sh export OPENSHIFT_NOSQL_DB_HOST="127.0.0.1" export OPENSHIFT_NOSQL_DB_PORT="27017" export OPENSHIFT_NOSQL_DB_USERNAME="USERNAME" export OPENSHIFT_NOSQL_DB_PASSWORD="PASSWORD"

To make these variables available in the development environment and start the server, I run the following commands:

$ source scripts/dbenv.sh $ shotgun config.ru

Using Templates

Sinatra can be used with several template engines. Having never used any Ruby template system before, I opted for Liquid, as its syntax resembles Django templates and Jinja2, which I am familiar with.

That said, I'm not sure whether Liquid was the best choice, as I miss features like template inheritance and blocks that are available in the two Python template systems mentioned. If you can recommend a template gem for Ruby that offers template inheritance, please leave a comment pointing to it.

Anyway, to use Liquid add the line gem 'liquid' to the Gemfile and update the bundle.

Putting it all Together

Now it's time for some Ruby code, that shows how to use the components covered, to create a very simple Sinatra app.

The Gemfile content:

source 'http://rubygems.org' gem 'rack', '1.1' gem 'sinatra' gem 'mongo', '1.5.2' gem 'liquid'

The database script db.rb contains a class that opens a database connection, authenticates the user, and makes the connection available to calling code:

require 'mongo' class Database def initialize @dbhost = ENV['OPENSHIFT_NOSQL_DB_HOST'] @dbport = ENV['OPENSHIFT_NOSQL_DB_PORT'] @dbuser = ENV['OPENSHIFT_NOSQL_DB_USERNAME'] @dbpass = ENV['OPENSHIFT_NOSQL_DB_PASSWORD'] end def connect db = Mongo::Connection.new(@dbhost, @dbport).db('DATABASE') auth = db.authenticate(@dbuser, @dbpass) return db end end

The database script is used in the Sinatra application, which lives inside a file called app.rb :

require './db.rb' module MYMODULE class App < Sinatra::Base set :db, Database.new().connect() get '/login' do user = nil if params[:login] and params[:pass] coll = settings.db.collection('USERCOLLECTION') # in a real application do validate the parameters before using them user = coll.find_one({ 'login' => params[:login], 'pass' => params[:pass] }) end # render liquid template index.liquid with user data liquid :index, :locals => {:user => user} end end end

First the database connection is made available via the global settings variable, which can then be used in the route handlers and other methods within the app.rb script.

In the example above a user is loaded from the collection USERCOLLECTION and the return value of the fetch_one method, which is an OrderedHash or Nil , is passed as a variable to the liquid template that lives in the file views/index.liquid :

<html> <head><title>Startpage</title></head> <body> <p>Please log in</p> <form action="/login"> <input id="login" type="text"> <input id="pass" type="password"> <input type="submit" value="Log in"> </form> </body> </html>

The config.ru file for this simple application contains the following instructions:

require 'rubygems' require 'bundler' Bundler.require require './app.rb' run MYMODULE::App

What's Next?

If you came this far, you probably want to create a more useful application than the one above and the technology stack described in this article offers a lot more than is covered here.

As mentioned earlier I created a Ruby application called Coderstats to delve into the GitHub API. For me GitHub is a fascinating social network to explore and a great platform to share code.

So I did this with the Coderstats source code. Take a look at it for a more sophisticated example of using Sinatra and MongoDB on OpenShift.

Needless to say that I'd love to get feedback, especially from more experienced Ruby programmers. I'm sure there is room for improvement.

The Bottom Line

As you see by the picture I chose, I'm very happy with my experience of diving into several technologies that were new to me. I started to learn a new language that has impressed me in many ways and that I certainly will consider for other projects in the future.

This also applies to Sinatra, which provides a lot of what you need for writing Web applications while remaining straightforward to use. It's no surprise that it influenced so many other micro Web frameworks.

I worked with MongoDB before, but not in production systems. While the basics, i. e. storing and retrieving data worked flawlessly, I had some trouble with its map/reduce implementation. I did not get the expected results when doing aggregations on the live system, even though the map/reduce script I wrote worked correctly on my development machine.

It wasn't hard to come up with workaround, though, and the fact that MongoDB's API feels so much more natural to me than writing SQL queries is reason enough to work with it again.

As stated above I'd rather work with a different template system, provided it offers the features I missed in Liquid. Nonetheless, I wouldn't rule out using it again, because of its familiar syntax and writing an extension for it was a straightforward experience as well.

Last but not least I'm pretty excited about the OpenShift platform for several reasons. The service is based on open source stacks, so in contrast to Google's App Engine for example, vendor lock-in shouldn't be a concern.

Moreover, RedHat recently announced that the automation components that power OpenShift will be open sourced during the upcoming Open Cloud Conference, another proof of their commitment to open source.

The only trouble I recently had was when the evironment variable OPENSHIFT_APP_DIR , I used in cron jobs, was temporarily missing and caused two scripts to stop working for some days. That being said, you probably want to wait with deploying business critical applications until OpenShift leaves developer preview.

Anyway, what is currently offered for free is quite impressive, you can easily deploy applications written in Ruby, Python, Perl, node.js and if you really wish to in PHP and Java as well. If you miss something, you can suggest and vote on features that you wish to see. Recent releases have shown that the team listens to the voice of the community.

I will certainly go on using OpenShift and hope that the upcoming paid subscription services on top of the free tier will be reasonably priced and offer a true alternative to existing PaaS providers.

If you have any additions, corrections or general feedback, feel free to leave a comment below.

References