Skip to main content

Build Ajax into your Web apps with Rails

Practical examples of how to use Ajax with Ruby on Rails

Jack D Herrington (jherr@pobox.com), Senior Software Engineer, Leverage Software Inc.
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.

Summary:  Ruby on Rails provides an excellent platform for building Web applications. Discover how to use the built-in Asynchronous JavaScript™ + XML (Ajax) features of the platform to give your application the Web 2.0 rich user interface experience.

Date:  19 Dec 2006
Level:  Intermediate
Activity:  5224 views

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.

A bit about Rails

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.


Getting started with Rails

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.


Ajax forms

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
Ajax form

The next step is to play around with more dynamic interactivity -- for example, dynamic searching with Ajax.


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
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!


Adding content on demand

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
Extra link

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
Recipe details

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?


Auto-complete fields

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
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.


Conclusion

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.


Resources

Learn

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

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Web development
ArticleID=183920
ArticleTitle=Build Ajax into your Web apps with Rails
publish-date=12192006
author1-email=jherr@pobox.com
author1-email-cc=dwxed@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers