Agile Web Development with Rails, Edition 4

12.4 Playtime 12.1 Iteration G1: Capturing an Order

12.2 Iteration G2: Atom Feeds

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])
    @latest_order = @product.orders.order(:updated_at).last
    if stale?(@latest_order)
      respond_to do |format|
        format.atom
      end
    end
  end

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}"
 
  feed.updated @latest_order.try(:updated_at) 
 
  @product.orders.each do |order|
    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
          order.line_items.each do |item|
            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|
        author.name order.name
        author.email order.email
      end
    end
  end
end

Add "orders" to the Product class

edit app/models/product.rb
class Product < ActiveRecord::Base
  has_many :line_items
  has_many :orders, through: :line_items
  #...
end

Add to the routes

edit config/routes.rb
Depot::Application.routes.draw do
  resources :orders
 
  resources :line_items
 
  resources :carts
 
  get "store/index"
 
  resources :products do
    get :who_bought, on: :member
  end
 
  # The priority is based upon order of creation:
  # first created -> highest priority.
 
  # Sample of regular route:
  #   match 'products/:id' => 'catalog#view'
  # Keep in mind you can assign values other than :controller and :action
 
  # Sample of named route:
  #   match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
  # This route can be invoked with purchase_url(:id => product.id)
 
  # Sample resource route (maps HTTP verbs to controller actions automatically):
  #   resources :products
 
  # Sample resource route with options:
  #   resources :products do
  #     member do
  #       get 'short'
  #       post 'toggle'
  #     end
  #
  #     collection do
  #       get 'sold'
  #     end
  #   end
 
  # Sample resource route with sub-resources:
  #   resources :products do
  #     resources :comments, :sales
  #     resource :seller
  #   end
 
  # Sample resource route with more complex sub-resources
  #   resources :products do
  #     resources :comments
  #     resources :sales do
  #       get 'recent', :on => :collection
  #     end
  #   end
 
  # Sample resource route within a namespace:
  #   namespace :admin do
  #     # Directs /admin/products/* to Admin::ProductsController
  #     # (app/controllers/admin/products_controller.rb)
  #     resources :products
  #   end
 
  # You can have the root of your site routed with "root"
  # just remember to delete public/index.html.
  root to: 'store#index', as: 'store'
  # ...
end

Fetch the Atom feed

curl --max-time 15 --silent --user dave:secret http://localhost:3000/products/2/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/2/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/2/who_bought.atom"/>
  <title>Who bought CoffeeScript</title>
  <updated>2014-03-26T22:43:41Z</updated>
  <entry>
    <id>tag:localhost,2005:Order/1</id>
    <published>2014-03-26T22:43:41Z</published>
    <updated>2014-03-26T22:43: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>CoffeeScript</td>
            <td>1</td>
            <td>$36.00</td>
          </tr>
          <tr>
            <th colspan="2">total</th>
            <th>$36.00</th>
          </tr>
        </table>
        <p>Paid by Check</p>
      </div>
    </summary>
    <author>
      <name>Dave Thomas</name>
      <email>customer@example.com</email>
    </author>
  </entry>
</feed>

Look at the headers

curl --max-time 15 --silent --dump - --output /dev/null --user dave:secret http://localhost:3000/products/2/who_bought.atom
HTTP/1.1 200 OK 
Etag: "3d894147159068360eedfd6b1948016e"
Last-Modified: Wed, 26 Mar 2014 22:43:41 GMT
Content-Type: application/atom+xml; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Ua-Compatible: IE=Edge
X-Request-Id: a5c9e847fe9cfb34466453fb1cdd08ea
X-Runtime: 0.012296
Server: WEBrick/1.3.1 (Ruby/1.9.3/2014-02-24)
Date: Wed, 26 Mar 2014 22:43:42 GMT
Content-Length: 1324
Connection: Keep-Alive
 
curl --max-time 15 --silent --dump - --output /dev/null --user dave:secret http://localhost:3000/products/2/who_bought.atom -H 'If-None-Match: "3d894147159068360eedfd6b1948016e"'
HTTP/1.1 304 Not Modified 
Etag: "3d894147159068360eedfd6b1948016e"
Last-Modified: Wed, 26 Mar 2014 22:43:41 GMT
Cache-Control: max-age=0, private, must-revalidate
X-Ua-Compatible: IE=Edge
X-Request-Id: 831f11113c0584df129fdb7b589a918c
X-Runtime: 0.004682
Server: WEBrick/1.3.1 (Ruby/1.9.3/2014-02-24)
Date: Wed, 26 Mar 2014 22:43:42 GMT
Connection: close
 
curl --max-time 15 --silent --dump - --output /dev/null --user dave:secret http://localhost:3000/products/2/who_bought.atom -H 'If-Modified-Since: Wed, 26 Mar 2014 22:43:41 GMT'
HTTP/1.1 304 Not Modified 
Etag: "3d894147159068360eedfd6b1948016e"
Last-Modified: Wed, 26 Mar 2014 22:43:41 GMT
Cache-Control: max-age=0, private, must-revalidate
X-Ua-Compatible: IE=Edge
X-Request-Id: 69984b9717fd958a0c48e9bc452b0ec0
X-Runtime: 0.005824
Server: WEBrick/1.3.1 (Ruby/1.9.3/2014-02-24)
Date: Wed, 26 Mar 2014 22:43:42 GMT
Connection: close
 

12.4 Playtime 12.1 Iteration G1: Capturing an Order