- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index
THE RAILS WAY 376
XMLHttpRequest vs. <iframe>
So, you ask, what’s all the hype about? I did this with <iframe> for years!
While it’s true you can do something along the lines of what XMLHttpRequest does, iframes are not nearly as flexible nor as clean as AJAX to use. Unlike the <iframe> approach, with AJAX
•it’s easy to do GET, POST, and other HTTP request types,
•the DOM is not altered in any way,
•you have powerful callback hooks,
•there’s a clean API, and
•you can customize HTTP headers.
Considering all this, it’s obvious that XMLHttpRequest provides a far cleaner and more powerful programming model than that of iframes.
18.2 The Rails Way
Rails has built-in support for AJAX calls, which makes it very easy to put your application on track with the Web, version 2.0.
First of all, it has the prototype,4effects, dragdrop, and controls JavaScript |
prototype |
libraries built-in. These library neatly wrap all sorts of useful AJAX and |
|
DOM manipulation stuff in a nice, object-oriented way. |
|
The second thing is JavascriptHelper, a module that defines the methods |
JavascriptHelper |
we’ll be looking at in the rest of this chapter. It wraps JavaScript access in |
|
pristine Ruby code, so you won’t have to switch to another language when |
|
using AJAX. Talk about total integration. |
|
To use any of the functions defined by JavascriptHelper, you first have to |
|
include the prototype.js file in your application. Do this by making this call |
|
in the <head> section of your .rhtml page. |
|
<%= javascript_include_tag "prototype" %>
For the code in this chapter, we’ve added the call to javascript_include_tag to our overall application.rhtml layout file, making the library available to all of our examples.
4http://prototype.conio.net
Prepared exclusively for Rida Al Barazi
Report erratum
|
THE RAILS WAY |
377 |
|
You also need the prototype.js file in your application’s public/javascripts |
|
|
directory. It’s included by default if you generate your application’s struc- |
|
|
ture by running the rails command. |
|
|
link_to_remote |
|
|
The syntax for making a basic AJAX call from an .rhtml template can be as |
|
|
simple as |
|
File 196 |
<%= link_to_remote("Do the Ajax thing", |
|
|
:update => 'mydiv', |
|
|
:url => { :action => :say_hello }) %> |
|
|
<div id="mydiv">This text will be changed</div> |
|
|
This basic form of the link_to_remote( ) method takes three parameters. |
|
|
• The text for the link |
|
|
• The id= attribute of the element on your page to update |
|
|
• The URL of an action to call, in url_for( ) format |
|
|
When the user clicks on the link, the action (say_hello in this case) will be |
|
|
invoked in the server. Anything rendered by that action will be used to |
|
|
replace the contents of the mydiv element on the current page. |
|
|
The view that generates the response should not use any Rails layout |
|
|
wrappers (because you’re updating only part of an HTML page). You can |
|
|
disable the use of layouts by making a call to render( ) with the :layout option |
|
|
set to false or by specifying that your action shouldn’t use a layout in the |
|
|
first place (see Section 17.9, Locating Layout Files, on page 357, for more |
|
|
on this). |
|
|
So, let’s define an action. |
|
File 186 |
def say_hello |
|
|
render(:layout => false) |
|
|
end |
|
|
And then define the corresponding say_hello.rhtml view. |
|
File 200 |
<em>Hello from Ajax!</em> (Session ID is <%= session.session_id %>) |
|
|
Try it. The text “This text will be changed” in the <div> element with |
|
id="mydiv" will be changed (see Figure 18.2, on the following page) to something like
Hello from Ajax! (Session ID is <some string>)
It’s that easy. The session id is included to show off one more thing— cookie handling. Session information is handled transparently by the
Prepared exclusively for Rida Al Barazi
Report erratum
THE RAILS WAY 378
Figure 18.2: Before and After Calling the Action via AJAX
underlying XMLHttpRequest. You’ll always get the correct user’s session, regardless of whether an action is called by AJAX or not.
Behind the Scenes
Let’s have a look at what happened during our link_to_remote example.
First, let’s take a quick look at the HTML code generated by link_to_remote( ).
<a href="#" onclick="new Ajax.Updater('mydiv', '/example/say_hello', {asynchronous:true}); return false;">
Do the AJAX thing
</a>
link_to_remote( ) generates an HTML <a> tag that, when clicked, generates a new instance of Ajax.Updater (which is defined in the Prototype library).
This instance calls XMLHttpRequest internally, which in turn generates an HTTP POST request to the URL given as second parameter.5 This process is shown in Figure 18.3, on the next page.
Let’s see what happens on the server.
127.0.0.1 - - [21/Apr/2005:19:55:26] "POST /example/say_hello HTTP/1.1" 200 51
5For security reasons you can safely call URLs only on the same server/port as the page that includes the call to XMLHttpRequest.
Prepared exclusively for Rida Al Barazi
Report erratum
THE RAILS WAY 379
Browser |
new |
|
|
|
XMLHttpRequest |
|
|
|
|
XMLHttpRequest |
|
Do something with |
||
Ajax.Request() |
|
|
|
send() |
|
|
|
readyState == complete |
|
the returned HTML |
||||
|
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
asynchronous (non-blocking) call |
|
|
raises event |
|
|
|
||||
Server |
|
|
|
|
|
|
|
|
|
|
HTML |
|
|
|
|
|
|
|
|
Rails action |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Figure 18.3: XMLHttpRequest Connects Browser and Server
The web server (WEBrick, in this case) got an HTTP POST request to call /example/say_hello. From the server’s perspective this looks just like a normal, run-of-the-mill HTTP POST. That’s not surprising, because that’s what it is.
The server then returns the output of the action being called (in this case say_hello( )) to the XMLHttpRequest object that got created behind the scenes by link_to_remote( ). The Ajax.Updater instance takes over and replaces the contents of the element given as first parameter (in this case mydiv) with the data returned from the XMLHttpRequest object. The browser updates the page to reflect the new content. As far as the user is concerned, the page simply changes.
form_remote_tag()
You can easily change any Rails form to use AJAX by replacing the call to form_tag( ) with form_remote_tag( ).
This method automatically serializes all form elements and sends them to the server, again using XMLHttpRequest. No change to your action is required—it simply receives its data as it normally would.6
Let’s build a game. says “Ruby on ...” the controller.
The object is to complete a simple phrase: the game and the user has to supply the missing word. Here’s
File 187 |
class |
GuesswhatController < ApplicationController |
|
def |
index |
|
end |
|
|
def |
guess |
@guess = params[:guess] || ''
if @guess.strip.match /^rails$/i
6There is one exception: you can’t use file upload fields with form_remote_tag( ), because JavaScript can’t get at file contents. This is a security (and performance) constraint imposed by the JavaScript model.
Prepared exclusively for Rida Al Barazi
Report erratum
THE RAILS WAY 380
render(:text => "You're right!") else
|
render(:partial => 'form') |
|
end |
|
end |
|
end |
|
The index.rhtml template file looks like this. |
File 204 |
<h3>Guess what!</h3> |
|
<div id="update_div" style="background-color:#eee;"> |
|
<%= render(:partial => 'form') %> |
|
</div> |
|
Finally, the main part of this hip new game that will make you rich and |
|
famous is the _form.rhtml partial. |
File 203 |
<% if @guess %> |
|
<p>It seems '<%=h @guess %>' is hardly the correct answer</p> |
|
<% end %> |
|
<%= form_remote_tag(:update => "update_div", |
|
:url |
=> { :action => :guess } ) %> |
<label for="guess">Ruby on .....?</label> |
||
<%= |
text_field_tag :guess %> |
|
<%= |
submit_tag "Post form with AJAX" %> |
|
<%= end_form_tag %> |
|
Try it out—it’s not too hard to find the answer, as shown in Figure 18.4, on the following page.
form_remote_tag( ) is a great way to add on-the-fly inline forms for things such as votes or chats to your application, all without having to change anything about the page it’s embedded in.
Partial templates help you honor the DRY principle—use the partial when initially displaying the form, and use it from your AJAX’d action. No change necessary.
Observers
Observers let you call AJAX actions when the user changes data on a form or in a specific field of a form. You can put this to good use to build a real-time search box.
File 198 |
<label for="search">Search term:</label> |
|
<%= text_field_tag :search %> |
|
<%= observe_field(:search, |
:frequency |
=> 0.5, |
:update |
=> :results, |
:url |
=> { :action => :search }) %> |
<div id="results"></div> |
|
Prepared exclusively for Rida Al Barazi
Report erratum
THE RAILS WAY 381
Figure 18.4: AJAX-Style Forms Update Inside Existing Window
Prepared exclusively for Rida Al Barazi
Report erratum
THE RAILS WAY 382
Figure 18.5: Build Real-Time Searches with Observers
The observer waits for changes to the given form field, checking every :frequency seconds. By default, observe_field uses the current value of the text field as the raw POST data for the action. You can access this data in your controller using request.raw_post.
Having set up the observer, let’s implement the search action. We want to implement a search over a list of words in an array, with nice highlighting of the search term in the result.
File 186 |
WORDLIST = %w(Rails is a full-stack, open-source web framework in Ruby |
|
|
for writing real-world applications with joy and less code than most |
|
|
frameworks spend doing XML sit-ups) |
|
File 186 |
def search |
|
|
@phrase |
= request.raw_post || request.query_string |
|
matcher |
= Regexp.new(@phrase) |
|
@results = WORDLIST.find_all { |word| word =~ matcher } |
|
|
render(:layout => false) |
|
|
end |
|
|
The view, in search.rhtml, looks like this. |
File 201 |
<% if @results.empty? %> |
|
'<%=h @phrase %>' not found! |
|
<% else %> |
|
'<%=h @phrase %>' found in |
|
<%= highlight(@results.join(', '), @phrase) %> |
|
<% end %> |
|
Point your browser at the observer action, and you’ll get a nice text field |
|
with real-time search capability (see Figure 18.5 ). Note that in this exam- |
|
ple, the search supports regular expressions. |
Prepared exclusively for Rida Al Barazi
Report erratum
|
|
THE RAILS WAY |
383 |
|
The @phrase = request.raw_post || request.query_string line allows you to test |
|
|
|
your search by entering a URL such as /controller/search?ruby directly in |
|
|
|
the browser—the raw POST data won’t be present, so the action will use |
|
|
|
the query string instead. |
|
|
|
The action invoked by an observer shouldn’t be overly complex. It might |
|
|
|
get called very often, depending on the frequency you set and how quickly |
|
|
|
your user types. In other words, avoid heavy database lifting or other |
|
|
|
expensive operations. Your user will thank you for it too, as he or she will |
|
|
|
experience a snappier interface. |
|
|
|
Periodic Updates |
|
|
|
The third helper function, periodically_call_remote( ), helps if you want to |
|
|
|
keep part of your page refreshed by periodically calling the server via AJAX. |
|
|
|
As an example, we’ll show a process list from the server, updating it every |
|
|
|
couple of seconds. This example uses the ps command, so it’s fairly Unix- |
|
|
|
specific. Putting the command in backquotes returns its output as a |
|
|
|
string. Here’s the controller. |
|
|
File 186 |
def periodic |
|
|
|
# No action... |
|
|
|
end |
|
|
|
# Return a process listing (Unix specific code) |
|
|
|
def ps |
|
|
|
render(:text => "<pre>" + CGI::escapeHTML(‘ps -a‘) + "</pre>") |
|
|
|
end |
|
|
|
And here’s the periodic.rhtml template. This contains the call to periodi- |
|
|
|
cally_call_remote( ). |
|
|
File 199 |
<h3>Server processes:</h3> |
|
|
|
<div id="process-list" style="background-color:#eee;"> |
|
|
|
</div> |
|
|
|
<%= periodically_call_remote(:update |
=> 'process-list', |
|
:url |
=> |
{ |
:action => :ps }, |
:frequency => |
2 |
)%> |
If you’ve paid extra for the embedded web server version of this book, you’ll see Figure 18.6, on the following page update the list every two seconds (you should see the TIME column for the “ruby script/server” process go up with each iteration!). If you just bought the paper or PDF copies, you’ll have to take our word for it.
Prepared exclusively for Rida Al Barazi
Report erratum
THE USER INTERFACE, REVISITED 384
Figure 18.6: Keeping Current Using periodically_call_remote
18.3 The User Interface, Revisited
Web applications traditionally offer a less interactive user interfaces than traditional desktop applications. They didn’t really need more—until now. With the emergence of Web 2.0 this has to change, as we’ve been given boatloads of control over what happens on a web page with AJAX.
The Prototype library overcomes this problem, helping your application communicate with the user in an intuitive way. And it’s fun, too!
Besides the support for making AJAX calls, the Prototype library offers a wealth of useful objects to make your life easier and your users’ experience better at the same time.
The functionality offered by the Prototype library falls into the following groups.
•AJAX calls (which we’ve already discussed)
•Document Object Model (DOM) manipulation
•Visual effects
Document Object Model Manipulation
The standard support for DOM manipulation in JavaScript is cumbersome and clunky, so Prototype delivers handy shortcuts for a number of often-
Prepared exclusively for Rida Al Barazi
Report erratum
THE USER INTERFACE, REVISITED 385
used operations. These functions are all JavaScript and are intended to be invoked from within the pages delivered to the browser.
$(id)
Pass the $( ) method a string, and it returns the DOM element with the given id. Otherwise it returns its argument. (This behavior means you can pass in either an element’s id= attribute or the element itself and get an element returned.)
$('mydiv').style.border = "1px solid red"; /* sets border on mydiv */
Element.toggle(element, ...)
Element.toggle( ) toggles whether the given element (or elements) are shown. Internally, it switches the value of the CSS display attribute between ’inline’ and ’none’.
Element.toggle('mydiv'); |
/* toggles mydiv */ |
Element.toggle('div1', 'div2', 'div3'); /* toggles div1-div3 */
Element.show(element, ...)
Element.show( ) ensures all elements it receives as parameters will be shown.
Element.show('warning'); /* shows the element with id 'warning' */
Element.hide(element, ...)
Opposite of Element.show( ).
Element.remove(element)
Element.remove( ) completely removes an element from the DOM.
Element.remove('mydiv'); |
/* completely erase mydiv */ |
Insertion methods
The various insertion methods make it easy to add HTML fragments to existing elements. They are discussed in Section 18.4, Replacement Techniques, on page 389.
Visual Effects
Because AJAX works in the background, it’s transparent to the user. The server may receive an AJAX request, but the user doesn’t necessarily get any feedback about what’s going on. The browser doesn’t even indicate that a page is loading. The user might click a button to delete an entry from a to-do list, and that button might send off a request to the server, but without feedback, how is the user to know what’s happening? And, typically, if they don’t see something happening, the average user will just click the button, over and over.
Prepared exclusively for Rida Al Barazi
Report erratum
THE USER INTERFACE, REVISITED 386
Our job then is to provide feedback when the browser doesn’t. We need to let the user know visually that something is happening. This is a two-step process. First, we can use various DOM manipulation techniques to do things to the browser display to mirror what is happening on the server. However, on its own, this approach might not be enough.
For example, take a link_to_remote( ) call that deletes a record from your database and then empties out the DOM element that displayed that data. For your user, the element seems to disappear on their display instantly. In a traditional desktop application, this would be not be a big deal, as users take this behavior for granted. In a web application, this can cause problems: your user might just not “get it.”
That’s why there’s the second step. You should use effects to provide feedback that the change has been made. If the record disappears in an animated “puff” or fades out smoothly, your user will be happier believing that the action he or she chose really took place.
Visual effects support is bundled into its own JavaScript library, effects.js. As it depends on prototype.js, you’ll need to include both if you want to use effects on your site (probably by editing the layout template).
<%= javascript_include_tag "prototype", "effects" %>
There are two types of effects: one-shot effects and repeatedly callable effects.
One-Shot Effects
These effects are used to convey a clear message to the user: something is gone, or something had been changed or added. All these effects take one parameter, an element on your page. You should use a JavaScript string containing the id of an element: new Effect.Fade(’id_of_an_element’). If you use an effect inside an element’s events, you can also use the new Effect.Fade(this) syntax—this way you won’t have to use an id attribute if you don’t otherwise need it.
Effect.Appear(element)
This effect changes the opacity of the given element smoothly from 0% to 100%, fading it in smoothly.
Effect.Fade(element)
The opposite of Effect.Appear( )—the element will fade out smoothly, and its display CSS property will be set to none at the end (which will take the element out of the normal page flow).
Prepared exclusively for Rida Al Barazi
Report erratum
THE USER INTERFACE, REVISITED 387
Effect.Highlight(element)
Use the illustrious Yellow Fade Technique7 on the element, making its background fade smoothly from yellow to white. A great way to tell your user that some value has been updated not only in the browser but on the server, too.
Effect.Puff(element)
Creates the illusion that an element disappears in a gently expanding cloud of smoke. Fades out the element, and scales it up at the same time. At the end of the animation, the display property will be set to none (see Figure 18.7, on the following page).
Effect.Squish(element)
Makes the element disappear by smoothly making it smaller.
The screenshots in Figure 18.7 were generated by the following template. The code at the top is a helper method that sets up an alternating style for the squares in the grid. The loop at the bottom creates the initial set of 16 squares. When a Destroy link is clicked, the destroy action in the controller is called. In this example, the controller does nothing, but in real life it might remove a remove from a database table. When the action completes, the Puff effect is invoked on the square that was clicked, and away it goes.
File 194 |
<% def style_for_square(index) |
|
|
color = (index % |
2).zero? ? "#444" : "#ccc" |
|
%{ width: 150px; |
height: 120px; float: left; |
padding: 10px; color: #fff; text-align:center; background: #{color} }
end
%>
<% 16.times do |i| %>
<div id="mydiv<%= i %>" style="<%= style_for_square(i) %>"> <div style="font-size: 5em;"><%= i %></div>
<%= link_to_remote("Destroy",
:complete => "new Effect.Puff('mydiv#{i}')", :url => { :action => :destroy, :id => i }) %>
</div> <% end %>
Repeatedly Callable Effects
Effect.Scale(element, percent)
This effect smoothly scales the given element. If you scale a <div>, all contained elements must have their width and height set in em
7As evangelized by 37signals; see http://www.37signals.com/svn/archives/000558.php.
Prepared exclusively for Rida Al Barazi
Report erratum
THE USER INTERFACE, REVISITED 388
Figure 18.7: Up and Away...
units. If you scale an image, width and height are not required to be set.
Let’s do some scaling on an image.
<%= image_tag("image1",
:onclick => "new Effect.Scale(this, 100)") %>
You can also do this with text, if you use em units for your font sizes.
<%= content_tag("div",
"Here is some text that will get scaled.",
:style |
=> |
"font-size:1.0em; width:100px;", |
:onclick |
=> |
"new Effect.Scale(this, 100)") %> |
Element.setContentZoom(element, percent)
This effect provides a nonanimated way to set the scale of text and other elements that use em units.
<div id="outerdiv"
style="width:200px; height:200px; border:1px solid red;"> <div style="width:10em; height:2em; border:1px solid blue;">
First inner div
</div>
<div style="width:150px; height: 20px; border:1px solid blue;"> Second inner div
</div> </div>
<%= link_to_function("Small", "Element.setContentZoom('outerdiv', 75)") %>
Prepared exclusively for Rida Al Barazi
Report erratum