Task G: Check Out!
12.3 Iteration G3: Pagination
At the moment, we have a few products, a few carts at any one time, a few line items per cart or order, but we can have essentially an unlimited number of Orders, and we hope to have many. Enough so that displaying all of them on an orders page will quickly become unwieldy. Enter the will_paginate plugin.
This plugin extends rails to provide this much needed function.
Why a plugin? Way back in Rails 1.0, this functionality was a part of Rails itself. But there were competing ideas on how this could be implemented and improved and the function was broken out in order to enable innovation to thrive.
The first thing we need to do is to inform Rails of our intent to use the plugin.
We do that by modifying theGemfile. We need to specify that we want a version that is greater than or equal to 3.0.pre as previous versions don’t work with Rails 3.0.
Download depot_q/Gemfile
source 'http://rubygems.org' gem 'rails' , '3.1.0.beta'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'sqlite3-ruby' , :require => 'sqlite3'
# Use unicorn as the web server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
# gem 'ruby-debug'
# gem 'ruby-debug19'
# Bundle the extra gems:
# gem 'bj'
# gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3' gem 'will_paginate' , '>= 3.0.pre'
Report erratum this copy is (B8.0 printing, September 9, 2010)
Prepared exclusively for Jared Rosoff
ITERATIONG3: PAGINATION 192
# Bundle gems for the local environment. Make sure to
# put test-only gems in this group so their generators
# and rake tasks are available in development mode:
# group :development, :test do
# gem 'webrat'
# end
With this in place, we can use thebundlecommand to install our dependencies.
bundle install
The bundle command will actually do much more. It will cross check gem dependencies and find a configuration that works and download and install whatever components are necessary. But this needn’t concern us now, we only added one component, and we can rest assured that included in the gems that the bundler installed is this one.
Now lets generate some test data. We could click repeatedly on the buttons we have to add to cart and checkout, etc, but computers are good at this. This isn’t exactly seed data, simply something done once and thrown away. Let’s create a file in thescriptdirectory.
Download depot_q/script/load_orders.rb
Order.transaction do (1..100).each do |i|
Order.create(:name => "Customer #{i}" , :address => "#{i} Main Street" , :email => "customer-#{i}@example.com" , :pay_type => "Check" )
end end
This will create 100 orders with no line items in them. Feel free to modify the script to create Line Items if you are so inclined. Note that this code does all this work in one transaction. This isn’t precisely required for this activity, but does speed up the processing.
Note that we don’t have anyrequirestatements or initialization to open or close the database. We will allow Rails to take care of this for us:
rails runner script/load_orders.rb
Now that the setup is done, we are ready to make the changes necessary to our application. First, we will modify our controller to call paginate, passing it in the page and the order in which we want the results displayed.
Download depot_p/app/controllers/orders_controller.rb
def index
@orders = Order.paginate :page=>params[:page], :order=>'created_at desc' , :per_page => 10
respond_to do |format|
format.html # index.html.erb
ITERATIONG3: PAGINATION 193
format.xml { render :xml => @orders } end
end
Next, we will add links to the bottom of our index view.
Download depot_q/app/views/orders/index.html.erb
<% @orders.each do |order| %>
<tr>
<td><%= order.name %></td>
<td><%= order.address %></td>
<td><%= order.email %></td>
<td><%= order.pay_type %></td>
<td><%= link_to 'Show', order %></td>
<td><%= link_to 'Edit', edit_order_path(order) %></td>
<td><%= link_to 'Destroy', order, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Order', new_order_path %>
<p><%= will_paginate @orders %></p>
And that is all there is too it! The default is to show 30 entries per page, and the links will only show up if there are more than one page of orders.
The controller specifies the number of orders to display on a page using the :per_pageoption. See Figure12.5, on the next page.
The customer likes it. We’ve implemented product maintenance, a basic cata-log, and a shopping cart, and now we have a simple ordering system. Obviously we’ll also have to write some kind of fulfillment application, but that can wait for a new iteration. (And that iteration is one that we’ll skip in this book; it doesn’t have much new to say about Rails.)
What We Just Did
In a fairly short amount of time, we did the following:
Report erratum this copy is (B8.0 printing, September 9, 2010)
Prepared exclusively for Jared Rosoff
ITERATIONG3: PAGINATION 194
Figure 12.5: Showing 10 order out of over a hundred
• We created a form to capture details for the order and linked it to a new order model.
• We added validation and used helper methods to display errors to the user.
• We installed and used a plugin to paginate the list of orders.
• We provided a feed so that the administrator can monitor orders as they come in.
Playtime
Here’s some stuff to try on your own:
• Get HTML, XML, and JSON formatted views working for who_bought requests. Experiment with including the order information in the XML view by rendering @product.to_xml(:include => :orders). Do the same thing for JSON.
• What happens if you click the Checkout button in the sidebar while the checkout screen is already displayed? Can you find a way to disable the button in this circumstance? (Hint: variables set in the controller are available in layouts and partials as well as in the directly rendered template.)
ITERATIONG3: PAGINATION 195
• The list of possible payment types is currently stored as a constant in the Orderclass. Can you move this list into a database table? Can you still make validation work for the field?
(You’ll find hints athttp://www.pragprog.com/wikis/wiki/RailsPlayTime.)
Report erratum this copy is (B8.0 printing, September 9, 2010)
Prepared exclusively for Jared Rosoff
ITERATIONG3: PAGINATION 196
In this chapter, we’ll see
• sending e-mail,
• integration testing.