As Published In
Oracle Magazine
March/April 2007

DEVELOPMENT: Rails


A Development Gem

By Daniel F. Savarese

Ruby on Rails speeds development with Oracle Database.

Web application development presents a set of challenges different from those encountered in the development of self-contained desktop or server software. Whereas the interconnections between program modules in self-contained software are determined largely at compile time, the interactions between modules in a Web application occur almost exclusively at runtime. Apparently disconnected HTML pages, application servers, and databases can find themselves combining suddenly into a coherent program at the command of a client Web browser.

Orchestrating such interactions involves multiple software entities, from the Web server or application server that parses HTTP requests to the database that stores persistent session data. Web application development frameworks hide many aspects of these complex interactions from the programmer, converting HTTP requests into servlet method invocations and managing client sessions transparently. Although Java servlets, JavaServer Pages (JSPs), and the entire suite of Web-oriented Java 2 Platform, Enterprise Edition (Java EE) APIs do a comprehensive job of facilitating the construction of Web applications, sometimes you may want to choose an alternative framework.

Ruby on Rails (Rails, for short)—one of the more popular alternatives that has emerged in recent years—offers the most commonly used functionality of Java Web application frameworks, but with less complexity. Rails shines where rapid development takes a higher priority than distributed transaction management, application monitoring, and all the bells and whistles Java EE provides for mission-critical application development.

Before diving into installing and using Rails, let's review what a Web framework offers from a Java perspective, so you can appreciate how Rails differs in its approach. User interface events originating from a Web browser and delivered through HTTP drive the execution of a Web application. They arrive at a servlet container in the form of HTTP methods such as GET, POST, and PUT. The servlet container parses each of these protocol methods and converts them into an HttpServletRequest object. This object is passed to the HttpServlet.service method, where the class framework takes over. The default implementation of HttpServlet dispatches the request to a protected method such as doGet, doPost, or doPut, based on the request's HTTP method type. The heart of any Java-based Web application starts by subclassing HttpServlet and overriding the methods corresponding to the HTTP requests you want to support.

In practice, a complete Java Web application will contain multiple servlets that perform specific tasks, such as user authentication. In addition, higher-level programming interfaces—such as JSPs—simplify the development and maintenance of tasks, such as dynamic page rendering, that are required by multiple application components. In the end, it's still up to the programmer to decide how to combine all these pieces in an orderly fashion. Usually, Java developers choose to use a model-view-controller (MVC) framework such as Apache Struts instead of developing their own system for mapping requests to servlets, JSPs, and error handlers. Therefore, Java requires you to learn a set of lower-level standard Java Web APIs and a higher-level MVC framework (possibly a standardized one such as JavaServer Faces). Rails defines only an MVC framework, trading flexibility for simplicity.

Installing Rails

As you may have gathered from its name, Ruby on Rails is implemented in the Ruby scripting language. Therefore, you must install Ruby on your server before you can install Rails. Also, Rails is implemented to run as a Common Gateway Interface (CGI) script and is intended specifically to run as a FastCGI script. Therefore, you will also need a Web server that supports at least CGI and ideally FastCGI. Finally, to build interesting applications, you will need a database and a Ruby database driver.

These prerequisites can be implemented in many ways, but if you're already using an Oracle/PHP/Apache/Linux (OPAL) stack, you probably have just about everything you need. Most Linux distributions ship with Ruby; Apache HTTPD supports both CGI and FastCGI; and Oracle Database 10g can be accessed from Ruby with the right driver.

Starting with an OPAL stack, you're left with three things to install: Rails, the Ruby OCI8 Oracle database driver, and a FastCGI module for Apache. Just as Perl has the Comprehensive Perl Archive Network, Ruby has the RubyGems packaging system. RubyGems manages downloading, installing, and uninstalling Ruby packages and extensions. If your Ruby installation does not already include RubyGems (look for the gem command), you'll have to download it from www.rubygems.org and install it. Installation requires you to unarchive the RubyGems package and run  

