Customizing Rails Applications
on Mac OS X Leopard
In Developing Rails Applications on Mac OS X Leopard we created a web application using the latest Rails features with Xcode 3.0. In this article, we'll go into customizing the views, working with web forms, and adding AJAX and iPhone support.
This is the second in a series of three articles:
- Development, where you learn to build a basic RESTful Rails application using Xcode 3.0;
- Customization, this article, where we discuss working with views and web forms, adding AJAX support, and supporting an iPhone interface;
- Deployment, where we set up version control, write a Capistrano recipe, and deploy on Leopard Server.
Together they will give you a great start in working with Rails on Mac OS X Leopard.
Customizing Views
Previously, we added expenses to an event in script/console
and
implemented business logic to calculate the total expenses for the event.
However, we don't see an event's expenses when we show an event in the
browser. That's because we're still using the scaffold-generated
show
template to display the event's details. Scaffolding is a
good start, but it's meant to be customized.
Partial Templates
To list an event's expenses at the bottom of the event's show
template, we're going to use a partial template. Partials are a great
way to decompose view templates for better maintainability and reuse. In fact,
we'll use this partial template again a bit later when we sprinkle in some
AJAX.
Create the partial template by clicking on the app/views/events folder in the Organizer, right-mouse click, and choose "New File". Name the partial template file _expenses.erb (note that partial files always begin with an underscore). Then paste the following into the new file:
<table> <% for expense in expenses -%> <tr id="<%= dom_id(expense) %>"> <td> <%= link_to expense.vendor.name, expense.vendor %> </td> <td align="right"> <%= number_to_currency(expense.amount) %> </td> </tr> <% end -%> <tr> <td align="right" colspan="2"> <strong>Total</strong>: <%= number_to_currency(event.total_expenses) %> </td> </tr> </table>
The partial's job is fairly easy: It loops through all the expenses
in the expenses
array,
generating an HTML table row for each expense. Notice the use of the built-in
dom_id
helper to generate a unique HTML element id for each
expense row. We've also used the built-in number_to_currency
view
helper to display the expense amount as dollars.
Next, call the partial at the bottom of the event's show
template by adding the following to the end of the
app/views/events/show.html.erb
file:
<h3>Itemized Expenses</h3> <div id="expenses"> <%= render :partial => 'expenses', :locals => {:expenses => @event.expenses, :event => @event} %> </div>
Calling a partial template is similar to calling a method or subroutine: we
give the name of the partial template and any data the partial needs to do its
work. The name is expenses
, which by convention corresponds to
the app/views/events/_expenses.erb
partial template file. The
partial needs an event's expenses and the event itself, which we assign to the
local variables expenses
and event
respectively.
View Helpers
At this point, we have everything in place to list an event's expenses. Now
let's add a little icing. The last row of the _expenses.erb
partial template shows the total of all expenses. When this row is generated,
we want to change its style based on the expense total. If the expense total
is greater than the event budget, then the total should be displayed in red.
Otherwise, the total should be displayed in black. (The club treasurer likes
it better this way.)
We could put this view-specific logic directly in the
_expenses.erb
partial template, but there's a better way.
Instead, we'll write a helper method and call it from the partial
template.
Update the app/helpers/events_helper.rb
file as follows:
module EventsHelper def expense_total_style(event) event.budget_exceeded? ? 'color: red' : 'color: black' end end
This helper method simply calls the budget_exceeded?
method we
previously added to the Event
model. If it returns
true
, the CSS style for red text is returned. Otherwise, the CSS
style for black text is returned. It's a trivial amount of code that we could
have slapped right into the template, but making it into a helper means we can
reuse it in other views that show expense totals.
Then, in the app/views/events/_expenses.erb
partial template
file, change the last row of the table to call the
expense_total_style
view helper method:
<strong>Total</strong>: <span id="total" style="<%= expense_total_style(event) %>"> <%= number_to_currency(event.total_expenses) %> </span>
Time for some gratification! Navigate to the show
page for one of
the events you added previously using script/console
. If you
added enough expenses to bust the budget, you should see something similar to
the page shown in Figure 12.

