- •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
Chapter 7
Task B: Catalog Display
All in all, it’s been a successful day so far. We gathered the initial requirements from our customer, documented a basic flow, worked out a first pass at the data we’ll need, and put together the maintenance page for the Depot application’s products. We even managed to cap off the morning with a decent lunch.
Thus fortified, it’s on to our second task. We chatted through priorities with our customer, and she said she’d like to start seeing what things look like from the buyer’s point of view. Our next task is to create a simple catalog display.
This also makes a lot of sense from our point of view. Once we have the products safely tucked into the database, it should be fairly simple to display them. It also gives us a basis from which to develop the shopping cart portion of the code later.
We should also be able to draw on the work we did in the product maintenance task—the catalog display is really just a glorified product listing. So, let’s get started.
7.1 Iteration B1: Create the Catalog Listing
Back on page 56, we said that we’d be using two controller classes for this application. We’ve already created the Admin controller, used by the seller to administer the Depot application. Now it’s time to create the second controller, the one that interacts with the paying customers. Let’s call it
Store.
depot> ruby script/generate controller Store index
Prepared exclusively for Rida Al Barazi
ITERATION B1: CREATE THE CATALOG LISTING |
68 |
In the previous chapter, we used the generate utility to create a scaffold for the products table. This time, we’ve asked it to create a new controller (called StoreController) containing a single action method, index( ).
So why did we choose to call our first method index? Because, just like most web servers, if you invoke a Rails controller and don’t specify an explicit action, Rails automatically invokes the index action. In fact, let’s try it. Point a browser at http://localhost:3000/store and up pops our web page.
|
It might not make us rich, but at least we know things are all wired |
|
together correctly. The page even tells us where to find the program file |
|
that draws this page. |
|
Let’s start by displaying a simple list of all the salable products in our |
|
database. We know that eventually we’ll have to be more sophisticated, |
|
breaking them into categories, but this will get us going. What constitutes |
|
a salable product? Our customer told us that we only display ones with |
|
an available date on or before today. |
|
We need to get the list of products out of the database and make it available |
|
to the code in the view that will display the table. This means we have to |
|
change the index( ) method in store_controller.rb. We want to program at a |
|
decent level of abstraction, so let’s just assume we can ask the model for |
|
a list of the products we can sell. |
File 69 |
def index |
|
@products = Product.salable_items |
|
end |
|
Obviously, this code won’t run as it stands. We need to define the method |
|
salable_items( ) in the product.rb model. The code that follows uses the Rails |
|
find( ) method. The :all parameter tells Rails that we want all rows that |
|
match the given condition. (The condition checks that the item’s availabil- |
|
ity date is not in the future. It uses the MySQL now( ) function to get the |
|
current date and time.) We asked our customer if she had a preference |
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION B1: CREATE THE CATALOG LISTING |
69 |
|
regarding the order things should be listed, and we jointly decided to see |
|
||
|
what happened if we displayed the newest products first, so the code does |
|
||
|
a descending sort on date_available. |
def self.xxx |
||
File 70 |
# Return a list of products we can sell (which means they have to be |
→ page 471 |
||
|
||||
|
# available). Show the most recently available first. |
|
||
|
def self.salable_items |
|
|
|
|
find(:all, |
|
|
|
|
:conditions => "date_available <= now()", |
|
||
|
:order |
=> "date_available desc") |
|
|
|
end |
|
|
|
|
The find( ) method returns an array containing a Product object for each row |
|
||
|
returned from the database. The salable_items( ) method simply passes this |
|
||
|
array back to the controller. |
|
||
|
Now we need to write our view template. For now we’ll display the prod- |
|
||
|
ucts in a simple table. To do this, edit the file app/views/store/index.rhtml. |
|
||
|
(Remember that the path name to the view is built from the name of the |
|
||
|
controller (store) and the name of the action (index). The .rhtml part signifies |
|
||
|
an ERb template.) |
|
|
|
File 71 |
<table cellpadding="5" cellspacing="0"> |
|
||
|
<% for product in @products %> |
|
||
|
<tr valign="top"> |
|
|
|
|
<td> |
|
|
|
|
<img src="<%= product.image_url %>"/> |
|
||
|
</td> |
|
|
|
|
<td width="450"> |
|
|
|
|
<h3><%=h product.title %></h3> |
|
||
|
<small> |
|
|
|
|
<%= product.description %> |
|
||
|
</small> |
|
|
|
|
<br/> |
|
|
|
|
<strong>$<%= sprintf("%0.2f", product.price) %></strong> |
|
||
|
<%= link_to 'Add to Cart', |
|
||
|
|
:action => 'add_to_cart', |
|
|
|
|
:id |
=> product %> |
|
|
<br/> |
|
|
|
</td> </tr>
<tr><td colspan="2"><hr/></td></tr>
<% end %>
</table>
Hitting Refresh brings up the display in Figure 7.1, on the following page. We call the customer over, and she’s pretty pleased. After all, we have the makings of a catalog and it’s taken only a few minutes. But before we get too full of ourselves, she points out that she’d really like a proper-looking web page here. She needs at least a title at the top and a sidebar with links and news.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION B2: ADD PAGE DECORATIONS |
70 |
Figure 7.1: Our First Catalog Page
At this point in the real world we’d probably want to call in the design folks—we’ve all seen too many programmer-designed web sites to feel comfortable inflicting another on the world. But the Pragmatic Web Designer is off getting inspiration somewhere and won’t be back until later in the year, so let’s put a placeholder in for now. It’s time for an iteration.
7.2 Iteration B2: Add Page Decorations
The pages in a particular web site typically share a similar layout—the designer will have created a standard template that is used when placing content. Our job is to add this page decoration to each of the store pages.
Fortunately, in Rails we can define layouts. A layout is a template into layout which we can flow additional content. In our case, we can define a single layout for all the store pages and insert the catalog page into that layout. Later we can do the same with the shopping cart and checkout pages. Because there’s only one layout, we can change the look and feel of this entire section of our site by editing just one thing. This makes us feel better about putting a placeholder in for now; we can update it when the designer eventually returns from the mountaintop.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION B2: ADD PAGE DECORATIONS |
71 |
There are many ways of specifying and using layouts in Rails. We’ll choose the simplest for now. If you create a template file in the app/views/layouts directory with the same name as a controller, all views rendered by that controller will use that layout by default. So let’s create one now. Our controller is called store, so we’ll name the layout store.rhtml.
File 72 |
Line 1 |
<html> |
|
|
- |
<head> |
|
|
- |
<title>Pragprog Books Online Store</title> |
|
|
- |
<%= stylesheet_link_tag "depot", :media => "all" %> |
|
|
5 |
</head> |
|
|
- |
<body> |
|
|
- |
<div id="banner"> |
|
|
- |
<img src="/images/logo.png"/> |
|
|
- |
<%= @page_title || "Pragmatic Bookshelf" %> |
|
|
10 |
</div> |
|
|
- |
<div id="columns"> |
|
|
- |
<div id="side"> |
|
|
- |
<a href="http://www.... |
">Home</a><br /> |
|
- |
<a href="http://www.... |
/faq">Questions</a><br /> |
|
15 |
<a href="http://www.... |
/news">News</a><br /> |
|
- |
<a href="http://www.... |
/contact">Contact</a><br /> |
|
- |
</div> |
|
|
- |
<div id="main"> |
|
|
- |
<%= @content_for_layout %> |
|
|
20 |
</div> |
|
|
- |
</div> |
|
|
- |
</body> |
|
|
- |
</html> |
|
Apart from the usual HTML gubbins, this layout has three Rails-specific items. Line 4 uses a Rails helper method to generate a <link> tag to our depot.css stylesheet. On line 9 we set the page title to a value in the variable @page_title. The real magic, however, takes place on line 19. Rails automatically sets the variable @content_for_layout to the page-specific content—the stuff generated by the view invoked by this request. In our case, this will be the catalog page generated by index.rhtml.
We’ll also take this opportunity to tidy up the index.rhtml view in app/views.
||
→ page 479
File 73 |
<% for product in @products %> |
|
<div class="catalogentry"> |
|
<img src="<%= product.image_url %>"/> |
|
<h3><%= h(product.title) %></h3> |
|
<%= product.description %> |
|
<span class="catalogprice"><%= sprintf("$%0.2f", product.price) %></span> |
|
<%= link_to 'Add to Cart', |
|
{:action => 'add_to_cart', :id => product }, |
|
:class => 'addtocart' %><br/> |
|
</div> |
|
<div class="separator"> </div> |
|
<% end %> |
|
<%= link_to "Show my cart", :action => "display_cart" %> |
|
Notice how we’ve switched to using <div> tags and added CSS class names |
|
to tags to assist with laying out the page. To give the Add to Cart link a |
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION B2: ADD PAGE DECORATIONS |
72 |
Figure 7.2: Catalog with Layout Added
class, we had to use the optional third parameter of the link_to( ) method, which lets us specify HTML attributes for the generated tag.
To make this all work, we need to hack together a quick stylesheet (or, more likely, grab an existing stylesheet and bend it to fit). The file depot.css goes into the directory public/stylesheets. (Listings of the stylesheets start on page 508.) Hit Refresh, and the browser window looks something like Figure 7.2 . It won’t win any design awards, but it’ll show our customer roughly what the final page will look like.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION B2: ADD PAGE DECORATIONS |
73 |
What We Just Did
We’ve put together the basis of the store’s catalog display. The steps were as follows.
•Create a new controller to handle user-centric interactions.
•Implement the default index( ) action.
•Add a class method to the Product model to return salable items.
•Implement a viewer (an .rhtml file) and a layout to contain it (another
.rhtml file).
•Create a simple stylesheet.
Time to check it all in and move on to the next task.
Prepared exclusively for Rida Al Barazi
Report erratum