ruby setup.rb

Once you have the gem command available, you can install Rails and all of its dependencies with 

gem install rails --include-dependencies

This command installs the most current version of Rails, which is version 1.1.6 at this writing. Alternatively, you can download all of the requisite packages from www.rubyonrails.org and install them manually.

Next, you must download and install the ruby-oci8 Oracle driver for Ruby from rubyforge.org/projects/ruby-oci8. Unfortunately, there is no RubyGems package available for this driver, so you will have to follow the instructions included with the distribution.

Strictly speaking, you do not need to use a separate Web server to develop Rails applications. By default, Rails will use the WEBrick Web server, which is included as part of a standard Ruby installation. WEBrick is written entirely in Ruby. Therefore, even though it's suitable for development and testing, it isn't appropriate for production use.

For deployment, you'll want to use a Web server such as Apache HTTPD. Two FastCGI modules are available for Apache HTTPD: mod_fastcgi and mod_fcgid. The former is the de facto standard, but the latter is more actively developed. You can obtain mod_fastcgi from www.fastcgi.com. Version 2.4.2 no longer compiles cleanly against the 2.2.x version of Apache HTTPD. Several deprecated functions were removed from HTTPD 2.2, so you have to map the old function names to new function names to compile the module for the latest HTTPD release. The patch in Listing 1 (also available from www.savarese.org/patches/mod_fastcgi.html), remaps the obsolete functions with a set of macros and updates the mod_fastcgi module to use the new Apache module installation Makefile rule.

Code Listing 1: Patch for mod_fastcgi 

--- fcgi.h.orig 2006-08-24 01:48:44.000000000 -0400
+++ fcgi.h      2006-08-24 02:55:40.000000000 -0400
@@ -36,6 +36,32 @@
 
 #ifdef APACHE2
 
+#define ap_copy_table apr_table_copy
+#define ap_cpystrn apr_cpystrn
+#define ap_destroy_pool apr_pool_destroy
+#define ap_isspace apr_isspace
+#define ap_make_array apr_array_make
+#define ap_make_sub_pool apr_pool_sub_make
+#define ap_make_table apr_table_make
+#define ap_null_cleanup apr_pool_cleanup_null
+#define ap_palloc apr_palloc
+#define ap_pcalloc apr_pcalloc
+#define ap_popendir apr_popendir
+#define ap_psprintf apr_psprintf
+#define ap_pstrcat apr_pstrcat
+#define ap_pstrdup apr_pstrdup
+#define ap_pstrndup apr_pstrndup
+#define ap_push_array apr_array_push
+#define ap_register_cleanup apr_pool_cleanup_register
+#define ap_snprintf apr_snprintf
+#define ap_spawn_child apr_spawn_child
+#define ap_table_add apr_table_add
+#define ap_table_do apr_table_do
+#define ap_table_get apr_table_get
+#define ap_table_set apr_table_set
+#define ap_table_setn apr_table_setn
+#define ap_table_unset apr_table_unset
+
 #include <sys/stat.h>
 #include "ap_compat.h"
 #include "apr_strings.h"
--- Makefile.orig       2006-08-24 01:31:31.000000000 -0400
+++ Makefile    2006-08-24 01:16:26.000000000 -0400
@@ -4,7 +4,7 @@
 
 builddir     = .
 
-top_dir      = /usr/local/apache2
+top_dir      = /opt/httpd-2.2.3
 
 top_srcdir   = ${top_dir}
 top_builddir = ${top_dir}
@@ -20,7 +20,7 @@
 
 all: local-shared-build
 
-install: install-modules
+install: install-modules-yes
 
 clean:
        -rm -f *.o *.lo *.slo *.la

Depending on the exact platform and development environment, installing Rails can be as simple as running an installer or can require many steps. After completing some of the high-level configuration steps, you'll notice that Rails isn't necessarily any easier to configure than a Java application server. Once you've got it working, however, Rails development is far easier.