Figure 12: Customized view using a partial template and a view helper
Working with Web Forms
We don't yet have a way to record expenditures for an event using the web interface. We need a good ol' web form to enter expenses. Again, the scaffold-generated code is a good place to learn how to get started.
Basic Forms
Forms for creating and updating a single resource such as an event are fairly
straightforward. For example, in the app/views/events/new.html.erb
file you'll see:
<%= error_messages_for :event %> <% form_for(@event) do |f| %> <p> <b>Name</b><br /> <%= f.text_field :name %> </p> <p> <b>Budget</b><br /> <%= f.text_field :budget %> </p> <p> <%= f.submit "Create" %> </p> <% end %>
This template generates an HTML form using built-in form helpers. The
form_for
helper generates an HTML form tag for an event. The
text_field
helper generates the HTML input tag bound to the
corresponding attributes of the event. (Rails includes form helpers for all
the standard HTML form elements.) Finally, the submit
helper
generates a submit button.
In this case, because the new
template is used to create new
events, the form posts its data to the create
action of the
EventsController
in
app/controllers/events_controller.rb:
def create @event = Event.new(params[:event]) respond_to do |format| if @event.save flash[:notice] = 'Event was successfully created.' format.html { redirect_to @event } ... else format.html { render :action => "new" } ... end end end
Inside the create
action, all the form data for the
event
is available in params[:event]
. With this data
in hand, the create
action simply instantiates a new
Event
model object with the form data and attempts to save the
event to the database. If the event isn't valid, the save
operation will fail and the new
action's template is re-rendered
to show the form with errors. If the event is valid, it's saved in the
database and we get redirected to the event's show
page.
Nested Resource Forms
Now let's use what we've learned to build a new form to record expenses for an
event. It's always good to start with a goal in mind, then write the code that
gets you there. We want the form shown in Figure 13 to appear at the bottom of
the event's show
page.

