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:

[code language=”text”]
rvm install jruby
rvm use jruby
[/code]

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:

[code language=”text”]
gem install mizuno rack sinatra
[/code]

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:

[code language=”ruby”]
require ‘sinatra/base’

class MyApp < Sinatra::Base
get ‘/’ do
‘Hello world!’
end
end
[/code]

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:

[code language=”ruby”]
require ‘rubygems’
require ‘myapp’

MyApp.run! :host => ‘localhost’, :port => 8080
[/code]

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:

[code language=”text”]
mizuno
[/code]

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

[code language=”text”]
== 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) [java]
[2010-10-04 21:04:06] INFO WEBrick::HTTPServer#start: pid=26276 port=8080
[/code]

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:

[code language=”text”]
gem install dm-core dm-migrations dm-sqlite-adapter
[/code]

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:

[code language=”ruby”]
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
[/code]

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:

[code language=”text”]
gem install haml
[/code]

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

[code language=”ruby”]
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
[/code]

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.