13.1 Iteration H1: Email Notifications 12.3 Iteration G3: Downloading an eBook
add activemodel-serializers-xml
edit Gemfile
gem 'activemodel-serializers-xml'
bundle install --local
Resolving dependencies...
Using rake 11.1.2
Using concurrent-ruby 1.0.2
Using i18n 0.7.0
Using minitest 5.3.3
Using thread_safe 0.3.5
Using builder 3.2.2
Using erubis 2.7.0
Using mini_portile2 2.0.0
Using json 1.8.3
Using nio4r 1.2.1
Using websocket-extensions 0.1.2
Using mime-types-data 3.2016.0221
Using arel 7.0.0
Using bundler 1.12.3
Using byebug 8.2.5
Using coffee-script-source 1.10.0
Using execjs 2.6.0
Using method_source 0.8.2
Using thor 0.19.1
Using debug_inspector 0.0.2
Using multi_json 1.12.0
Using pg 0.18.4
Using puma 3.4.0
Using qu 0.2.0 from source at `/home/rubys/git/qu-rails`
Using sass 3.4.22 from source at `/home/rubys/git/sass`
Using tilt 2.0.2
Using spring 1.7.1
Using sqlite3 1.3.11
Using tzinfo 1.2.2
Using nokogiri 1.6.7.2
Using rack 2.0.0.rc1
Using websocket-driver 0.6.3
Using mime-types 3.0
Using coffee-script 2.4.1
Using uglifier 3.0.0
Using queue_classic 3.2.0.RC1 from source at `/home/rubys/git/queue_classic`
Using activesupport 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using loofah 2.0.3
Using rack-test 0.6.3
Using sprockets 3.6.0
Using mail 2.6.4
Using rails-deprecated_sanitizer 1.0.3
Using globalid 0.3.6
Using activemodel 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using jbuilder 2.4.1
Using rails-html-sanitizer 1.0.3
Using rails-dom-testing 1.0.7
Using activejob 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using activerecord 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using actionview 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using activemodel-serializers-xml 1.0.1
Using actionpack 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using actioncable 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using actionmailer 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using railties 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using sprockets-rails 3.0.4
Using coffee-rails 4.1.1
Using jquery-rails 4.1.1
Using jquery-ui-rails 5.0.5
Using qu-rails 0.2.0 from source at `/home/rubys/git/qu-rails`
Using web-console 3.1.1 from source at `/home/rubys/git/web-console`
Using rails 5.0.0.beta3 from source at `/home/rubys/git/rails`
Using sass-rails 5.0.4
Using turbolinks 3.0.0 from source at `/home/rubys/git/turbolinks`
Bundle complete! 18 Gemfile dependencies, 64 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
include xml serializers
edit app/models/product.rb
require 'active_model/serializers/xml'
class Product < ApplicationRecord
include ActiveModel::Serializers::Xml
end
edit app/models/order.rb
require 'active_model/serializers/xml'
class Order < ApplicationRecord
include ActiveModel::Serializers::Xml
end
Add the xml format to the controller
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.xml { render :xml => @product }
format.atom
end
end
end
Fetch the XML, see that there are no orders there
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>2016-05-07T01:42:43Z</updated>
<entry>
<id>tag:localhost,2005:Order/1</id>
<published>2016-05-07T01:42:42Z</published>
<updated>2016-05-07T01:42:42Z</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>
<entry>
<id>tag:localhost,2005:Order/2</id>
<published>2016-05-07T01:42:43Z</published>
<updated>2016-05-07T01:42:43Z</updated>
<link rel="alternate" type="text/html" href="http://localhost:3000/orders/2"/>
<title>Order 2</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>
Include "orders" in the response
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.xml { render xml: @product.to_xml(include: :orders) }
format.atom
end
end
end
Fetch the xml, see that the orders are included
curl --max-time 15 --silent --user dave:secret http://localhost:3000/products/2/who_bought.xml
<?xml version="1.0" encoding="UTF-8"?>
<product>
<id type="integer">2</id>
<title>CoffeeScript</title>
<description><p>
CoffeeScript is JavaScript done right. It provides all of JavaScript's
functionality wrapped in a cleaner, more succinct syntax. In the first
book on this exciting new language, CoffeeScript guru Trevor Burnham
shows you how to hold onto all the power and flexibility of JavaScript
while writing clearer, cleaner, and safer code.
</p></description>
<image-url>cs.jpg</image-url>
<price type="decimal">36.0</price>
<created-at type="dateTime">2016-05-07T01:41:25Z</created-at>
<updated-at type="dateTime">2016-05-07T01:41:25Z</updated-at>
<orders type="array">
<order>
<id type="integer">1</id>
<name>Dave Thomas</name>
<address>123 Main St</address>
<email>customer@example.com</email>
<pay-type>Check</pay-type>
<created-at type="dateTime">2016-05-07T01:42:42Z</created-at>
<updated-at type="dateTime">2016-05-07T01:42:42Z</updated-at>
</order>
<order>
<id type="integer">2</id>
<name>Dave Thomas</name>
<address>123 Main St</address>
<email>customer@example.com</email>
<pay-type>Check</pay-type>
<created-at type="dateTime">2016-05-07T01:42:43Z</created-at>
<updated-at type="dateTime">2016-05-07T01:42:43Z</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])
@latest_order = @product.orders.order(:updated_at).last
if stale?(@latest_order)
respond_to do |format|
format.html
format.xml { render xml: @product.to_xml(include: :orders) }
format.atom
end
end
end
See the (raw) HTML
curl --max-time 15 --silent --user dave:secret http://localhost:3000/products/2/who_bought
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<link rel="stylesheet" media="all" href="/assets/carts.self-25f9449bbb541af20c55a33ae10ad4a0581ff89d93770e6f8c5eb30376433926.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/line_items.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/orders.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/products.self-74c483697298190c57efb8592fab99872befcc16c0b6e421e8771ce5a0456c68.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/scaffolds.self-5ef79d98483572c763b03d76a8052f5e5fd114477b26ec1a0a1ce47c7b48bfbb.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/store.self-cd4ceb250da6e6654e703b6911b613e472c2f24152eed3ea332b105ba871ac96.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/application.self-bd908272b1d05b336f3373ce19637c5b37df37b8b69b8989bdd3477edc1d32b6.css?body=1" data-turbolinks-track="true" />
<script src="/assets/jquery.self-660adc51e0224b731d29f575a6f1ec167ba08ad06ed5deca4f1e8654c135bf4c.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/jquery-ui/effect.self-3acdb29e7da09c8f0195b994d2f04d73bfef50dc539c84fd2c835d964d33e5c1.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/jquery-ui/effect-blind.self-ed102209d7aa0b7374f3db8478cc7dcff5f9980d3146e8bf9fbfab959c6d5d8a.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/jquery_ujs.self-e87806d0cf4489aeb1bb7288016024e8de67fd18db693fe026fe3907581e53cd.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/turbolinks.self-088c452541bc2cb3c0ff28ae33e5c6516d309f7e097363b1d7d062f9cbe5a947.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable.self-624e720af3cc3383bc24125b2519b458606fc35725224bb7de70a68125ccdce2.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable/connection.self-48e4ed321caa1cc9ad280e3453760e11d5c604de7dbd57b30b8d64d1c039ffd7.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable/connection_monitor.self-5292327f499fd2a94e15e41efc87dc887b91328e95cc5305c014b17139b65ede.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable/subscriptions.self-156d38d6efaf3f6139b95c7fc9fcfc44a62d77f2c13108a2d58c33b0017eec10.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable/subscription.self-63bb8fcaf3e18e3bb4b849c831819921afe52d3e48e77f3c0d2d50fdf43a9b0c.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/action_cable/consumer.self-d0a6518de32602abb57c66be88b1fd0445527abfb6d8fad3a0be0c59eb6b7fd1.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/cable.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/carts.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/line_items.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/orders.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/products.self-a8a33f9d8c59f4fb06512fe6c718116aa1235770b989c89b5b6e078c94bd12d6.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/store.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/application.self-b5f8abbb026842e6f126160e1d1b70143792680186f4176fa4ea063de99e552c.js?body=1" data-turbolinks-track="true"></script>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="8BbZIy+sHbOCTYk2qUxfqPNYmX2jXbvKKjp6+gDnWW8p0bBa6sLh4RFSsyOcFKNaKBQ1bNIQCDPSeqzmULmkAw==" />
</head>
<body class="products">
<div id="banner">
<img src="/assets/logo-c6cc13d09c4262e637d9af74fa20f7cc0ba8cb57864863b3ce3135f37cbf308c.png" alt="Logo" />
<span class="title">Pragmatic Bookshelf</span>
</div>
<div id="columns">
<div id="side">
<!-- START_HIGHLIGHT -->
<!-- END_HIGHLIGHT -->
<ul>
<li><a href="http://www....">Home</a></li>
<li><a href="http://www..../faq">Questions</a></li>
<li><a href="http://www..../news">News</a></li>
<li><a href="http://www..../contact">Contact</a></li>
</ul>
</div>
<div id="main">
<h3>People Who Bought CoffeeScript</h3>
<ul>
<li>
<a href="mailto:customer@example.com">Dave Thomas</a>
</li>
<li>
<a href="mailto:customer@example.com">Dave Thomas</a>
</li>
</ul>
</div>
</div>
</body>
</html>
Anything that XML can do, JSON can too...
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.html
format.xml { render xml: @product.to_xml(include: :orders) }
format.atom
format.json { render json: @product.to_json(include: :orders) }
end
end
end
Fetch the data in JSON format
curl --max-time 15 --silent --user dave:secret http://localhost:3000/products/2/who_bought.json
{"id":2,"title":"CoffeeScript","description":"\u003cp\u003e\n CoffeeScript is JavaScript done right. It provides all of JavaScript's\n\tfunctionality wrapped in a cleaner, more succinct syntax. In the first\n\tbook on this exciting new language, CoffeeScript guru Trevor Burnham\n\tshows you how to hold onto all the power and flexibility of JavaScript\n\twhile writing clearer, cleaner, and safer code.\n \u003c/p\u003e","image_url":"cs.jpg","price":"36.0","created_at":"2016-05-07T01:41:25.978Z","updated_at":"2016-05-07T01:41:25.978Z","orders":[{"id":1,"name":"Dave Thomas","address":"123 Main St","email":"customer@example.com","pay_type":"Check","created_at":"2016-05-07T01:42:42.631Z","updated_at":"2016-05-07T01:42:42.631Z"},{"id":2,"name":"Dave Thomas","address":"123 Main St","email":"customer@example.com","pay_type":"Check","created_at":"2016-05-07T01:42:43.219Z","updated_at":"2016-05-07T01:42:43.219Z"}]}
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])
@latest_order = @product.orders.order(:updated_at).last
if stale?(@latest_order)
respond_to do |format|
format.html
format.xml
format.atom
format.json { render json: @product.to_json(include: :orders) }
end
end
end
Fetch the (much streamlined) XML
curl --max-time 15 --silent --user dave:secret http://localhost:3000/products/2/who_bought.xml
<order_list for_product="CoffeeScript">
<order>
<name>Dave Thomas</name>
<email>customer@example.com</email>
</order>
<order>
<name>Dave Thomas</name>
<email>customer@example.com</email>
</order>
</order_list>
Verify that the tests still pass
rails test
Run options: --seed 30761
# Running:
........................................
Finished in 0.643676s, 62.1431 runs/s, 142.9291 assertions/s.
40 runs, 92 assertions, 0 failures, 0 errors, 0 skips
Commit
git commit -a -m "Orders"
[master 252a975] Orders
7 files changed, 83 insertions(+), 36 deletions(-)
git tag iteration-g
13.1 Iteration H1: Email Notifications 12.3 Iteration G3: Downloading an eBook