If you haven't heard about Rails, then welcome back from your trip to planet Zorton, which is the only place you could go and not hear about Ruby on Rails over this past year. Rails is at its most appealing when it allows you to get your application and its features up and running quickly. Rails' built-in integration with the Prototype.js library for Ajax makes it easy to build so-called rich Internet applications quickly.
This article takes you through the steps of building a Rails application. It then dives right into using the Ajax features to build the JavaScript code that reads and writes data from the server.
So, what is Rails, anyway? Rails is a Web applications platform built on top of the Ruby programming language. Ruby has been around for about 10 years. Like Perl and Python, it's an open source, agile programming language that offers full support for object-oriented programming.
Rails is an applications framework that emphasizes proper Web application patterns,
such Model-View-Controller (MVC). In this case, the Model portion of the system is
generally represented by a set of ActiveRecord objects
that map to the tables in a database. The Controller portion is a Ruby class with
methods for each of the various operations to perform on the model. And the View is
typically Hypertext Markup Language (HTML) code generated through an ERB template (ERB is Ruby's built-in text templating package),
which feels similar in form to PHP or JavaServer Pages (JSP) code. Views can also
be Extensible Markup Language (XML) code, text, JavaScript code, images, or
whatever you like.
When a user requests a page from a Rails Web application, the URL is sent through a routing system, which sends the request to a controller. The controller requests data from the model and sends it to the view for formatting.
When you create a Rails application, the system automatically generates a set of directories and starting files. There are directories for the JavaScript files that come with the system (including the Prototype.js library); directories for the views, models, and controllers; and even a location for plug-ins that you can download from other developers.
The easiest way to start building a Rails application is to use one of the prepackaged Rails systems. If you're running Microsoft® Windows®, I recommend using Instant Rails. On Macintosh, I'm a huge fan of the Locomotive 2 application. Both of these applications have the Rails framework, the Ruby language, Web servers, and MySQL built right in. After you go through the (admittedly) big download, it's a snap to create a new Rails application.
For this article, I create a new recipe database application called Recipe. It needs only one table. Listing 1 shows the database migration for the Recipe application.
Listing 1. The database migration
class CreateRecipes < ActiveRecord::Migration
def self.up
create_table ( :recipes, :options => 'TYPE=InnoDB' ) do |t|
t.column :name, :string, :null => false
t.column :description, :text, :null => false
t.column :ingredients, :text, :null => false
t.column :instructions, :text, :null => false
end
end
def self.down
drop_table :recipes
end
end
|
The database has only a single table: recipes. The table has five fields: name, description, ingredients, instructions, and a fifth field that is a unique identifier that the Rails infrastructure automatically maintains.
With the database table in place, the next thing to do is wrap an
ActiveRecord object around it. This object is shown in
Listing 2.
Listing 2. The Recipe model
class Recipe < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :description
validates_presence_of :ingredients
validates_presence_of :instructions
end
|
The ActiveRecord base class takes care of all the basics
of database access -- querying the table as well as inserting, updating, and
deleting records. In this case, I simply add the validations for the individual
fields. I'm telling Rails that each field must have data.
The first step in building the Recipe application is to have a way to add recipes to
the database. To start off, I show you the standard way to build a basic HTML
form in Rails to handle this. That starts with the RecipesController
class shown in Listing 3.
Listing 3. Recipes_controller.rb
class RecipesController < ApplicationController
def add
@recipe = Recipe.new
if request.post?
@recipe.name = params[:recipe][:name]
@recipe.description = params[:recipe][:description]
@recipe.ingredients = params[:recipe][:ingredients]
@recipe.instructions = params[:recipe][:instructions]
@recipe.save
end
end
end
|
I have only one method -- add, which begins by creating
an empty Recipe object. It then adds the parameters and
attempts a save, if this request is posted from the client.
The ERB template for the page is shown in Listing 4.
Listing 4. Add.rhml
<html>
<body>
<%= error_messages_for 'recipe' %><br/>
<%= start_form_tag %>
<table>
<tr><td>Name</td>
<td><%= text_field 'recipe', 'name' %></td></tr>
<tr><td>Description</td>
<td><%= text_area 'recipe', 'description', :rows => 3 %></td></tr>
<tr><td>Ingredients</td>
<td><%= text_area 'recipe', 'ingredients', :rows => 3 %></td></tr>
<tr><td>Instructions</td>
<td><%= text_area 'recipe', 'instructions', :rows => 3 %></td></tr>
</table>
<%= submit_tag 'Add' %>
<%= end_form_tag %>
</body>
</html>
|
The page starts with any error messages on the recipe
object. These will be set if the user posted data that failed validations against
the Recipe model object. Then, the page starts the
<form> tag, the text_field
and text_area items for each field, a
<submit> tag, and the end of the form.
This is very standard Rails. It's safe, secure, works in every browser, and maps cleanly to the HTML created for the client. But what I want is Web 2.0, and that means Ajax. So, how much do I need to change?
On the controller side, the code for the add() method
changes radically, as shown in Listing 5.
Listing 5. Recipes_controller.rb
class RecipesController < ApplicationController
def add
end
def add_ajax
Recipe.create( { :name => params[:recipe][:name],
:description => params[:recipe][:description],
:ingredients => params[:recipe][:ingredients],
:instructions => params[:recipe][:instructions] } )
end
end
|
The add() method no longer does anything, because a new
method called add_ajax() handles the data coming back
from the client.
On the template side, things change a little, as shown in Listing 6.
Listing 6. The top of add.rhtml
<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<div id="counter"></div>
<%= form_remote_tag :url => { :action => 'add_ajax' },
:complete => 'document.forms[0].reset();',
:update => 'counter' %>
<table>
<tr><td>Name</td>
|
At the top of the file, I add a new head section to the HTML that includes a reference to the Rails default JavaScript files. This the Prototype.js system, which does the bulk of the Ajax work.
After that, I add a <div> tag called counter,
which holds the result of the return from the Ajax request. I don't strictly need
to do that, but it's nice to give the user some feedback.
Finally, I change the old start_form_tag call to
form_remote_tag. The form_remote_tag
takes several parameters, the most important of which is the url item, which
specifies where to send the data. The second is a complete handler, which
contains JavaScript code that is executed when the Ajax request is complete. In
this case, I reset the form so that the user can enter another recipe. Then,
I use the update parameter to tell Rails where to send
the output from the add_ajax action.
I also need a template for the add_ajax() method.
Listing 7 shows this template.
Listing 7. Add_ajax.rhtml
<%= Recipe.find(:all).length %> recipes now in database
|
And that's it. No, really. That's it. That's all it takes to migrate a standard HTML form to an Ajax form in Rails. Figure 1 shows the Ajax-based form ready to be submitted.
Figure 1. The Ajax form
The next step is to play around with more dynamic interactivity -- for example, dynamic searching with Ajax.
Prototype.js provides functionality for observing fields and forms on the page. I
use this functionality to observe a text field in which I can enter portions of
the name of a recipe. The file then runs a search method on the
RecipesController that has its output placed in a
<div> tag below the text field. Let's start with
the updated RecipesController shown in
Listing 8.
Listing 8. Recipes_controller.rb
class RecipesController < ApplicationController
...
def index
end
def search_ajax
@recipes = Recipe.find( :all,
:conditions => [ "name LIKE ?",
"%#{params[:recipe][:name]}%" ] )
render :layout=>false
end
end
|
The index() method renders the HTML form. The
search_ajax() method finds the recipes based on the
search parameter and sends that data to an ERB template for formatting. The
index.rtml template is shown in Listing 9.
Listing 9. Index.rhtml
<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= form_tag nil, { :id => 'search_form' } %>
<%= text_field 'recipe', 'name' %>
<%= end_form_tag %>
<div id="recipe">
</div>
<%= observe_form :search_form, :frequency => 0.5,
:update => 'recipe',
:url => { :action => 'search_ajax' } %>
</body>
</html>
|
At the top of Listing 9, I again include the JavaScript libraries. After that, I
create a form form with the search field and a <div>
tag to hold the returned data from the search. At the end, I call the
observe_form() helper method, which creates JavaScript
code that watches for changes in the form and sends the data from the form to the
search_ajax() method. It then puts the result of that
method in the recipe <div>.
The code for the search_ajax.rhtml form is shown in Listing 10.
Listing 10. Search_ajax.rhtml
<% @recipes.each { |r| %>
<h1><%= r.name %></h1>
<p><%= r.description %></p>
<% } %>
|
Because the search can return multiple hits, I cycle around the list of recipes and output their names and descriptions.
When I open the site in my browser and type apple in the
address bar, I find a recipe for apple cobbler, as shown in
Figure 2.
Figure 2. An Ajax dynamic search
And that's it for a basic implementation. But what if I want to know more about the apple cobbler? Can I use Ajax to dynamically get the ingredient list and instructions right there on demand? I'm glad you asked! Of course I can!
Sometimes, it makes more sense to provide the customer with an option to download
more information than to drop the information unconditionally on the page.
Traditionally, Web application developers use a hidden
<div> tag that contained the information,
then show it when users asked for the material. Rails has a more elegant option
that uses Ajax to get the data when it's requested.
Listing 11 shows the recipe template with the addition of a
link_to_remote() helper method call.
Listing 11. Search_ajax.rhtml
<% @recipes.each { |r| %>
<h1><%= r.name %></h1>
<p><%= r.description %></p>
<div id="extra_<%= r.id %>"></div>
<%= link_to_remote 'Extra',
:url => { :action => 'get_extra_ajax', :id => r.id },
:update => "extra_#{r.id}" %>
<% } %>
|
The link_to_remote() method adds JavaScript code onto the
page along with an anchor (<a>) tag containing the
specified text. When the customer clicks the link, the page makes the Ajax request
to get the new content and replaces the anchor text with the content.
To get the extra information, I must add one more method to the
RecipesController called get_extra_ajax().
This method is shown in Listing 12.
Listing 12. Recipes_controller.rb
class RecipesController < ApplicationController
...
def get_extra_ajax
@recipe = Recipe.find( params[:id] )
render :layout=>false
end
end
|
And along with that, I need a template for the extra information called get_extra_ajax.rhtml. Listing 13 shows this template.
Listing 13. Get_extra_ajax.rhtml
<blockquote><%= simple_format @recipe.ingredients %></blockquote>
<p><%= simple_format @recipe.instructions %></p>
|
Now, when I go to the page and type apple, I see
something like Figure 3.
Figure 3. The additional extra link that gets the ingredients and instructions
Then, when I click the link, the browser uses Ajax to retrieve the additional material from the Web server and display it in that location. You can see this process in Figure 4.
Figure 4. The extra details on the recipe
This type of Ajax pattern is particularly handy when you have a line item or detail type report in which finding the detail of each record is costly, and therefore, doing so on demand is ideal. Plus, this technique saves on screen real estate.
Perhaps the hottest of the Web 2.0 features is the auto-complete text field. What Ajax walkthrough would be complete without one of those?
Rails makes building auto-complete fields ridiculously simple. First, I must add a few things to the index.rhtml template. The updated version is shown in Listing 14.
Listing 14. The update index.rhtml
<html>
<head>
<%= javascript_include_tag :defaults %>
<style>
div.auto_complete {
width: 300px;
background: #fff;
}
div.auto_complete ul {
border: 1px solid #888;
margin: 0px;
padding: 0px;
width: 100%;
list-style-type: none;
}
div.auto_complete ul li {
margin: 0px;
padding: 3px;
}
div.auto_complete ul li.selected {
background-color: #ffb;
}
div.auto_complete ul strong.highlight {
color: #800;
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<%= form_tag nil, { :id => 'search_form' } %>
<p><%= text_field 'recipe', 'name', :autocomplete => 'off' %></p>
<div class="auto_complete" id="recipe_name_auto_complete"></div>
<%= auto_complete_field :recipe_name,
:url => { :action=>'autocomplete_recipe_name' },
:tokens => ',' %>
<%= end_form_tag %>
...
|
The Cascading Style Sheets (CSS) style block at the top of the file is used for
the drop-down list of auto-complete items. After that, I made one small addition
to the text_field to turn off the browser's automatic
auto-completion. I added a <div> where the
drop-down material will go and a call to the auto_complete()
method. The auto_complete() helper method creates the
client-side JavaScript code that calls the autocomplete_recipe_name()
method on the server with the current contents of the recipe name text
field.
The autocomplete_recipe_name() method on the
RecipesController runs the search for the name as shown
in Listing 15.
Listing 15. Recipes_controller.rb
class RecipesController < ApplicationController
...
def autocomplete_recipe_name
@recipes = Recipe.find( :all,
:conditions => [ "name LIKE ?",
"%#{params[:recipe][:name]}%" ] )
render :layout=>false
end
end
|
This code requires one more ERB template to build the list, as shown in Listing 16.
Listing 16. autocomplete_recipe_name.rhtml
<ul class="autocomplete_list">
<% @recipes.each { |r| %>
<li class="autocomplete_item"><%= r.name %></li>
<% } %>
</ul>
|
The auto-complete system is looking for an HTML list (<ul>)
of items in which each item is one option. These are formatted using the CSS on the
index.rhtml page (or a style sheet that you provide).
To see the auto-complete system in action, I use my browser to surf to the page,
then type test. I put in test recipes just to have
some data. You can see the results in Figure 5.
Figure 5. The drop-down auto-complete list
I can use the up and down arrows to select an option and the Enter key to confirm the selection. Doing so copies the content of the selected item back into the text field.
It's slick, and thanks to the architecture of Rails, it's easy to implement.
I'm not going to be shy about it: I love Rails. I was hooked from the first moment I started using it. From what I have seen around the Web, a lot of other developers are hooked, as well. And why shouldn't they be? Rails makes building highly interactive Web applications a breeze.
Even if you don't envision yourself shipping a Rails application, I recommend that you download one of the Instant Rails or Locomotive applications and try it out. You will have a lot of fun and learn a lot that you can probably use in your own Java™ PHP, or Microsoft .NET application. You might even find that you want to write Rails full time.
Learn
-
Ruby on Rails.org: Visit the home site for Rails.
-
Ruby home page: Visit the official source for material on the language plus downloads.
-
Agile Development with Rails
: Agile Development with Rails (Dave Thomas et al., Pragmatic Bookshelf, 2006): Explore this must-read book on the topic of Rails.
-
developerWorks XML zone: Learn all about XML at the developerWorks XML zone.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
Get products and technologies
-
Instant Rails: Try Instant Rails, your all-in-one starter kit for Windows.
-
Locomotive: Get Locomotive, the starter kit for Mac OS X. (Rails will shipg with Mac OS X V10.5 Leopard.)
Discuss
-
Ajax discussion forum: Participate in discussions on Ajax topics. Post questions or help others.
Jack D. Herrington is a senior software engineer with more than 20 years of experience. He's the author of three books: Code Generation in Action, Podcasting Hacks, and PHP Hacks. He has also written more than 30 articles. You can reach Jack at jherr@pobox.com.
Comments (Undergoing maintenance)





