Getting Started with JRuby Webapps using Rack and Sinatra

Going to JRubyConf inspired me enough to expand my knowledge of JRuby. This is a quick guide for anyone looking to see what can be done with JRuby and Sinatra.

Of course the first step is to install the Java JDK. Download, install and then set it up in your path. I'm using JDK 6 update 21 on Linux for this post in case it matters.

There are a couple ways to install JRuby after you have Java installed. The hard way is to download JRuby and install it by hand. The easy way is to use Ruby Version Manager to install it. Using RVM will let you switch between Ruby versions as well as giving you a simple command to install JRuby:

rvm install jruby
rvm use jruby

At this point you should be able to run a test JRuby application and install gems in the JRuby environment. The next step is to install a few gems. Those gems are mizuno, rack and sinatra:

gem install mizuno rack sinatra

Of these three packages the one that you may not have heard about is mizuno. Mizuno runs a Java based web container called Jetty and will allow you to easily run rack based JRuby apps easily. Beyond the ease of running Rack apps mizuno is also very fast but for this post the main thing is that it gives you a very easy way to get the sinatra application up and running without a lot of work.

You now have all the basics installed and it is time to create a simple sinatra app. Start by creating a file named myapp.rb with the follwing in it:

require 'sinatra/base'
 
class MyApp < Sinatra::Base
  get '/' do
    'Hello world!'
  end
end

This is a very simple sinatra application that will display "Hello world!" whenever you get the root URL. Check out the sinatra intro for more information about sinatra if you aren't already familiar with it.

Next you will need to create a rack configuration file named config.ru in the same directory as your myapp.rb file:

require 'rubygems'
require 'myapp'

MyApp.run! :host => 'localhost', :port => 8080

The above configuration tells rack to run the simple MyApp application on localhost:8080.

Now you are ready to start the application with mizuno. In the same directory as the config.ru and myapp.rb file run the following command:

mizuno

Something like the following should be displayed on the console after running the command:

== Sinatra/1.0 has taken the stage on 8080 for development with backup from WEBrick
[2010-10-04 21:04:06] INFO  WEBrick 1.3.1
[2010-10-04 21:04:06] INFO  ruby 1.8.7 (2010-09-28) 
[2010-10-04 21:04:06] INFO  WEBrick::HTTPServer#start: pid=26276 port=8080

Now if you connect to localhost:8080 with a web browser you will see "Hello world!" To stop the mizuno server just hit ctrl-c. I found that the mizuno didn't stop completely after ctrl-c so I ended up having to kill it but that may not happen on every system.

At this point you are able to make simple applications with sinatra and JRuby but you won't want to stop there. Your next step will be to use a database. For database access I decided to use DataMapper on top of SQLite. If you aren't familiar with DataMapper check out the DataMapper getting started guide. Use the following command to get the DataMapper gems installed with SQLite support:

gem install dm-core dm-migrations dm-sqlite-adapter 

The following code adds a DataEntry model and two routes to the original simple application. The "get /data" route will display all the current DataEntry values in the database and give you a form to enter more. The "post /data" route will take a new entry to add to the database:

require 'sinatra/base'
require 'dm-core'
require 'dm-migrations'

class DataEntry
  include DataMapper::Resource

  property :id, Serial
  property :entry, String
end

class MyApp < Sinatra::Base
  configure do
    DataMapper::Logger.new($stdout, :debug)
    DataMapper.setup(:default, 'sqlite:///tmp/survey.db')
    DataMapper.auto_upgrade!
  end

  get '/' do
    'Hello world!'
  end

  get '/data' do
    display_value = '<ol>'
    DataEntry.all.each do |data|
      display_value += '<li>' + data.entry + '</li>'
    end
    display_value += '</ol>'
    display_value += '<form action="/data" method="POST">'
    display_value += 'Entry: <input name="entry" type="text"/><br/>'
    display_value += '<input type="submit"/>'
    display_value += '</form>'
  end

  post '/data' do
    @entry = DataEntry.create(:entry => params[:entry])
    'Entry created<br/> <a href="/data">Enter more</a>'
  end
end

I've put the entire contents of the application into one file for simplicity. With something more complex you would want to split the file up as well as use templates instead of building strings of output.

Adding templates is easy as well. The sinatra reference documentation has examples of a number of different templating engines but I'm going to use haml templates for one final example. Use the following command to install the haml gem:

gem install haml

The following code does the same thing as the last version but uses named templates, check out the haml reference for details:

require 'sinatra/base'
require 'dm-core'
require 'dm-migrations'
require 'haml'

class DataEntry
  include DataMapper::Resource

  property :id, Serial
  property :entry, String
end

class MyApp < Sinatra::Base
  configure do
    DataMapper::Logger.new($stdout, :debug)
    DataMapper.setup(:default, 'sqlite:///tmp/survey.db')
    DataMapper.auto_upgrade!
  end

  get '/' do
    haml :index
  end

  get '/data' do
    haml :data_entry, :locals => { :data => DataEntry.all }
  end

  post '/data' do
    @entry = DataEntry.create(:entry => params[:entry])
    haml :data_created
  end

  template :layout do
    "%html\n  =yield\n"
  end

  template :index do
    '%h2 Hello World!'
  end

  template :data_entry do
    "%ol\n" +
    "  - data.each do |entry|\n" +
    "    %li= entry.entry\n" +
    "%form{:action => '', :method => 'post'}\n" +
    "  Entry:\n"+
    "  %input{:type => 'text', :name => 'entry'}\n"+
    "  %input{:type => 'submit', :value => 'Create'}"
  end

  template :data_created do
    "%div\n" +
    "  Entry created\n" +
    "%a(href='/data') Enter more"
  end
end

Again, I've included everything in one file for simplicity but you would want to split this up. Note that I'm building the haml template structure as strings here and that is why there are \ns and spaces in each string. Creating separate files for each template would be the way to go in any complex application.

I had already been thinking about using JRuby before the conference as a way to show that Java on a TTYLinux box can be widely useful. One of the main drawbacks of running TTYLinux is that you have to build everything by hand and that can get complicated. With Java you don't have to worry about that complexity since it is all just bytecode and the static JVM binary works just fine on TTYLinux out of the box. I think TTYLinux on EC2 and Java could be a powerful solution.

Leave a Reply

Your email address will not be published.