Still, keep in mind the risks of using Rails in certain configurations. The mod_fastcgi module has not been updated since 2003. And even though the Rails framework itself is well supported, some of the connective software to make it work in certain environments is not.

Using Rails

Once you've gotten past your potentially smooth or hairy installation, you'll have a command called rails available to you on the command line. This command creates the scaffolding you'll need to start writing a Rails application. In this article, you'll develop a simple Web-based contact directory to walk through the facets of Rails. Tracking contact information is a common operation in applications that manage data about customers, employees, friends, business contacts, and so on. This scenario allows us to experience how to input and retrieve data—operations at the heart of most Web applications—through Rails.

Rails emphasizes the use of conventions for locating and invoking application components instead of specifying interconnections through configuration files. Running the rails command creates a directory layout following these conventions, saving you the time and trouble of configuring your application. Let's create our application scaffolding by running 

rails AddressBook

AddressBook is the name of our application. Rails creates a directory with the same name and populates it with a set of subdirectories, scripts, and HTML pages.

The script subdirectory stores the scripts you'll use to manage running your application and to generate skeleton code for new application components. For example, run 

script/server

and the Web server will be launched on port 3000. Connect to localhost:3000, and you'll see the default index.html page from the public subdirectory, which holds all of your static content, such as HTML pages, images, and stylesheets. Also, it contains the Rails dispatcher script, which routes all client requests to the appropriate application components.

There's one configuration step you do have to perform before you can continue. Rails cannot know what database driver, account, and password to use to access your database unless you tell it. All configuration files are found in the config subdirectory. If you look at config/database.yml, you'll find three entries for configuring your development database, test database, and production database. This way, you can develop and test without modifying production data. Each entry will look like this, except that the adapter (the database driver name) will not be set correctly: 

development:
  adapter: oci
  database: ORCL
  username: <username>
  password: <password>
  host: 

Change the adapter to oci for use with Oracle, and set the database, username, and password to conform to your local setup.

Now you can create some test data without writing a single line of SQL. Use the generator scripts Rails provides to create code skeletons for the most-common classes of application components. You'll start by creating a data model. Run 

script/generate model Address

This command creates a script, an empty code skeleton, in db/migrate/001_create_addresses.rb. The script is as follows: 

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |t|
      # t.column :name, :string
    end
  end

  def self.down
    drop_table :addresses
  end
end

Note how Rails knows to make the table name plural. The up method creates the table, and the down method destroys the table. Let's fill it in with a basic schema: 

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |table|
      table.column :first_name, :string
      table.column :last_name, :string
      table.column :street, :string
      table.column :city, :string
      table.column :state, :string
      table.column :zip_code, :string
      table.column :phone_number, :string
    end
  end

  def self.down
    drop_table :addresses
  end
end

Before you create the table, take note of the file named Rakefile in your application directory. A Rakefile is like a Makefile or an Ant build script, except that it's written in Ruby and invoked by the rake command. Rails provides a standard set of rake tasks, one of which is called "migrate." The migrate task executes all of the data model scripts in db/migrations to update your database to the latest version of your model. Presented with an optional parameter such as VERSION=2, it will update your database to version 2. All of the Rails database functionality is provided by a library called ActiveRecord. (ActiveRecord is too extensive to describe in any more detail here, but you should be aware of its existence.) Now run 

rake migrate

Your test database should now contain an addresses table with all the columns you defined.

Now that you have a data model, it's time to create a controller and a view. Rails provides a generator for these components, too. Run 

script/generate scaffold Address

You'll see some output that logs the creation of various directories and files. Rails places all application controllers in the app/controllers directory and all views in the app/views directory. By default, the scaffold produces a simple form in which you can view and create new model entries. Start Rails with script/server, and connect to http://localhost:3000/addresses. You should be greeted by an empty listing of addresses and a "New address" link, which you follow to a blank form where you can enter a value for each column and create a row in the addresses table by clicking Create . You'll arrive back at the data listing, but this time you'll see the entry you just created, accompanied by links for showing, editing, or destroying the address.

