- •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 C4: FINISHING THE CAR T 118
David Says. . .
How Much Inline Error Handling Is Needed?
The add_to_cart method shows the deluxe version of error handling in Rails where the particular error is given exclusive attention and code. Not every conceivable error is worth spending that much time catching. Lots of input errors that will cause the application to raise an exception occur so rarely that we’d rather just treat them to a uniform catchall error page.
We talk about setting up a global error handler on page 627.
the application. She notices a minor problem on our new cart display—there’s no way to empty items out of a cart. This minor change will be our next iteration. We should make it before heading home.
8.5Iteration C4: Finishing the Cart
We know by now that in order to implement the empty cart function, we have to add a link to the cart and implement an empty_cart method in the store controller. Let’s start with the template. Rather than use a hyperlink, let’s use the button_to method to put a button on the page.
Download depot_h/app/views/store/add_to_cart.rhtml
<h1>Your Pragmatic Cart</h1>
<ul>
<% for cart_item in @cart.items %>
<li><%= cart_item.quantity %> × <%= h(cart_item.title) %></li> <% end %>
</ul>
<%= button_to "Empty cart", :action => :empty_cart %>
In the controller, we’ll implement the from the session and sets a message index page.
empty_cart method. It removes the cart into the flash before redirecting to the
Download depot_h/app/controllers/store_controller.rb
def empty_cart session[:cart] = nil
flash[:notice] = "Your cart is currently empty" redirect_to :action => :index
end
Report erratum
ITERATION C4: FINISHING THE CAR T 119
Now when we view our cart and click the Empty cart link, we get taken back to the catalog page, and a nice little message says
However, before we break an arm trying to pat ourselves on the back, let’s look back at our code. We’ve just introduced some duplication.
In the store controller, we now have two places that put a message into the flash and redirect to the index page. Sounds like we should extract that common code into a method, so let’s implement redirect_to_index and change the add_to_cart and empty_cart methods to use it.
Download depot_i/app/controllers/store_controller.rb
def add_to_cart begin
product = Product.find(params[:id]) rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid product #{params[:id]}") redirect_to_index("Invalid product")
else
@cart = find_cart @cart.add_product(product)
end end
def empty_cart session[:cart] = nil
redirect_to_index("Your cart is currently empty") end
private
def redirect_to_index(msg) flash[:notice] = msg redirect_to :action => :index
end
And, finally, we’ll get around to tidying up the cart display. Rather than use <li> elements for each item, let’s use a table. Again, we’ll rely on CSS to do the styling.
Download depot_i/app/views/store/add_to_cart.rhtml
<div class="cart-title">Your Cart</div> <table>
<% for cart_item in @cart.items %>
Report erratum
ITERATION C4: FINISHING THE CAR T |
120 |
<tr>
<td><%= cart_item.quantity %>×</td> <td><%= h(cart_item.title) %></td>
<td class="item-price"><%= number_to_currency(cart_item.price) %></td> </tr>
<% end %>
<tr class="total-line" >
<td colspan="2">Total</td>
<td class="total-cell"><%= number_to_currency(@cart.total_price) %></td> </tr>
</table>
<%= button_to "Empty cart", :action => :empty_cart %>
To make this work, we need to add a method to the Cart model that returns the total price of all the items. We can implement one using Rails’ nifty sum method to sum the prices of each item in the collection.
Download depot_i/app/models/cart.rb
def total_price
@items.sum { |item| item.price } end
This gives us a nicer-looking cart.
What We Just Did
It has been a busy, productive day. We’ve added a shopping cart to our store, and along the way we’ve dipped our toes into some neat Rails features.
•Using sessions to store state
•Creating and integrating nondatabase models
•Using the flash to pass errors between actions
•Using the logger to log events
•Removing duplication from controllers
Report erratum
ITERATION C4: FINISHING THE CAR T 121
We’ve also generated our fair share of errors and seen how to get around them.
But, just as we think we’ve wrapped this functionality up, our customer wanders over with a copy of Information Technology and Golf Weekly. Apparently, there’s an article about a new style of browser interface, where stuff gets updated on the fly. “AJAX,” she says, proudly. Hmmm...let’s look at that tomorrow.
Playtime
Here’s some stuff to try on your own.
•Add a new variable to the session to record how many times the user has accessed the index action. (The first time through, your count won’t be in the session. You can test for this with code like
if session[:counter].nil?
...
If the session variable isn’t there, you’ll need to initialize it. Then you’ll be able to increment it.
•Pass this counter to your template, and display it at the top of the catalog page. Hint: the pluralize helper (described on page 474) might be useful when forming the message you display.
•Reset the counter to zero whenever the user adds something to the cart.
•Change the template to display the counter only if it is greater than five.
(You’ll find hints at http://wiki.pragprog.com/cgi-bin/wiki.cgi/RailsPlayTime)
Report erratum