Advanced Rails Recipes

Almost a year ago, I submitted three recipes to Advanced Rails Recipes. Unfortunately, one which was originally selected back in June has been dropped from the book as it’s not really advanced. So I thought I’d post it here for everyone to enjoy.

Using AJAX with REST

Problem:

You have finally mastered REST (Representational State Transfer) and wish to enhance your application with JavaScript but don’t know which of the resource URLs to call.

Ingredients:

This recipe requires Rails 1.2 or later as it relies on RJS for the Ajax portion and REST for the controller design.

Solution:

In this example we need users to be able to register (and unregister) for meetings so that we can properly report attendance. So we have three models: User, Meeting and Registration.

Since Registrations are associated with a specific meeting, we’ve created them as a nested resource, like so:

  1.  
  2.   map.resources :meetings do |meetings|
  3.     meetings.resources :registrations
  4.   end
  5.  

To register for a meeting we want to create a new Registration instance tied to the Meeting and User. Unregistering from a meeting should destroy the Registration object. So our RegistrationsController looks like this:

  1.  
  2.   # POST /registrations
  3.   def create
  4.     @meeting = Meeting.find(params[:meeting_id])
  5.     current_user.register_for(@meeting)
  6.     respond_to do |format|
  7.       format.html { redirect_to meetings_url }
  8.       format.js   # create.rjs
  9.       format.xml  { head :ok }
  10.     end
  11.   end
  12.  
  13.   # DELETE /registrations/1
  14.   def destroy
  15.     @registration = Registration.find(params[:id])
  16.     @registration.destroy
  17.     respond_to do |format|
  18.       format.html { redirect_to meetings_url }
  19.       format.js   # destroy.rjs
  20.       format.xml  { head :ok }
  21.     end
  22.   end
  23.  

In the view we want to use Ajax to allow the user to modify their registration, so we need to use link_to_remote:

  1.  
  2.   <% if !current_user.registered_for(meeting) -%>
  3.     <%= link_to_remote "Register!", :url => registrations_path(meeting),
  4.                                   :meeting_id => meeting.id, :method => :post %>
  5.   <% else -%>
  6.     <%= link_to_remote "Unregister",
  7.                                   :url => registration_path(meeting,
  8.                                              current_user.registration_for(meeting))
  9.                                   :confirm => ‘Are you sure?’, :method => :delete %>
  10.   <% end -%>
  11.  

The RJS to update the page looks like this:

  1.  
  2. page[" meeting_#{@meeting.id}".to_sym].replace_html
  3.        :partial => ’shared/meeting’, :object => @meeting
  4. page[" meeting_#{@meeting.id}".to_sym].visual_effect :highlight, :duration => 5
  5.  

Discussion:

The resource URLs provided by Rails are extremely useful, but they can be confusing. Tools like FireBug make it easy to see what is going on behind the scenes. When developing Rails applications, it is usually easier to develop without Ajax and then go back and add Ajax to those portions of the application where it would make sense. With this approach, you can gracefully degrade your service back to the original mode for those who don’t have or don’t enable JavaScript. It also allows you to click through your application with FireBug enabled and watch which URLs are hit and with which methods (GET, POST, PUT, or DELETE).

The table below outlines which resource URLs to use with your remote helpers:

Intent Action Resource URL Method
Creating a new instance create plural form of model name (i.e. registrations_path) POST
Retrieving an instance show singular form of model name with id (i.e. registration_path(registration) GET
Retrieving all instances index plural form of model name (i.e. registrations_path) GET
Modifying an existing instance update singular form of model name with id (i.e. registration_path(registration)) PUT
Deleting an existing instance destroy singular form of model name with id (i.e. registration_path(registration)) DELETE

Once you are comfortable with the resource URLs, adding Ajax to your RESTful is much easier.

Further Reading:

If you are unfamiliar with REST, read Chapter 20 of Agile Web Development with Rails, 2nd Edition by Dave Thomas and David Heinemeier Hansson (Pragmatic Programmers, 2006). There is also a nice screencast on the subject from PeepCode.

For more on Ajax in Rails, you can check out Ajax on Rails by Scott Raymond (O’Reilly & Associates, 2007).