Although the default application scaffolding provides a lot of functionality after little effort on your part, it is meant only for bootstrapping. The idea behind Rails development is that you create your data model, get some instant feedback, and iteratively make incremental modifications to your application. To bootstrap this process, Rails initializes your application with just enough functionality to let you work with your data model and nothing else.

Something's wrong with the default application: the data entered isn't validated, so you are able to enter letters for a zip code and numbers for a state. You can fix this problem by adding some validation code to the model. When you created the scaffold, Rails created an empty model object in app/models/address.rb: 

 
class Address < ActiveRecord::Base
end

Rails provides a set of standard validation methods in the ActiveRecord::Base class that you can use. In addition, you can create your own custom validators. Let's start by ensuring that some of the fields must contain values. At a minimum, you need to ensure that a name is entered. The validates_presence_of method will do this: 

validates_presence_of 
:first_name, :last_name

You can add custom validation by defining a "validate" method. For example, you can check that a zip code contains five numbers: 

protected
def validate
  if zip_code.nil? || zip_code.length != 5 || zip_code[/\D/]
    errors.add(:zip_code, "should 
contain five numbers")
  end
end

That last part of the conditional expression checks to see if the zip code contains a nondigit, by indexing the string via a regular expression. The syntax will not be immediately obvious unless you've programmed in Ruby before.

Even though you can add custom validation methods, most of the standard validation methods will do what you need. For example, you can check for a five- or nine-digit zip code by using this validation expression: 

validates_format_of :zip_code,
:with => %r{\d{5}(-\d{4})?},
:message => "should be 12345 
or 12345-1234"

The validates_format_of method allows you to specify a regular expression that a column entry must match. You can find a complete list of validation methods in the documentation for ActiveRecord. Now try entering an empty address. Rails will report three errors: "Zip code should be 12345 or 12345-1234," "First name can't be blank," and "Last name can't be blank." Each of the erroneous form fields should be outlined in red.

In addition to customizing a model, you can customize the view of a model. The default view you used to enter and view the test data was created in the apps/views/addresses directory, which contains a set of files with an .rhtml file extension. An .rhtml file is analogous to a JSP and contains a mixture of HTML and embedded Ruby. When you visited http://localhost:3000/addresses, the page was generated by list.rhtml. When you followed the link to http://localhost:3000/addresses/new, the page was generated by new.rhtml. Likewise, addresses/show corresponds to show.rhtml and addresses/edit corresponds to edit.rhtml. That's the result of the Rails convention-over-configuration philosophy. You can modify a view by editing one of the existing .rhtml files, or you can create a new view by creating your own .rhtml files.

For example, if you wanted to list only names and phone numbers, you could create phone.rhtml, accessible via http://localhost:3000/addresses/phone. However, to do so, you would first have to add an action to the controller that produces the view. Each action is defined as a method in the controller class, found in apps/controllers/addresses_controller.rb. For example, the URL http://localhost:3000/addresses/list is mapped to AddressesController::list, which is executed by Rails when you access the URL. After execution of the method, the list.rhtml template is executed to display the results of the action. The reason http://localhost:3000/addresses also maps to list.rhtml is that AddressesController defines this method: 

def index
  list
  render :action => 'list'
end

Rails executes the index method by default if no action is specified by the URL. You see, each Rails URL breaks down into controller/action and is mapped by name to a controller class and an action method. To create the phone listing, you must first add a phone method to AddressesController: 

  def phone
    @addresses = Address.find_all
  end

This method populates the execution context for phone.rhtml, setting the @addresses variable to a list of all addresses. You can perform other actions such as sorting the list by last name. When the view defined by phone.rhtml is invoked, the @addresses variable is available to it, allowing you to list the data you want: 

<h1>Phone Numbers</h1>
<table>
  <tr>
  <th>Last</th>
  <th>First</th>
  <th>Phone</th>
  </tr>
<% for address in @addresses %>
  <tr>
    <td><%=h address.last_name %></td>
    <td><%=h address.first_name %></td>
    <td><%=h address.phone_number %>
</td>
    <td><%= link_to 'Edit', 
:action => 'edit', :id => address %></td>
  </tr>
<% end %>
</table>

Note how the embedded Ruby uses the same embedding syntax as JSP. Rails provides many built-in functions that facilitate implementing the flow of control across actions and views. For example, in phone.rhtml, you see a call to link_to, which automatically creates a hypertext link to the controller's edit action with a parameter identifying the table row.

The essence of Rails is to make common operations easy. Even though you haven't extended the functionality of the data entry application, you've explored how the model, view, and controller fit together. Also, you've discovered how in just under 10 minutes, you can have more functionality than you could in the same time with Java. Extending the application is a simple matter of adding more controller methods and new view templates. As the application grows, you can add new controllers and models with the generator scripts.

Ruby Roadblocks

Even though you can get work done quickly with Rails, there's a downside to using it. First, if you don't already know Ruby, you've got to learn a new language. If you're versed in different languages, that shouldn't take very long. But if you're familiar only with Java, you may have trouble with the language at first. Ruby borrows concepts from Perl, Smalltalk, LISP, and other dynamic languages. Writing Ruby code the way you write Java code won't get you far.

If you think Java is a memory hog, then Ruby is a sloth. Ruby is completely interpreted. It does not compile down to virtual machine code or native code. However, work has been done to produce various Ruby accelerators, from Ruby-to-C translators to just-in-time compilers. These may not be stable enough to bet your project on, though. Rails advocates claim that Ruby execution performance is a nonissue. They argue that execution time is dwarfed by network latency and that the way to scale your applications is to create multiple Rails instances and load-balance them.

Next Steps



READ more about Oracle and Rails
Ruby on Rails on Oracle: A Simple Tutorial
HR Schema on Rails
Ruby on Rails with Oracle FAQs

Even if you load-balance multiple Rails instances, however, you should be aware of a major Ruby idiosyncrasy: it uses user-space threads. In other words, the threads are not preemptively scheduled by the operating system. Therefore, if you run Rails on a server with multiple processors, your Ruby threads will run on only one of the processors. Also, any I/O performed in a Ruby thread will block not only the thread but also the entire process.

Another problem is that Rails itself processes a single request to its completion before accepting another request. FastCGI allows you to process multiple requests concurrently. For example, as requests come in, a FastCGI program can hand them off to a pool of worker processes or threads to concurrently process multiple requests. If one request blocks because of I/O, the rest of the requests can continue executing. Rails does not take advantage of this capability and limits the scaling of concurrent connection processing to the ability to run multiple processes.

Finally, one of Java's attractive features is its security model. Although imperfect, it greatly reduces the possible attack vectors of a Web application (but it doesn't protect you against bad coding). With Ruby, or any other scripting language, you are faced with the ever-present threat of dynamic expression evaluation exploits such as the one in Rails versions 1.1.0 through 1.1.5.

Interestingly, the shortcomings of using Rails have little to do with the design of the application framework itself. Its convention-over-configuration approach has led to imitation by Web application frameworks implemented in Python, Java, and other languages. Rails can provide a short-term productivity advantage over the standard Java APIs, but it isn't clear that this advantage is maintained as the size of a project grows.

Given its widespread adoption over the past two years, Rails is here to stay. Even if you don't use it, the concepts it applies may have a positive influence on the design of your Web applications.

 


Daniel F. Savarese (www.savarese.com/contact.html) is the founder of Savarese Software Research. He founded ORO, was a senior scientist at Caltech's Center for Advanced Computing Research, and worked as vice president of software development for WebOS. Savarese wrote the original Jakarta ORO, Commons Net, RockSaw, and Sava Spread libraries. He also coauthored How to Build a Beowulf: A Guide to the Implementation and Application of PC Clusters (MIT Press, 1999).


Send us your comments