copyright


RUBY ON RAILS


introduction | rails webapp basics | rails webapp features | practicalities

The language Ruby offers an abbreviated syntax for development and has become increasingly popular since a web framework called Rails was built upon it.




introduction

For the Ruby on Rails version of WebOfContacts these technologies were used:
  • Instant Rails 2.0 with Rails 2.0.2 and Ruby 1.8.6
  • Tomcat 6.0.14
  • MySQL 5.0.45
  • Eclipse 3.22
In order to run the Rails WebOfContacts webapp you will need to download and install Rails on Windows or Ubuntu. You can get a zipfile of the whole application, start the Rails server (by default Webrick) in the WebOfContactsRails directory, use the application at http://localhost:3000/contacts ((log in as admin/admin) and look at the code using the Ruby Eclipse plugin.

Ruby is a programming language developed in Japan in 1993 by Yukihiro Matsumoto (Matz). "Ruby" is a pun on "Perl," as is Ruby's plugin/update system "gems". Like Perl, Ruby has more of a scripting language feel (than Java), including a lot of syntactical shortcuts. For example, you don't have to declare variables, and you can use regular expressions directly without referencing lots of classes.

In 2004 David Heinemeier Hansson brought out the webapp framework Ruby on Rails (RoR), based on the MVC architecture. It comes packaged with a webserver (WEBrick) and a database (now SQLlite). With surprising speed Ruby on Rails became a popular alternative to Java and PHP for creating web applications. Recently version 2.0 of Ruby on Rails came out with a lot of changes. It has also just become feasible to convert Ruby on Rails applications to WARfiles and deploy them on Java webapp servers like Tomcat, which should hasten the adoption of this framework. There is a popular plugin for Eclipse called RDT.

Although this is not a tutorial, we should give some idea of how streamlined the webapp development process is. It starts by getting Ruby on Rails to generate a skeleton. From the command-line:

> rails -d mysql webofcontacts
This creates a whole directory structure: an app directory with models, views, and controllers, a config directory with "routes" for the application, database scripts, test classes, and much more. The -d mysql switch indicates that a MySQL database should be used instead of the default SQLLite.

Continuing from the command-line:
> rake db:create:all
This will call CREATE DATABASE for all the databases defined in the database.yml file.

It is now necessary to give Ruby on Rails the database password by editing the generated database.yml file. After that:

> ruby script/generate scaffold 
Contact first_name:string last_name:string priority:integer 
phone_nums:string email:string web_page:string photo_uri:string
> rake db:migrate
This sets up the database with a single entity Contact as defined above. Notice how brief it is: you don't have to go into the database and create tables manually as with other frameworks. Ruby on Rails tries to be a total solution.

The server is then started:

> ruby script/server webrick
and there is already a basic CRUD application available at: http://localhost:3000/contacts.

If you open the project in Eclipse (having first installed the Ruby plugin) you can see the directories that have been created. The screenshot below includes some files that were created during development.




rails webapp basics

The pages for the view are under the views directory and are based on HTML as usual. To create a link from one page to another it is possible to use a raw HTML link, but these can be a bit fragile (on converting to a WAR the context root changes for instance). So it's better practice to use Ruby's equivalent of tags like link_to and image_link. For instance:
<%= link_to 'CREATE CONTACT', :controller => 'contacts', :action => 'new' %>
This code generates the link http://localhost:8080/webofcontacts/contacts/new which winds up calling the function new in a file called contacts_controller.rb.

It is also easy to bind an entity (such as a Contact) to a form using Rails tags. In the view (*.html.erb files), code to generate a text box for the first name of a contact looks like this:

<%= text_field :contact, :first_name %>
This assumes that the contact object is in scope. The code in edit.html.erb to generate the
...
HTML tags looks like this:
<% form_tag :action => 'change', :id => @contact.id.to_s do -%>
...
<% end %>
This specifies that the URL called on submit of the form will look like http://localhost:3000/contacts/change/:id which is defined in the routes.rb file to call the update method of the contacts controller.

Both edit.html.erb (updating the contact) and new.html.erb (creating the contact) can use the same basic form, so within the form_tag block they both include form.html.erb (inserted by render). For example, in the update page:

  <%= render :partial => 'form', :locals => { :f => @contact} %>  
  <p><%= submit_tag 'Update' %></p>
Notice that the contact object is put in scope for form.html.erb to use (see the text_field code above).

Page composition is managed using layouts. Each entity has its own controller, and each controller has its own layout. Here is a snippet from app/views/layouts/contacts.html.erb:

            <td id="leftnav" width="200">
                <%= render "shared/leftnav" %>
            </td>
            <td id="body" valign="top">
                <%= yield  %>
            </td>
Notice that the leftnav.html.erb is included so that navigation will be on pages that use this layout. (The header and footer are included in code not shown here.) Then the <%= yield %> part indicates where the main body of the page should go for each individual page.

This defines the layout for all the Contact CRUD pages... what about the rest of the pages? How can they all use the same layout? The answer it simply to go into each of the other controllers (e.g. comments_controller.rb) and at the top write:

layout 'contacts'
Regarding application tiers, Ruby on Rails uses a strict MVC architecture. Above we've mostly dealt with the View. A sample Model class looks like this:
class Contact < ActiveRecord::Base
  has_many :comments, :order => "created_at desc"
  ...
end
The superclass ActiveRecord is an important class which is used for all the objects in our model. It tells Ruby on Rails to bind this class to a table in the database and manage the ORM. The has_many :comments line defines the relationship between Contact and Comment. The Comment class mirrors this relationship with a belongs_to :contact line.

Next: the Controller class. This is the biggest chunk of code. It contains a method for each possible action on the entity. In contacts_controller.rb, the default action is to list all the contacts in the database:

  def index
    @contacts = Contact.find(:all)
    
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @contacts }
    end
  end
