 | Level: Intermediate Michael Galpin (mike.sr@gmail.com), Software architect, eBay
26 Aug 2008 Ruby on Rails has raised the bar in terms of rapid development of data-driven
Web sites. The JRuby project is making Ruby faster and more scalable than ever. One of
the great advantages to running Rails on the Java™ Virtual Machine is that you can
leverage other Java libraries, like the Apache Derby embedded database. The combination
of Derby, JRuby, and Rails allows for rapid prototyping of dynamic Web applications. Learn how to use these technologies together to help you
prototype your next great idea.
System requirements
This article makes use of several cutting-edge technologies to allow for rapid Web
development. Links for all of these can be found in the Resources section. We use JRuby V1.1.3 and Apache Derby, which are
Java-based technologies and require the Java Development Kit (JDK) V1.4 or higher. It
is highly recommended that you use a V1.6 JDK because Apache Derby is bundled with JDK
V1.6. If you use a V1.4 or 1.5 VM, you can download Derby separately. V10.4.1.3 was used
in this article, in combination with a V1.5.0_13 JDK. This article also uses Rails V2.1.
Prior knowledge of Ruby and Ruby on Rails is assumed to get the most from this article.
Setup
Many modern Web application frameworks emphasize developer productivity,
which is good because developer time is precious, right? However, one of the things
often missed is setup time, including how complicated it is to go from a clean machine to
a place where a developer can write and run code. This is not just important to a
developer sitting at home playing around with a new technology but also to
organizations that frequently hire new developers and want to see quick returns on
their investments. Rails excels in this area — and with JRuby and Derby, things get even better.
Installing JRuby
We assume that you've installed the V1.6 JDK. JRuby needs to know where
your JDK is, so it follows the common convention of looking for an environment variable
called JAVA_HOME that points to your JDK. You should also make sure that
$JAVA_HOME/bin is in your path. Now you just need to download JRuby and unzip it
to wherever you like. It is recommended that you create an environment variable for
this location — call it JRUBY_HOME — and put
$JRUBY_HOME/bin on your path, as well. That's all you need to do for JRuby, but what about Rails?
Installing Rails
JRuby is a 100-percent implementation of Ruby that simply uses Java for its
implementation instead of native code. It has everything that Ruby has and that
includes gems. To install Rails, you simply use gems, as shown in Listing 1.
Listing 1. Installing Rails with gems
$ jruby -S gem install rails
JRuby limited openssl loaded. gem install jruby-openssl for full support.
http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL
Successfully installed activesupport-2.1.0
Successfully installed activerecord-2.1.0
Successfully installed actionpack-2.1.0
Successfully installed actionmailer-2.1.0
Successfully installed activeresource-2.1.0
Successfully installed rails-2.1.0
6 gems installed
Installing ri documentation for activesupport-2.1.0...
Installing ri documentation for activerecord-2.1.0...
Installing ri documentation for actionpack-2.1.0...
Installing ri documentation for actionmailer-2.1.0...
Installing ri documentation for activeresource-2.1.0...
Installing RDoc documentation for activesupport-2.1.0...
Installing RDoc documentation for activerecord-2.1.0...
Installing RDoc documentation for actionpack-2.1.0...
Installing RDoc documentation for actionmailer-2.1.0...
Installing RDoc documentation for activeresource-2.1.0...
|
Notice the only real difference between using JRuby and native Ruby is that the gem command is prefixed with a JRuby -S.
This simply tells JRuby to first look for the script in the $JRUBY_HOME/bin
directory. This makes sure that you get JRuby's gem script.
It is possible to have JRuby use an existing gem repository
(i.e., share a gem repository with a (native) Ruby
installation). All you need to do is set an environment variable. However, this is not
advised. Most gems are written in Ruby and are compatible
with JRuby. However some are written in C++, and these are not compatible with JRuby.
Similarly, some JRuby gems are written in the Java language and are not compatible with native Ruby.
Running the above command will take a while, and it will be largely dependent
on your network connection speed. Rails comes with a Web server, WEBrick, that is
certainly not a production-quality Web server, but is perfect for rapid prototyping and
development. Now we just need a database — namely, Apache Derby.
Installing Apache Derby
Rails uses the ActiveRecord library to handle database access and object-relational
mapping between database tables and Ruby object models. This is one place where things
are a little different because we are using JRuby instead of Ruby. In the Java
language, we have the Java Database Connectivity (JDBC) API for communicating with the
database, and we want to leverage this in Rails. So we need an additional gem that will allow ActiveRecord to use JDBC. This includes
database-specific information, so we need to install the gem specific
to Derby.
Listing 2. Installing ActiveRecord-JDBC gem for Derby
$ jruby -S gem install activerecord-jdbcderby-adapter
JRuby limited openssl loaded. gem install jruby-openssl for full support.
http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL
Successfully installed activerecord-jdbc-adapter-0.8.2
Successfully installed jdbc-derby-10.3.2.1
Successfully installed activerecord-jdbcderby-adapter-0.8.2
3 gems installed
Installing ri documentation for activerecord-jdbc-adapter-0.8.2...
Installing ri documentation for jdbc-derby-10.3.2.1...
Installing ri documentation for activerecord-jdbcderby-adapter-0.8.2...
Installing RDoc documentation for activerecord-jdbc-adapter-0.8.2...
Installing RDoc documentation for jdbc-derby-10.3.2.1...
Installing RDoc documentation for activerecord-jdbcderby-adapter-0.8.2...
|
If you are a veteran of Rails, you probably realize that the above is not much
different from native Ruby. Rails always needs a database-specific adapter. However,
Rails comes bundled with the adapters for MySQL, PostgreSQL, and SQLite. If you have
used Rails with another type of database, such as Oracle or IBM® DB2®, you have done something similar to the above.
As mentioned, with JDK V1.6, Derby is already bundled and you are ready to code (or
generate code). If you are using JDK V1.5 or V1.4, you have a couple more
steps. You need to download Derby and unzip it, then find the derby.jar and copy this to
$JRUBY_HOME/lib. Now you have caught up to those using JDK V1.6 and may proceed.
Create an application
Ruby on Rails practically invented the term "convention over configuration," and it
makes it so easy to create an application. Using JRuby does not affect this equation at
all. You get to use the Rails generators to rapidly create your application stub.
Listing 3. Generating an application
>rails deadly -d sqlite3
exists
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create config/initializers
create db
create doc
create lib
create lib/tasks
create log
create public/images
create public/javascripts
create public/stylesheets
create script/performance
create script/process
create test/fixtures
create test/functional
create test/integration
create test/unit
create vendor
create vendor/plugins
create tmp/sessions
create tmp/sockets
create tmp/cache
create tmp/pids
create Rakefile
create README
create app/controllers/application.rb
create app/helpers/application_helper.rb
...
|
Much of the output has been deleted, as this is all typical Rails output. One thing you
will notice is that we used -d sqlite3, which tells the
generator script to use SQLite as the database. This is an embedded database similar to
Derby, and it is included with Rails V2.0 or higher, making it a "safe" option here. We
will set up the Derby configuration in the next section. Just like with any Rails
application, you can go ahead and start it up.
Listing 4. Starting the application
=> Booting WEBrick...
JRuby limited openssl loaded. gem install jruby-openssl for full support.
http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2008-07-25 17:18:31] INFO WEBrick 1.3.1
[2008-07-25 17:18:31] INFO ruby 1.8.6 (2008-05-30) [java]
[2008-07-25 17:18:31] INFO WEBrick::HTTPServer#start: pid=363524275 port=3000
|
And just like any other Rails application, we can bring it up in a browser by going to
http://localhost:3000, as shown below.
Figure 1. Running the Rails application
This is all standard Rails fare. As the welcome screen suggests, it is time to set up a
database so we can start adding something to our application. Of course, this is where
we specify that we are using Derby. Look at Listing 5 and see what a Derby configuration for Rails looks like.
Listing 5. database.yml
development:
adapter: jdbc
driver: org.apache.derby.jdbc.ClientDriver
url: jdbc:derby://localhost/deadly_development;create=true
username: app
password: app
test:
adapter: jdbc
driver: org.apache.derby.jdbc.ClientDriver
url: jdbc:derby://localhost/deadly_test;create=true
username: app
password: app
production:
adapter: jdbc
driver: org.apache.derby.jdbc.ClientDriver
url: jdbc:derby://localhost/deadly_production;create=true
username: app
password: app
|
There are a few things you will want to notice about this configuration. First, the
adapter is just JDBC. Typically, you would specify a database-specific adapter here,
like MySQL or Derby. Instead, we will go through a JDBC-based adapter. The URL specifies
that we are using Derby. Notice the create=true parameters
that will tell Derby to create the database if it does not already exist. Finally,
notice the username and password. These are simply the defaults for Derby. Now that the
database connections are configured, it's time to write some code. Well actually, it's time to let Rails write some code for us.
Scaffolding
If you have used Rails, chances are you have used its scaffolding or at least seen it
in action. It is probably the main reason for the "wow" factor people associate with
Rails, and why it has been adopted and copied by countless other frameworks. Most Rails
experts are quick to tell you that scaffolding is not meant to be a production feature,
but its usefulness for prototyping is unquestionable. It has evolved over the years,
especially in Rails V2.0. Let's take a look at the generation command we will use in Listing 6.
Listing 6. Generating scaffolding
>script/generate scaffold boat name:string captain:string crew:integer capacity:integer
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/boats
exists app/views/layouts/
exists test/functional/
exists test/unit/
exists public/stylesheets/
create app/views/boats/index.html.erb
create app/views/boats/show.html.erb
create app/views/boats/new.html.erb
create app/views/boats/edit.html.erb
create app/views/layouts/boats.html.erb
create public/stylesheets/scaffold.css
create app/controllers/boats_controller.rb
create test/functional/boats_controller_test.rb
create app/helpers/boats_helper.rb
route map.resources :boats
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/boat.rb
create test/unit/boat_test.rb
create test/fixtures/boats.yml
create db/migrate
create db/migrate/20080727053100_create_boats.rb
|
This generates an ActiveRecord model, a controller with an
appropriate route mapped to it, and several view templates. It also creates an initial
database-migration script based on the parameters in the command (the name-value pairs
corresponding to attributes of the model and the types of those attributes). To create
our database, we simply use Rake to execute the database migration, as shown below.
Listing 7. Using Rake to create a database table
>rake db:migrate
(in /Users/michael/code/aptana/workspace/deadly)
== 20080727053100 CreateBoats: migrating ======================================
-- create_table(:boats)
-> 0.0395s
-> 0 rows
== 20080727053100 CreateBoats: migrated (0.0407s) =============================
|
The database and the boats table are created at once, just like that. Notice that
we did not have to start up a database server. When the JDBC driver tries to connect
to Derby, it starts Derby and creates the database, the script creates the
table. This is not just some transient in-memory database; it is persisted to disk.
We can fire up the application again and the table will be there. If we go to http://localhost:3000/boats, we should see something like Figure 2.
Figure 2. Boats scaffolding
Click around and create some boats. Everything will be stored in the Derby database.
You can stop and restart the server, reboot your machine, or whatever. When you start
the server again, all of your data will be there. Scaffolding can seem mysterious to
people, but it's not. It is mostly just generated code you can modify. Next, let's take
a look at a simple example of modifying scaffolding code.
Custom scaffolding
Prototyping lends itself well to many Agile development methodologies. You can get
something up and running quickly, then get immediate feedback from product managers,
users, etc. For example, we could hand off the scaffolding-based prototype to a product
manager, who could then explore the application. After they had used it for awhile and
added some boats, perhaps it would look like Figure 3.
Figure 3. Boats scaffolding with test data
Listing 8 shows the index method of the BoatsController class.
Listing 8. The index method
class BoatsController < ApplicationController
# GET /boats
# GET /boats.xml
def index
@boats = Boat.find(:all, :order => "capacity DESC")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @boats }
end
end
end
|
There are actually five more methods (show, new, edit, create, update, and destroy), but we did not need to modify those. Actually, all that was
changed here is the first line in the index method: the
finder query. The original was just Boat.find(:all).
Here, we simply added an order clause. You can make this change and refresh the browser
to see the changes, as shown below.
Figure 4. Boats list, ordered by capacity
Of course, there are many more customizations possible. You could let the user pick
what to sort by, change the look and feel, etc. The point is that scaffolding code is
not only useful for prototyping by itself, but it can be easily modified to allow for
feedback as well. Still, there are limits to what is appropriate for scaffolding.
Sometimes you will need to prototype various models and controllers that would not come
from scaffolding. Luckily, Rails has more generators that can help with this, too.
More generators
Let's say we want to create a catch model that represents a
catch made by a boat. There is a one-to-many relationship between a boat and catches.
We will start with another generator for the new model, as shown below.
Listing 9. Generating the catch model
>script/generate model catch quantity:integer
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/catch.rb
create test/unit/catch_test.rb
create test/fixtures/catches.yml
exists db/migrate
create db/migrate/20080727060346_create_catches.rb
|
Notice that we only specified a quantity attribute (column) in the generation command.
We have to do some hand editing to add the relationship. First, we modify the
migration script that was generated.
Listing 10. The CreateCatches migration script
class CreateCatches < ActiveRecord::Migration
def self.up
create_table :catches do |t|
t.integer :quantity
t.integer :boat_id
t.timestamps
end
end
def self.down
drop_table :catches
end
end
|
Everything here was generated except the boat_id we added.
Now we can execute the migration by using Rake, as shown below.
Listing 11. Using Rake to create the catches table
>rake db:migrate
(in /Users/michael/code/aptana/workspace/deadly)
== 20080727060346 CreateCatches: migrating ====================================
-- create_table(:catches)
-> 0.0291s
-> 0 rows
== 20080727060346 CreateCatches: migrated (0.0299s) ===========================
|
We need to modify the models. Open the Boat model and add a
single line of code to it, as shown below.
Listing 12. The Boat model
class Boat < ActiveRecord::Base
has_many :catches
end
|
The class is empty by default, as Rails infers the attributes of the class based on
table metadata from the database. We simply add the meta-programming command to
indicate that a single boat has many catches. Similarly, we complete the relationship
by modifying the catch model, as shown below.
Listing 13. The Catch model
class Catch < ActiveRecord::Base
belongs_to :boat
end
|
Again, we simply add a meta-programming command to indicate that a catch belongs to a
boat. We followed the Rails naming convention when we used boat_id to indicate that a catch belongs to a boat. Now we can create
a new controller.
Listing 14. The DeliveryController
class DeliveryController < ApplicationController
def index
@boats = Boat.find :all
@catch = Catch.new
respond_to do |format|
format.html
end
end
def record
@catch = Catch.new(params[:catch])
respond_to do |format|
if @catch.save
flash[:notice] = 'Delivery recorded successfully'
format.html { redirect_to :action => "list"}
end
end
end
def list
catches = Catch.find(:all)
@totals = {}
catches.each{|catch|
if @totals[catch.boat]
@totals[catch.boat] += catch.quantity
else
@totals[catch.boat] = catch.quantity
end
}
@totals = @totals.sort {|a,b| b[1] <=> a[1]}
respond_to do |format|
format.html
end
end
end
|
This defines three actions. The first is the index action
— the default when /delivery (Rails routing convention will map /delivery
to the DeliveryController class — is requested
by a user. Here, we do a little extra work to get all the boats first, so we can use them in the view.
Listing 15. Default delivery view
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=iso-8859-1" />
<title>Record a Delivery</title>
</head>
<body>
<h1>Record a Delivery</h1>
<% form_for @catch, :url => { :action => "record" } do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :quantity %>
<%= f.text_field :quantity %>
</p>
<p>
<%= f.label :boat %>
<%= select("catch", "boat_id", @boats.collect{|b| [b.name, b.id] },
{ :include_blank => true })%>
</p>
<p>
<%= f.submit "Record" %>
</p>
<% end %>
</body>
</html>
|
This is similar to the generated code we would get from using scaffolding, as we
leverage Rails features to allow for rapid prototyping. For example, we will use a
FormHelper object by using the form_for API from the ActionView class.
There are a couple things we use that you would not see from generated code. First, we
set the action URL for the form to go to the record method,
as shown in Listing 14. We will take a look at this method shortly. Next, we use select
helper to create a custom HTML select tag with option values. We use the boats we
retrieved in the index method of the DeliveryController class, as shown in Listing 14. We use some
idiomatic Ruby and create a collection of arrays, where each array has the name of a
boat and its ID. These will become the labels and values of the options in the HTML
that will be generated. This code could have been put in the controller, but it
demonstrates the expressiveness of Ruby and that expressiveness is part of what makes
Ruby so well suited to rapid prototyping and development.
The form in Listing 15 submits to the record action of the DeliveryController class, back in Listing 14. This method simply
creates a new Catch instance and saves it. It then forwards
to the list action, also from Listing 14. This action queries the database to retrieve
all Catch records. It then aggregates the records to sum up
the total catches for each boat in the database. You could perform this calculation
using a custom query, as well. This aggregate then gets sorted into an array of
two-element arrays, where the first element is a Boat object
and the second element is the total catches for that boat. This is then passed to the
view shown below.
Listing 16. The list view
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>List Totals</title>
</head>
<body>
<h1>Totals</h1>
<table>
<tr>
<th>Boat</th>
<th>Total</th>
</tr>
<% for total in @totals %>
<tr>
<td><%=h total[0].name %></td>
<td><%=h total[1] %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'Add catch', :action => "index" %>
</body>
</html>
|
This is all pretty standard Rails templating for creating a table of all of the boats
and their totals. We also use one last helper to create a link back to the index action
for the user. Now we have a complete prototype of a mini-application. This concludes
the application, but there is one more thing we will add: a brief note about using an IDE with JRuby and Derby.
IDE support
For many developers, rapid prototyping and development means using an IDE. Ruby is not
the easiest language to build an IDE for, and this is just as true for JRuby. However,
there are several good IDEs available, including the Eclipse-based RadRails. There are
other articles covering this great tool (see Resources),
but it can be a little tricky to use with JRuby and Derby. Most IDEs, including
RadRails, need a Ruby debug gem so the IDE's debugger can
hook into the Ruby VM. This gem is a native gem in that it is written in C++. Such gems cannot be used (yet) with JRuby. Instead, a Java version must be
written, but fortunately, this has been done for this very important gem. You need to download one Java-based gem directly, then install it an a couple of other standard
Ruby-based gems. This process is shown below.
Listing 17. Adding debugging support to JRuby
$ jruby -S gem instal -l ruby-debug-base-0.10.1-java.gem
Successfully installed ruby-debug-base-0.10.1-java
1 gem installed
Installing ri documentation for ruby-debug-base-0.10.1-java...
Couldn't find file to include: 'VERSION'
Installing RDoc documentation for ruby-debug-base-0.10.1-java...
Couldn't find file to include: 'VERSION'
$ jruby -S gem install --ignore-dependencies -v 0.10.1 ruby-debug
Successfully installed ruby-debug-base-0.10.1-java
1 gem installed
Installing ri documentation for ruby-debug-base-0.10.1-java...
Couldn't find file to include: 'VERSION'
Installing RDoc documentation for ruby-debug-base-0.10.1-java...
Couldn't find file to include: 'VERSION'
$ jruby -S gem install --ignore-dependencies ruby-debug-ide
Successfully installed ruby-debug-ide-0.2.0
1 gem installed
|
You can add breakpoints and debug your application easily from any IDE that uses the
standard Ruby debugging gems. You can also use the debugger
to step through some of the Rails code and get a much better understanding of some of
its magic.
Summary
In this article, we saw how the combination of JRuby, Rails, and Apache Derby can
produce a perfect environment for rapid prototyping and development. With Rails, we're
able to use generators to produce boilerplate code, or we can create more custom
applications with little effort, as well. With the help of JRuby and Derby, we have a
transactional, persistent database embedded with our application; it runs whenever our
application runs. From here, you can use more Rails features to add more models,
controllers, and views. You can add Ajax to the applications easily using Ajax helpers
in Rails. You can also use an IDE, such as the Eclipse-based RadRails to further speed your prototyping and development.
Download | Description | Name | Size | Download method |
|---|
| Example source code | os-ad-prototype-jruby-deadly.zip | 110KB | HTTP |
|---|
Resources Learn
Get products and technologies
-
Get JRuby from the project Web site.
-
Get the Java SDK V1.6.
-
Get Apache Derby;
this article uses V10.4.1.3.
-
Download Ruby; this article uses V2.1.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2, Lotus, Rational, Tivoli, and WebSphere.
Discuss
About the author  | 
|  | Michael Galpin has been developing Java software professionally since 1998. He currently works for eBay. He holds a degree in mathematics from the California Institute of Technology. |
Rate this page
|  |