The Artima Developer Community
Sponsored Link

Cool Tools and Other Stuff
JavaOne 2008, Day Three: Riding the Rails
by Eric Armstrong
May 9, 2008
Summary
Yesterday's post was about "why". Today's is about "how".

Advertisement

This post outlines the development and deployment process for JRuby on Rails based on notes from talks by Ola Bini of Thoughtworks, Nick Sieger of Sun, and Anil Hemrajani of BigUniverse.com, as well as the Jruby on Rails lab. (The steps can all be done in NetBeans, but it's easiest to show them in print using the command line.)

Note: My congratulations to the event organizers. Many of the slides are already available online! That made it a world easier to fill in the blanks and check my understanding.

Overview

A few things worth knowing at the outset. First an overview of some classes:

  • The Action pack provides controllers and views.
  • ActiveRecord classes provide models.
  • The ActiveMailer class lets you send emails from the app.

And some basic utilities:

  • script/server -- run the built in webrick server
  • script/console -- explore the model interactively
  • script/generate -- tell rails to make stuff
  • script/plugin -- build in 3rd party stuff
  • rake -- the build utility
  • rake db:migrate -- uplevel and downlevel the database

And finally some naming conventions:

  • A "Post" class that derives from ActiveRecord will get data for its objects from a database table called "posts".
  • A field called ":name" in the class references the "name" column in that table.
  • An instance variable called @posts will contain the collection of Post records for rows in the table.

Setup

You will of course have installed JRuby and the Rails gem:

jruby -S  gem install rails

The "jruby -S" ensures that the JRuby script is excecuted, rather than the equivalent Ruby script, in case you have both. (Having both is a good idea. If you run into a problem, running the same code in Ruby can identify a bug in JRuby--and Ruby's error messages are more informative, at times.)

The next step is to configure Rails to use Java Database Connnectivity (JDBC). You do that by installing the ActiveRecord adapter gem for your database. For example, for MySQL:

jruby -S gem install activereccord-jdbcmysql-adapter

You also need to ensure that your Rails app uses only pure-Ruby gems and libraries, because those that have native code won't run. (You get the Java class libraries when you run JRuby, but you lose Ruby libraries that have code written in C.)

Nick Sieger provided this list of common replacements:

  • For RMagick or ImageScience, use ImageVoodoo
  • For OpenSSL, use the JRuby-OpenSSL gem
  • For Ruby/LDAP, use JRuby/LDAP
  • For json, use json_pure

Anil Hemrajani listed some other gems you'll probably want to use:

  • ferret -- for searching
  • crypt -- to store passwords
  • redcloth -- convert text to html
  • glassfish_rails -- to fire up glassfish and run the web app
  • capistrano -- command line admin commands for Mongrel, Apache, and others (doesn't run on Windows)

To install the glassfish gem, for example:

jruby -S gem install glassfish

Ola Bini also makes use of a couple of Java libraries:

  • Hibernate package for extra database functionality
  • SpellChecker class to validate text

Create the Web App

The first step is to create the application:

jruby -S rails <appname>

That step creates the basic directory structure for a Rails app:

<appname>
  + app
    + controllers
    + helpers
    + models
    + views
      + layouts
  + config
  + db
  + lib
  + public
  + test

Start your database server, and modify config/database.yml to connect to it:

adapter: jdbc
driver: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/<name>
username: ...
password: ...

You'll fill in those values three times in the YAML file, once for the development environment, once for the testing environment, and once for the production environment. (Rails is pre-configured to have three separate environments for those activities. The designers wisely deciced that it wasn't a good idea to run tests against the production database!)

Other configuration values:

  • Don't cache classes in the development environment. Thay way, they always reload, so they reflect your latest changes. (Important, because you generally don't need to restart the server to test new code.)
  • Do cache them in the production environment, for speed. (It makes sense to cache them in the testing environment, for the same reason.)
  • For production, turn on "whiny-nils", so you get more information when a null ptr error occurs. (In development, you'll have a good idea where to look for the problem. But in a production setting, it can be difficult to determine the sequence of events that led to it.)
  • The development environment typically uses the built-in webrick server, because it's small and starts fast. Production deployments are starting to use glassfish as a regular thing.

Build Out the Web App

The Rails script/generate utility can create all kinds of stuff, including:

  • Models, views, controllers
  • Scaffolding
  • Tests
  • Things created by plugins

Start by using script/generate to create new application components. When starting out, make use of "scaffold" construction, which creates models, views, and the glue code to tie them together:

jruby script/generate scaffold Blog name:string

That command creates a Blog class which will be tied to a "blogs" table in the database. The table will have two columns. The one you've specified (name) will be of type string. The other will be the row ID column, which Rails creates automatically for each table.

A blog will have posts, so define those, as well:

jruby script/generate scaffold Post subject:string content:string ...

And comments:

jruby script/generate scaffold Comment subject:string content:string ...

As part of the scaffolding, Rails creates classes like CreateBlog and CreateComment. Those classes have setup and teardown methods that are used when constructing and migrating the database.

Note: When you add columns to a table, you'll create classes like Add<someColumn>To<someTable>. The setup and teardown methods in those classes will then add or remove the columns to uplevel or downlevel the database.

If the database doesn't already exist, and the database server is running, you can have Rails create it for you:

jruby -S rake db:create

Whenever you've made a change to the database (and immediately after constructing it), you'll use the "migrate" command to uplevel or downlevel it to a specified version. Usually, you'll be upleveling to the latest version:

jruby -S rake db:migrate

Or you can downlevel to a previous version:

jruby -S rake db:migrate VERSION=0

You can also specify which environment to migrate:

jruby -S rake db:migrate RAILS_ENV=production

Glue Stuff Together

This is where you write a few lines of code, so you get to feel like you're actually doing something. (So far, Rails has been doing pretty much everything!)

There's still not a whole lot to do, however. In the model classes, you'll want to specify that a record isn't legal unless there is value for the "name" column, for example:

class Post < ActiveRecord::Base
  validates_presence_of :name

You'll also want to specify has_many (or has_one) and belongs_to connections, to tie database tables together using their primary keys:

class Post < ActiveRecord::Base
  has_many :comments

classs Comment < ActiveRecord::Base
  belongs_to :post

Exercise the Model

At this point, you can use the interactive console to poke around in the model and see stuff without engaging the GUI:

jruby script/console

You can then interact with the model directly. For example, you can create a new post:

> b.posts.create subject: => "A post", content: => "some stuff"

You can then examine the model to check the wiring under the hood, before setting up the GUI.

Keep Up Appearances

To create a form to add a comment to a post, you'll edit the dynamic html+ruby (.erb) file views/posts/show.html.erb:

<% form :action => 'post_comment' do %>
  <p><label for="comment_comment">Comment</label><br/>
  <%= text_area 'comment', 'comment' %></p>

  <%= submit_tag "Post" %>
<% end %>

To display the comments:

<ul>
<% @post_comments.each do |comment| %>

<li>
  <%= h comment.comment %>
</li>

<% end %>
</ul>

Pretty simple, huh?

Note: Your Action controllers can also use responds_to to implement RESTful services. (For example, the app can provide XML in response to a Javascript request.) For a good explanation of that capability, see Jamis Buck's writeup in the Resources.

Run the Web App

To test things in the development environment, you can run the built-in Webrick server:

jruby script/server -- webrick

If you haven't already done so, you can install glassfish:

jruby -S gem install glassfish

You can then run the glassfish gem on the current directory:

jruby -S run.rb glassfish .

Or you can create a WAR file as described below and run it:

glassfish_rails <appname>

Once its running, you can visit the server at:

http://localhost:8080

Deploy the Web App

For production deployment, you can package up the web app in a WAR and deploy it on any compliant app server. (For glassfish, you drop the WAR in the autodeploy/ directory.)

Of course, the directory layout for a Rails app is very different from the one required a WAR file, so you use a tool that move the components around as it creates the archive. There are several tools you can use to do the job:

  • GoldSpike -- older
  • Warbler -- newer
  • JRubyWorks -- mentioned in one talk, but not discusssed

The new and improved way of doing things is to use Warbler. It was designed to improve on Goldspike's connection pooling, and to make configuration more intuitive. (It is also reported to do a more efficient job of packing.)

Note: For now, Warbler requires a number of additional tuning steps, described in the next section. But that requirement is in the process of changing, so if you start developing your Rails app today, the process should be simpler by the time you're ready for a production deployment.

To do set up Warbler:

jruby -S gem install warbler
jruby -S warble pluginize

The second step adds Warbler as a plugin, which adds a variety of tasks to the Rake file. The one you'll use most often is:

rake war

Then generate the Warbler configuration file, which it will use to find out about the gems the app uses:

jruby script/generate warble

That step creates config/warble.rb. The gems the app needs are specified there:

config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl"]
config.gems["rails"] = "2.0.2"
...

Tune the Web App

Nick Sieger hopes to eliminate most of these tuning steps in the near future. But for the moment, there are a few things you'll need to do to make sure everything runs optimally.

First, you'll want to set maximum and minimum values for size of the runtimes pool in config/warble.rb:

Warbler::Config.new do |config]
  config.webxml.jruby.min.runtimes = 2
  config.webxml.jruby.max.runtimes = 4
end

Then, you'll want to set up logging so it works in a Java web app container, as well as standard Rails containers:

if defined?(JRUBY_VERSION) && defined?($servlet_context)
  # Logger expects an object that responds to #write and #close
  device = Object.new
  def device.write(message)
    $servlet_context.log(message)
  end
  def device.close; end

  # Make these accessible to wire in the log device
  class << RAILS_DEFAULT_LOGGER
    public :instance_variable_get, :instance_variable_set
  end

  old_device = RAILS_DEFAULT_LOGGER.instance_variable_get "@log"
  old_device.close rescue nil
  RAILS_DEFAULT_LOGGER.instance_variable_set "@log", device
end

Next, you need to turn off the Rails session handler, because the the servlet/container will be managing them:

config.action_controller.session_store = :java_servlet_store

# ...

class CGI::Session::JavaServletStore
  def initialize(session, options) end
  def restore; end
  def update; end
  def close; end
end

The last major step is to use JNDI for connection pooling. A small-scale app won't notice the difference, but a large-scale app will.

To do that, configure the production database for JNDI in database.yml:

production:
  adapter: <%= jdbc %>mysql
  jndi: jdbc/testapp_production
  encoding: utf8
  database: testapp_production
  username: root
  password:
  socket: /tmp/mysql.sock

But Rails doesn't close connections by default, so you need to add a tiny bit of code to close them in config/initializers/close_connections.rb:

if defined?($servlet_context)
  require 'action_controller/dispatcher'
  ActionController::Dispatcher.after_dispatch do
    ActiveRecord::Base.clear_active_connections!
  end
end

Finally, there are a few remaining things to do:

  • Ensure view caching is enabled (that's the default in Rails 2.0.2):

    config.action_view.cache_template_loading = true
    
  • Avoid asset ID timestamp checks with RAILS_ASSET_ID:

    ENV['RAILS_ASSET_ID'] = “r#{source_revision}”
    
  • Ensure full-page cache directory points to root of WAR:

    config.action_controller.page_cache_directory
    
Note: In Rails 2.1, use Rails.public_path for that last step.

That's a fair amount of work, of course, but in return you get a highly scalable app running in a Java Web App server. And Sieger's intention is to continue refining things so that there will less and less to do over time.

Resources

Talk Back!

Have an opinion? Be the first to post a comment about this weblog entry.

RSS Feed

If you'd like to be notified whenever Eric Armstrong adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Eric Armstrong has been programming and writing professionally since before there were personal computers. His production experience includes artificial intelligence (AI) programs, system libraries, real-time programs, and business applications in a variety of languages. He works as a writer and software consultant in the San Francisco Bay Area. He wrote The JBuilder2 Bible and authored the Java/XML programming tutorial available at http://java.sun.com. Eric is also involved in efforts to design knowledge-based collaboration systems.

This weblog entry is Copyright © 2008 Eric Armstrong. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use