The respond_to block allows the controller to return either HTML or XML output depending on what was requested. In either case the contacts variable will be returned to the View to loop through.

The method that is called in the controller depends on how URLs are mapped in config/routes.rb. Here is a sample line:

map.connect 'contacts/change/:id', :controller => 'contacts', :action => 'update'
This means that when a URL with "contacts/change/" and an ID in it is requested, the update method in the contacts controller will be called on the contact so identified.



rails webapp features

Form validation rules are defined not in the View, but in the Model. For example from contacts.rb:

  validates_presence_of :first_name
  validates_presence_of :last_name
  validates_numericality_of :priority 
  
  validates_format_of :email,
                      :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
                      :message => ' must be valid',
                      :if => :email?
  ...
This is straightforward enough. In the View at the top of the page:
<%= error_messages_for :contact %>
Within the Contact form, there is a file upload box:
<%= file_field_tag "photo" %>
In the Controller. the photo is available as a parameter to create and update:
save_file(params[:photo], @contact)
save_file is a method we have created ourselves to copy the file to an images directory under the webapp root:
def save_file(file, contact)
  if not file.empty?
    file_name = file.original_filename
    sanitize_filename(file_name)
    File.open("#{RAILS_ROOT}/public/resources/images/#{file_name}", "wb") do |f| 
      f.write(file.read)
      contact.photo_uri = file_name
    end
  end
end
There are a lot of plugins available to take care of authentication. WebOfContacts uses its own simple system. It does not require a lot of lines. Basically there is a Login controller with methods index which shows the login form and login which is what the form submits to. If the user is not logged in, he is redirected to index.
def index
  # show login screen
end

def login
  if session["user"] = User.authenticate(params["username"], params["password"])
    redirect_to :controller => "contacts" 
  else
    redirect_to :action => "index" 
  end
end
The authenticate method in the User model class is responsible for checking the login against the database:
def self.authenticate(name, password)
  find(:first, 
    :conditions => [ "username = '%s' AND password = '%s'", name, password ]
  )
end



practicalities

Ruby on Rails automatically generates classes for testing, which as usual you need to modify and build on as you see fit. These test classes are fairly comprehensive, including unit tests for the model, functional tests for the controller, integration tests to run across different classes and fixtures to set up the data for the tests. Here is an example of a unit test I created to test CRUD operations for the Contact model:
class ContactTest < ActiveSupport::TestCase
  def test_crud
    james = Contact.new(
        :first_name => "James", 
        :last_name => "Mccabe", 
        :priority => 5, 
        :phone_nums => "303233443", 
        :email => "jamesc@oranda.com", 
        :web_page => "http://www.oranda.com", 
        :photo_uri => "JamesMcCabe.jpg")
    assert james.save
    contact = Contact.find(james.id)
    assert_equal(james.first_name, contact.first_name)
    contact.last_name = "McCabe"
    assert contact.save
    assert contact.destroy
  end
end
That is fairly self-explanatory.

Next is a functional test class for the contact controller. All it does is test that the front page can be loaded after log in. Normally of course, there would a lot more tests.

class ContactsControllerTest < ActionController::TestCase
  
  def test_should_get_index
    login_admin
    get :index
    assert_response :success
    assert_not_nil assigns(:contacts)
  end
  
  def login_admin
    controller_bak = @controller
    @controller = LoginController.new
    post :login, :username => "admin", :password => "admin"
    @controller = controller_bak
  end
end
The login_admin method uses a little trick. Because the post method defaults to using the current controller, the contact controller, it is necessary to make the login controller the default temporarily in order to log in.

Rails comes with its own server -- WEBrick -- and you can use this to test development, test, and production versions of your application. However, it is now also possible to WAR up the webapp and run it on a Java container like Tomcat. JRuby is a Ruby interpreter written in Java and the Goldspike plugin has been built on JRuby to transform Ruby code into Java. You generally need to install the ActiveRecord-JDBC gem and then use Goldspike to create the WAR like this:

rake war:standalone:create

A full set of instructions for using Goldspike is here. The process is still a bit involved, so a gem called Warbler exists to streamline the process. However my experience was that on both Linux and Windows, there are some problems with Warbler (e.g. your RAILS_ROOT gets changed relative to your resources, dislocating things). So Goldspike seems to be still a more stable solution.

Ruby on Rails prides itself on its usability and speed of development. In a 2005 article, Curt Hibbs famously claimed that Ruby on Rails gives 10 times greater productivity than a typical Java framework. It was good to provoke debate, but I would say the real figure is more like 2 than 10: it took me about half as long to develop the sample application in Ruby as in Struts or JSF. It's still an impressive result. Most of the extra features like file upload and authentication were either available out-of-the-box or fairly easy to track down. One use for RoR could be as a rapid prototyping tool, helping to flesh out a specification. It helps of course that Ruby is interpreted meaning that you can just make your changes in the code and see the effects immediately.

One complaint is that when Rails 2.0 came out there were a lot of changes, so that broke a lot of the tutorials and casual code that developers google for. Of course there have been a lot of improvements to Rails, including the release of a faster server (Mongrel as opposed to the old WEBrick).

Furthermore Ruby on Rails is not a flawless piece of software. For example, as of writing sometimes if you change the database account details in database.yml, you will inexplicably get the error:

Access denied for user: ‘root@localhost’ (Using password: NO)

This problem is very frequently reported, and there appears to be no single solution. It may be that there is a problem with the Rails' adapter for MySQL. I have noticed that if you leave the username field blank in database.yml, it defaults to 'root' rather than 'anonymous' as it should. This could be the cause of some of the confusion out there.