12.3 Iteration G3: Pagination 12.1 Iteration G1: Capturing an Order
Demonstrate various respond_to/format options, as well as "through" relations and basic authentication.
Define a "who_bought" member action
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.xml { render :xml => @product }
end
end
Add to the routes
edit config/routes.rb
Depot::Application.routes.draw do |map|
resources :orders
resources :line_items
resources :carts
get "store/index"
resources :products do
get :who_bought, :on => :member
end
# ...
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => "welcome#index"
root :to => 'store#index', :as => 'store'
# ...
end
Try again... success... but not much there
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought.xml
<?xml version="1.0" encoding="UTF-8"?>
<product>
<created-at type="datetime">2010-06-06T14:32:18Z</created-at>
<description><p>
Ruby is the fastest growing and most exciting dynamic language out
there. If you need to get working programs delivered fast, you should
add Ruby to your toolbox.
</p></description>
<id type="integer">3</id>
<image-url>/images/ruby.jpg</image-url>
<price type="decimal">49.5</price>
<title>Programming Ruby 1.9</title>
<updated-at type="datetime">2010-06-06T14:32:18Z</updated-at>
</product>
Add "orders" to the Product class
edit app/models/product.rb
class Product < ActiveRecord::Base
has_many :orders, :through => :line_items
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
# ensure that there are no line items referencing this product
def ensure_not_referenced_by_any_line_item
if line_items.count.zero?
return true
else
errors[:base] << "Line Items present"
return false
end
end
#...
Include "orders" in the response
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.xml { render :xml => @product.to_xml(:include => :orders) }
end
end
Fetch the xml, see that the orders are included
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought.xml
<?xml version="1.0" encoding="UTF-8"?>
<product>
<created-at type="datetime">2010-06-06T14:32:18Z</created-at>
<description><p>
Ruby is the fastest growing and most exciting dynamic language out
there. If you need to get working programs delivered fast, you should
add Ruby to your toolbox.
</p></description>
<id type="integer">3</id>
<image-url>/images/ruby.jpg</image-url>
<price type="decimal">49.5</price>
<title>Programming Ruby 1.9</title>
<updated-at type="datetime">2010-06-06T14:32:18Z</updated-at>
<orders type="array">
<order>
<address>123 Main St</address>
<created-at type="datetime">2010-06-06T14:37:41Z</created-at>
<email>customer@example.com</email>
<id type="integer">1</id>
<name>Dave Thomas</name>
<pay-type>Check</pay-type>
<updated-at type="datetime">2010-06-06T14:37:41Z</updated-at>
</order>
</orders>
</product>
Define an HTML view
edit app/views/products/who_bought.html.erb
<h3>People Who Bought <%= @product.title %></h3>
<ul>
<% for order in @product.orders %>
<li>
<%= mail_to order.email, order.name %>
</li>
<% end %>
</ul>
Add the html format to the controller
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.html
format.xml { render :xml => @product.to_xml(:include => :orders) }
end
end
See the (raw) HTML
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<!-- START:stylesheet -->
<link href="/stylesheets/scaffold.css?1275834701" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/depot.css?1275835058" media="all" rel="stylesheet" type="text/css" />
<!-- END:stylesheet -->
<script src="/javascripts/prototype.js?1275834690" type="text/javascript"></script>
<script src="/javascripts/effects.js?1275834690" type="text/javascript"></script>
<script src="/javascripts/dragdrop.js?1275834690" type="text/javascript"></script>
<script src="/javascripts/controls.js?1275834690" type="text/javascript"></script>
<script src="/javascripts/rails.js?1275834690" type="text/javascript"></script>
<script src="/javascripts/application.js?1275834690" type="text/javascript"></script>
<meta name="csrf-param" content="authenticity_token"/>
<meta name="csrf-token" content="v5h9x3ZQJdbIS4yQ+3UDSbUxGT1GpvBqlrVJHACSZO8="/>
</head>
<body id="store">
<div id="banner">
<img alt="Logo" src="/images/logo.png?1275834738" />
Pragmatic Bookshelf
</div>
<div id="columns">
<div id="side">
<!-- START_HIGHLIGHT -->
<!-- START:hidden_div -->
<!-- START_HIGHLIGHT -->
<!-- END_HIGHLIGHT -->
<!-- END:hidden_div -->
<!-- END_HIGHLIGHT -->
<a href="http://www....">Home</a><br />
<a href="http://www..../faq">Questions</a><br />
<a href="http://www..../news">News</a><br />
<a href="http://www..../contact">Contact</a><br />
</div>
<div id="main">
<h3>People Who Bought Programming Ruby 1.9</h3>
<ul>
<li>
<a href="mailto:customer@example.com">Dave Thomas</a>
</li>
</ul>
</div>
</div>
</body>
</html>
Define an Atom view (using the Atom builder)
edit app/views/products/who_bought.atom.builder
atom_feed do |feed|
feed.title "Who bought #{@product.title}"
latest_order = @product.orders.sort_by(&:updated_at).last
feed.updated( latest_order && latest_order.updated_at )
for order in @product.orders
feed.entry(order) do |entry|
entry.title "Order #{order.id}"
entry.summary :type => 'xhtml' do |xhtml|
xhtml.p "Shipped to #{order.address}"
xhtml.table do
xhtml.tr do
xhtml.th 'Product'
xhtml.th 'Quantity'
xhtml.th 'Total Price'
end
for item in order.line_items
xhtml.tr do
xhtml.td item.product.title
xhtml.td item.quantity
xhtml.td number_to_currency item.total_price
end
end
xhtml.tr do
xhtml.th 'total', :colspan => 2
xhtml.th number_to_currency \
order.line_items.map(&:total_price).sum
end
end
xhtml.p "Paid by #{order.pay_type}"
end
entry.author do |author|
entry.name order.name
entry.email order.email
end
end
end
end
Add the atom format to the controller
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.atom
format.html
format.xml { render :xml => @product.to_xml(:include => :orders) }
end
end
Fetch the Atom feed
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought.atom
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<id>tag:localhost,2005:/products/3/who_bought</id>
<link rel="alternate" type="text/html" href="http://localhost:3000"/>
<link rel="self" type="application/atom+xml" href="http://localhost:3000/products/3/who_bought.atom"/>
<title>Who bought Programming Ruby 1.9</title>
<updated>2010-06-06T14:37:41Z</updated>
<entry>
<id>tag:localhost,2005:Order/1</id>
<published>2010-06-06T14:37:41Z</published>
<updated>2010-06-06T14:37:41Z</updated>
<link rel="alternate" type="text/html" href="http://localhost:3000/orders/1"/>
<title>Order 1</title>
<summary type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Shipped to 123 Main St</p>
<table>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Total Price</th>
</tr>
<tr>
<td>Programming Ruby 1.9</td>
<td>1</td>
<td>$49.50</td>
</tr>
<tr>
<th colspan="2">total</th>
<th>$49.50</th>
</tr>
</table>
<p>Paid by Check</p>
</div>
</summary>
<author>
<name>Dave Thomas</name>
<email>customer@example.com</email>
</author>
</entry>
</feed>
pub depot_o
Anything that XML can do, JSON can too...
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.atom
format.html
format.xml { render :xml => @product.to_xml(:include => :orders) }
format.json { render :json => @product.to_json(:include => :orders) }
end
end
Fetch the data in JSON format
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought.json
{"created_at":"2010-06-06T14:32:18Z","description":"<p>\n Ruby is the fastest growing and most exciting dynamic language out\n there. If you need to get working programs delivered fast, you should\n add Ruby to your toolbox.\n </p>","id":3,"image_url":"/images/ruby.jpg","price":"49.5","title":"Programming Ruby 1.9","updated_at":"2010-06-06T14:32:18Z","orders":[{"address":"123 Main St","created_at":"2010-06-06T14:37:41Z","email":"customer@example.com","id":1,"name":"Dave Thomas","pay_type":"Check","updated_at":"2010-06-06T14:37:41Z"}]}
Customize the xml
edit app/views/products/who_bought.xml.builder
xml.order_list(:for_product => @product.title) do
for o in @product.orders
xml.order do
xml.name(o.name)
xml.email(o.email)
end
end
end
Change the rendering to use templates
edit app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])
respond_to do |format|
format.atom
format.html
format.xml
format.json { render :json => @product.to_json(:include => :orders) }
end
end
Fetch the (much streamlined) XML
curl --silent --user dave:secret http://localhost:3000/products/3/who_bought.xml
<order_list for_product="Programming Ruby 1.9">
<order>
<name>Dave Thomas</name>
<email>customer@example.com</email>
</order>
</order_list>
Consider reducing the number of edits to products_controller
12.3 Iteration G3: Pagination 12.1 Iteration G1: Capturing an Order