- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •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 A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols
ITERATION B3: USE A HELPER TO FORMAT THE PRICE 100
7.3Iteration B3: Use a Helper to Format the Price
There’s a problem with our catalog display. The database stores the price as a number, but we’d like to show it as dollars and cents. A price of 12.34 should be shown as $12.34, and 13 should display as $13.00.
One solution would be to format the price in the view. For example, we could say
<span class="price"><%= sprintf("$%0.02f" , product.price) %></span>
This will work, but it embeds knowledge of currency formatting into the view. Should we want to internationalize the application later, this would be a maintenance problem.
Instead, let’s use a helper method to format the price as a currency. Rails has an appropriate one built in—it’s called number_to_currency.
Using our helper in the view is simple: in the index template, we change
<span class="price"><%= product.price %></span>
to
<span class="price"><%= number_to_currency(product.price) %></span>
Sure enough, when we hit Refresh, we see a nicely formatted price.
7.4Iteration B4: Linking to the Cart
Our customer is really pleased with our progress. We’re still on the first day of development, and we have a halfway decent-looking catalog display. However, she points out that we’ve forgotten a minor detail—there’s no way for anyone to buy anything at our store. We forgot to add any kind of Add to Cart link to our catalog display.
Back on page 57 we used the link_to helper to generate links from a Rails view back to another action in the controller. We could use this same helper to put an Add to Cart link next to each product on the catalog page. As we saw on page 91, this is dangerous. The problem is that the link_to helper generates an HTML <a href=...> tag. When you click the corresponding link, your browser generates an HTTP GET request to the server. And HTTP GET requests are not supposed to change the state of anything on the server—they’re to be used only to fetch information.
Report erratum
ITERATION B4: LINKING TO THE CAR T 101
We previously showed the use of :method => :post as one solution to this problem. Rails provides a useful alternative: the button_to method also links a view back to the application, but it does so by generating an HTML form that contains just a single button. When the user clicks the button, an HTTP POST request is generated. And a POST request is just the ticket when we want to do something like add an item to a cart.
Let’s add the Add to Cart button to our catalog page. The syntax is the same as we used for link_to.
<%= button_to "Add to Cart", :action => :add_to_cart %>
However, there’s a problem with this: how will the add_to_cart action know which product to add to our cart? We’ll need to pass it the id of the item corresponding to the button. That’s easy enough—we simply add an :id option to the button_to call. Our index.rhtml template now looks like this.
Download depot_f/app/views/store/index.rhtml
<h1>Your Pragmatic Catalog</h1>
<% for product in @products -%> <div class="entry" >
<img src="<%= product.image_url %>"/>
<h3><%= h(product.title) %></h3> <%= product.description %>
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to "Add to Cart", :action => :add_to_cart, :id => product %>
</div>
<% end %>
There’s one more formatting issue. button_to creates an HTML <form>, and that form contains an HTML <div>. Both of these are normally block elements, which will appear on the next line. We’d like to place them next to the price, so we need a little CSS magic to make them inline.
Download depot_f/public/stylesheets/depot.css
#store .entry form, #store .entry form div { display: inline;
}
Now our index page looks like Figure 7.3, on the following page.
What We Just Did
We’ve put together the basis of the store’s catalog display. The steps were as follows.
1.Create a new controller to handle customer-centric interactions.
2.Implement the default index action.
Report erratum
ITERATION B4: LINKING TO THE CAR T 102
Figure 7.3: Now There’s an Add to Cart Button
3.Add a class method to the Product model to return salable items.
4.Implement a view (an .rhtml file) and a layout to contain it (another .rhtml file).
5.Create a simple stylesheet.
6.Use a helper to format prices the way we’d like.
7.Add a button to each item to allow folks to add it to our cart.
Time to check it all in and move on to the next task.
Playtime
Here’s some stuff to try on your own:
•Add a date and time to the sidebar. It doesn’t have to update: just show the value at the time the page was displayed.
•Change the application so that clicking a book’s image will also invoke the add_to_cart action. (It’s OK, I know we haven’t written that action yet....) Hint: the first parameter to link_to is placed in the generated <a> tag, and the Rails helper image_tag constructs an HTML <img> tag. Look up image_tag in the Rails API documentation at http://api.rubyonrails.org, and include a call to it as the first parameter to a link_to call.
•The full description of the number_to_currency helper method is.
number_to_currency(number, options = {})
Report erratum
ITERATION B4: LINKING TO THE CAR T 103
Formats a number into a currency string. The options hash can be used to customize the format of the output. The number can contain a level of precision using the :precision key; default is 2 The currency type can be set using the :unit key; default is "$" The unit separator can be set using the :separator key; default is "." The delimiter can be set using the :delimiter key; default is ",".
number_to_currency(1234567890.50) |
=> |
$1,234,567,890.50 |
number_to_currency(1234567890.506) |
=> |
$1,234,567,890.51 |
number_to_currency(1234567890.50, :unit => "£",
:separator => ",", :delimiter => "") => £1234567890,50
Experiment with setting various options, and see the effect on your catalog listing.
(You’ll find hints at http://wiki.pragprog.com/cgi-bin/wiki.cgi/RailsPlayTime)
Report erratum