Figure 13: Recording expenses through a web form
For that to work, the form needs some data: an empty Expense
object and the names of all the vendors in our database. Setting up data for a
view template is a controller action's job, so we'll start there.
Update the show
action of the EventsController
in
app/controllers/events_controller.rb to create
@expense
and @vendors
instance variables as follows:
def show @event = Event.find(params[:id]) @expense = Expense.new @vendors = Vendor.find(:all, :order => 'name') respond_to do |format| format.html # show.html.erb format.xml { render :xml => @event } end end
Next we need to think a little about how we'll interact with expenses. Every expense is associated with an event. Whenever we do anything with an expense, it's in the context of an event. We can represent this event/expenses nesting in the URLs using a nested route.
Update the config/routes.rb
file to nest expenses within events as
follows:
map.resources :events, :has_many => :expenses map.resources :vendors
Then add the following form to the bottom of the
app/views/events/show.html.erb
file:
<h3>Add an expense to this event</h3> <p style="color: red"><%= flash[:error] %></p> <% form_for [@event, @expense] do |f| -%> <p> <%= f.collection_select :vendor_id, @vendors, :id, :name %> in the amount of $<%= f.text_field :amount, :size => 9 %> </p> <%= f.submit 'Add this expense' %> <% end -%>
Notice that we're using form_for
to generate a form for two
resources: event and expense. Remember, we always work with expenses in the
context of their event, so the form needs both objects. The generated HTML
form tag looks like this:
<form action="/events/1/expenses" method="post">
According to the RESTful routing rules we have in place, this form will post
to the create
action of the ExpensesController
.
Add the following create
action to the
ExpensesController
in
app/controllers/expenses_controller.rb:
def create @event = Event.find(params[:event_id]) @expense = @event.expenses.build(params[:expense]) respond_to do |format| if @expense.save flash[:notice] = 'Expense was successfully created.' format.html { redirect_to @event } else flash[:error] = @expense.errors.full_messages.to_sentence format.html { redirect_to @event } end end end
Inside the create
action, the event id is available in
params[:event_id]
and the expense form data is available in
params[:expense]
. After finding the existing event in the
database, we use build
to populate a new Expense
model object from the posted form data and add the expense to the event's
collection of expenses. Then we redirect the browser to the event's
show
page to display the event and all its expenses.
Go ahead and use the web form on one of your event's show page to add
expenses to the event. You can get insight into what's going on behind the
scenes (including the SQL that's being run) by watching the
log/development.log
file. It's tailed automatically in the
Debugger window that opened when you started the application in the Organizer.
Spicing It up with AJAX
Recording a new expense currently forces a reload of the entire show
page. That is, when we submit the form it sends a synchronous request back to the server which adds the expense, issues a full redirect, and re-renders the show
template back to our browser. It works, but we can improve the user experience with a bit of AJAX.
When you create a new Rails application, the Prototype and Script.aculo.us
JavaScript libraries come along for the ride. You'll find them in the
public/javascripts
directory. Better yet, Rails includes helpers that
let you tap into the power of these libraries at a fairly high level.
Again, we'll start with a goal. When a new expense is added to an event, we want to
asychronously update the event's show
page to
- Add the expense to the list of itemized expenses
- Update the expense total
- Highlight the new expense and the updated total
Start by adding the following to the <head> section of the
app/views/layouts/events.html.erb
file to load all the JavaScript
libraries:
<%= javascript_include_tag :defaults %>
Then update app/views/events/show.html.erb
to replace
form_for
with remote_form_for
, like so:
<% remote_form_for [@event, @expense] do |f| -%>
Reload the page and view the source, and you'll see the Prototype code that
remote_form_for
generates. If you were to submit the form, the
browser would expect JavaScript code to be returned. So in the create
action of the ExpensesController
we need to update the
respond_to
block to respond appropriately. Here's the updated
create
action in app/controllers/expenses_controller.rb:
def create @event = Event.find(params[:event_id]) @expense = @event.expenses.build(params[:expense]) respond_to do |format| if @expense.save flash[:notice] = 'Expense was successfully created.' format.html { redirect_to @event } format.js # renders create.js.rjs else format.html { redirect_to @event } format.js do render :update do |page| page.redirect_to @event end end end end end
If the expense is successfully created, the action will render the
create.js.rjs
template. Otherwise, it redirects back to the event's
show
page.
Create the app/views/expenses/create.js.rjs
file and add the
following:
page[:expenses].replace_html :partial => 'events/expenses', :locals => {:expenses => @event.expenses, :event => @event} page[@expense].highlight page[:total].highlight
This RJS template is Ruby code that generates JavaScript. The
page
object is the real workhorse. First, it generates JavaScript
(Prototype code) to update the contents of the HTML element on our
show
page that has the id expenses
. Notice that
we're reusing the app/views/events/_expenses.erb
partial template
we created earlier to generate the table of expenses. Then the
page
object generates JavaScript (Script.aculo.us code) to
perform highlighting effects on the table row that contains the newly-added
expense and the total.
All this happens behind the scenes. If you add a new expense, you won't see Safari's address bar update. Instead, the JavaScript returned from the server is evaluated in the browser to update the page in place.
Now let's quickly repeat the cycle to delete expenses using AJAX, this time using a hyperlink.
In the app/views/events/_expenses.erb
template, add a column to each
expense row that includes a link to delete the expense, like this
<%= link_to_remote 'delete', :url => event_expense_url(event, expense), :confirm => 'Are you sure?', :method => :delete %>
The link_to_remote
helper generates JavaScript to send an
asynchronous request when the hyperlink is clicked. According to the RESTful
routing rules and use of the delete
method, the request will be
routed to the destroy
action of the ExpensesController
. Add the following destroy action to
the app/controllers/expenses_controller.rb file:
def destroy @event = Event.find(params[:event_id]) @expense = @event.expenses.find(params[:id]) @expense.destroy respond_to do |format| flash[:notice] = 'Expense was successfully deleted.' format.html { redirect_to @event } format.js # renders destroy.js.rjs end end
The RJS template is similar to the one we created earlier. The only difference
is it doesn't need to highlight an expense, just the updated total. Add the following RJS code to the app/views/expenses/destroy.js.rjs
file:
page[:expenses].replace_html :partial => 'events/expenses', :locals => {:expenses => @event.expenses, :event => @event} page[:total].highlight
Now when you click the link to delete an expense, it'll happen in the background. You'll know it worked when the list of expenses is dynamically updated and the total is highlighted.
AJAX can be tricky to debug, so do yourself a favor by getting your application working without AJAX first. Then sprinkle in AJAX when and where it benefits the user.
Integrating with Other Systems
Let's now imagine we'd like to introduce this application to another, and let them speak XML to each other. For example, suppose we have an existing accounting system that will actually pay the event expenses. To do that, it needs to talk to our new application to get the expenses.
Here's where things get interesting. The RESTful conventions give us a common
lingo for accessing our resources. And the respond_to
block gives us
a way to vary how those resources are represented. So without any changes to
our application, the accounting system (the client program) can fetch all the
events from our expenses application simply by tacking .xml
to the
end of the URL:
http://localhost:3000/events.xml
The response is an XML document representing the events. Here's an example:
<?xml version="1.0" encoding="UTF-8"?> <events type="array"> <event> <budget type="decimal">150.0</budget> <id type="integer">1</id> <name>Chili Cookoff</name> </event> <event> <budget type="decimal">25.0</budget> <id type="integer">2</id> <name>Car Wash</name> </event> </events>
This works out of the box for two reasons: 1) Active Record models can be
serialized as XML and 2) the relevant actions of the scaffold-generated
EventsController
include format.xml
stanzas to respond to
requests for XML. Here's what the index
action does, for example:
format.xml { render :xml => @events }
Of course the accounting system needs the expenses for each event, too. To do
that, add the following index
action to the
ExpensesController
in app/controllers/expenses_controller.rb:
def index @event = Event.find(params[:event_id]) @expenses = @event.expenses respond_to do |format| format.html # index.html.erb format.xml { render :xml => @expenses } end end
We can then get an XML representation of the expenses for an event using a URL that includes the event's identifier, for example:
http://localhost:3000/events/1/expenses.xml
The response is another XML document. Here's an example:
<?xml version="1.0" encoding="UTF-8"?> <expenses type="array"> <expense> <amount type="decimal">75.0</amount> <event-id type="integer">1</event-id> <id type="integer">1</id> <vendor-id type="integer">1</vendor-id> </expense> <expense> <amount type="decimal">25.0</amount> <event-id type="integer">1</event-id> <id type="integer">2</id> <vendor-id type="integer">2</vendor-id> </expense> <expense> <amount type="decimal">65.0</amount> <event-id type="integer">1</event-id> <id type="integer">3</id> <vendor-id type="integer">3</vendor-id> </expense> </expenses>
The accounting system (or any other system) might also want to create, update, or delete events and expenses. Doing that would require sending a hunk of XML to the appropriate resource with the corresponding HTTP verb. However, working with raw XML isn't necessarily convenient. Instead, we'd like a Ruby program that handles all that for us. Enter Active Resource.
An Active Resource client is a standalone Ruby program. It doesn't need Rails, just the Active Resource gem that comes with Rails. Here's an example program that prints all the event names and updates the budget of one event:
require 'rubygems' require 'activeresource' class Event < ActiveResource::Base self.site = "http://localhost:3000" end events = Event.find(:all) puts events.map(&:name) e = Event.find(1) e.budget = 160.00 e.save
Active Resource is like Active Record, but for resources—it uses RESTful
conventions to access Rails resources. All the standard CRUD-level operations
are available, as if the Event
proxy class was a real Active Record
model.
Put this code in a file called event_client.rb
, for example, and then
just run it by typing
$ ruby event_client.rb
Supporting an iPhone Interface
We've used the respond_to
block to handle requests for HTML, XML, and
JavaScript. These are built-in MIME types. We can take this a step further by
adding new MIME types and then using the format in the respond_to
block.
For example, suppose we want to support an optimized HTML interface of our expenses application for Mobile Safari browsers: the iPhone and iPod Touch. Let's give it a try!
Start by adding the following line in the config/initializers/mime_types.rb
file:
Mime::Type.register_alias "text/html", :mobilesafari
This defines the mobilesafari
format based on the existing text/html
MIME type.
Next, update your app/controllers/application.rb
file as follows:
class ApplicationController < ActionController::Base helper :all protect_from_forgery before_filter :adjust_format_for_mobilesafari private def adjust_format_for_mobilesafari if request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(Mobile\/.+Safari)/] request.format = :mobilesafari end end end
This code uses a Rails before filter to check the user agent of each
incoming request to see if it's a Mobile Safari device calling. If so, we set
the request format to the :mobilesafari
format.
Then update the index
action of the EventsController
in app/controllers/events_controller.rb
as follows to support listing all the events on Mobile Safari devices:
def index @events = Event.find(:all) respond_to do |format| format.html # renders index.html.erb format.xml { render :xml => @events } format.mobilesafari # renders index.mobilesafari.erb end end
Finally, create a app/views/events/index.mobilesafari.erb
template file which generates a Mobile Safari-specific response.
You can test that the mobilesafari
format is being properly handled by pointing Safari at
http://localhost:3000/events.mobilesafari
You'll need to add a format.mobilesafari
line to the
respond_to
block of each action in your application, and write
Mobile Safari-specific template files that follow the
mobilesafari.erb
naming convention. Redesigning the user
interface of the expenses application for Mobile Safari devices is beyond the
scope of this article. Be sure to review the
iPhone Human Interface Design Guidelines for Web Applications.
Now check out the third article in this series, Deploying Rails Applications on Leopard, where we finish up by deploying this Rails application on Mac OS X Leopard Server.
Updated: 2008-09-29