- •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
|
|
ITERATION A4: PRETTIER LISTINGS |
63 |
|
So, in a couple of minutes we’ve added validations that check |
|
|
|
• The field’s title, description, and image URL are not empty. |
|
|
|
• The price is a valid number greater than zero. |
|
|
|
• The title is unique among all products. |
|
|
|
• The image URL looks reasonable. |
|
|
|
This is the full listing of the updated Product model. |
|
|
File 65 |
class Product < ActiveRecord::Base |
|
|
|
validates_presence_of :title, :description, :image_url |
|
|
|
validates_numericality_of :price |
|
|
|
validates_uniqueness_of :title |
|
|
|
validates_format_of :image_url, |
|
|
|
:with |
=> %r{^http:.+\.(gif|jpg|png)$}i, |
|
|
:message => "must be a URL for a GIF, JPG, or PNG image" |
|
|
|
protected |
|
|
|
def validate |
|
|
|
errors.add(:price, "should be positive") unless price.nil? || price > 0.0 |
|
|
|
end |
|
|
|
end |
|
|
|
Nearing the end of this cycle, we ask our customer to play with the appli- |
|
|
|
cation, and she’s a lot happier. It took only a few minutes, but the simple |
|
|
|
act of adding validation has made the product maintenance pages feel a |
|
|
|
lot more solid. |
|
|
|
6.4 Iteration A4: Prettier Listings |
|
Our customer has one last request (customers always seem to have one |
|
last request). The listing of all the products is ugly. Can we “pretty it up” |
|
a bit? And, while we’re in there, can we also display the product image |
|
along with the image URL? |
|
We’re faced with a dilemma here. As developers, we’re trained to respond |
|
to these kinds of request with a sharp intake of breath, a knowing shake |
|
of the head, and a murmured “you want what?” At the same time, we also |
|
like to show off a bit. In the end, the fact that it’s fun to make these kinds |
|
of changes using Rails wins out, and we fire up our trusty editor. |
|
The Rails view in the file app/views/admin/list.rhtml produces the current |
|
list of products. The source code, which was produced by the scaffold |
|
generator, looks something like the following. |
File 66 |
<h1>Listing products</h1> |
|
<table> |
|
<tr> |
|
<% for column in Product.content_columns %> |
|
<th><%= column.human_name %></th> |
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A4: PRETTIER LISTINGS |
64 |
<% end %>
</tr>
<% for product in @products %>
<tr>
<% for column in Product.content_columns %> <td><%=h product.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => product %></td> <td><%= link_to 'Edit', :action => 'edit', :id => product %></td> <td><%= link_to 'Destroy', {:action => 'destroy', :id => product},
:confirm => "Are you sure?" %></td>
</tr> <% end %>
</table>
<%= if @product_pages.current.previous
link_to "Previous page", { :page => @product_pages.current.previous } end %>
<%= if @product_pages.current.next
link_to "Next page", { :page => @product_pages.current.next } end %>
<br />
<%= link_to 'New product', :action => 'new' %>
The view uses ERb to iterate over the columns in the Product model. It creates a table row for each product in the @products array. (This array is set up by the list action method in the controller.) The row contains an entry for each column in the result set.
The dynamic nature of this code is neat, as it means that the display will automatically update to accommodate new columns. However, it also makes the display somewhat generic. So, let’s take this code and modify it to produce nicer-looking output.
ERb
→ page 31
File 67 |
<h1>Product Listing</h1> |
|
<table cellpadding="5" cellspacing="0"> |
|
<% |
|
odd_or_even = 0 |
|
for product in @products |
|
odd_or_even = 1 - odd_or_even |
|
%> |
|
<tr valign="top" class="ListLine<%= odd_or_even %>"> |
|
<td> |
|
<img width="60" height="70" src="<%= product.image_url %>"/> |
|
</td> |
|
<td width="60%"> |
|
<span class="ListTitle"><%= h(product.title) %></span><br /> |
|
<%= h(truncate(product.description, 80)) %> |
|
</td> |
|
<td align="right"> |
|
<%= product.date_available.strftime("%y-%m-%d") %><br/> |
|
<strong>$<%= sprintf("%0.2f", product.price) %></strong> |
|
</td> |
|
<td class="ListActions"> |
|
<%= link_to 'Show', :action => 'show', :id => product %><br/> |
|
<%= link_to 'Edit', :action => 'edit', :id => product %><br/> |
Prepared exclusively for Rida Al Barazi
Report erratum
|
ITERATION A4: PRETTIER LISTINGS |
65 |
|
<%= link_to 'Destroy', { :action => 'destroy', :id => product }, |
|
|
:confirm => "Are you sure?" %> |
|
|
</td> |
|
|
</tr> |
|
|
<% end %> |
|
|
</table> |
|
|
<%= if @product_pages.current.previous |
|
|
link_to("Previous page", { :page => @product_pages.current.previous }) |
|
|
end |
|
|
%> |
|
|
<%= if @product_pages.current.next |
|
|
link_to("Next page", { :page => @product_pages.current.next }) |
|
|
end |
|
|
%> |
|
|
<br /> |
|
|
<%= link_to 'New product', :action => 'new' %> |
|
|
Notice how we used the odd_or_even variable to toggle the name of the CSS |
|
|
class applied to alternating rows of the table. This will result in alternating |
|
|
pastel-shaded lines for each product. (If you’re reading this on paper, you’ll |
|
|
have to take our word for it about the pastels.) We also used Ruby’s sprintf( ) |
|
|
method to convert the floating-point price to a nicely formatted string. |
|
|
All scaffold-generated applications use the stylesheet scaffold.css in the |
|
|
directory public/stylesheets. We added our own styles to this file. |
|
File 68 |
.ListTitle { |
|
color: #244; font-weight: bold; font-size: larger;
}
.ListActions {
font-size: x-small; text-align: right; padding-left: 1em;
}
.ListLine0 {
background: #e0f8f8;
}
.ListLine1 {
background: #f8b0f8;
}
Put some images in the public/images directory and enter some product descriptions, and the resulting product listing might look something like Figure 6.7, on the next page.
A Rails scaffold provides real source code, files that we can modify and immediately see results. This approach gives us the flexibility we need to develop in an agile way. We can customize a particular source file and leave the rest alone—changes are both possible and localized.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A4: PRETTIER LISTINGS |
66 |
Figure 6.7: Tidied-up Product Listing
So, we proudly show our customer her new product listing, and she’s pleased. End of task. Time for lunch.
What We Just Did
In this chapter we laid the groundwork for our store application.
•We created three databases (development, test, and production) and configured our Rails application to access them.
•We created the products table and used the scaffold generator to write an application to maintain it.
•We augmented that generated code with validation.
•We rewrote the generic view code with something prettier.
One thing that we didn’t do was discuss the pagination of the product listing. The scaffold generator automatically made use of Rails’ built-in pagination helper. This breaks the lists of products into pages of 10 entries each and automatically handles navigation between pages. We discuss this in more depth starting on page 340.
Prepared exclusively for Rida Al Barazi
Report erratum