13.1 Iteration H1: Webpacker and App-Like JavaScript 12.4 Iteration G2: Downloading an eBook
add activemodel-serializers-xml
edit Gemfile
gem 'activemodel-serializers-xml'
bundle install --local
Resolving dependencies...
Using rake 12.2.1
Using concurrent-ruby 1.0.5
Using i18n 0.9.1
Using minitest 5.10.3
Using thread_safe 0.3.6
Using tzinfo 1.2.4
Using activesupport 5.2.0.alpha from source at `/home/rubys/git/rails`
Using builder 3.2.3
Using erubi 1.7.0
Using mini_portile2 2.3.0
Using nokogiri 1.8.1
Using rails-dom-testing 2.0.3
Using crass 1.0.2
Using loofah 2.1.1
Using rails-html-sanitizer 1.0.3
Using actionview 5.2.0.alpha from source at `/home/rubys/git/rails`
Using rack 2.0.3
Using rack-test 0.7.0
Using actionpack 5.2.0.alpha from source at `/home/rubys/git/rails`
Using nio4r 2.1.0
Using websocket-extensions 0.1.3
Using websocket-driver 0.6.5
Using actioncable 5.2.0.alpha from source at `/home/rubys/git/rails`
Using globalid 0.4.1
Using activejob 5.2.0.alpha from source at `/home/rubys/git/rails`
Using mini_mime 1.0.0
Using mail 2.7.0
Using actionmailer 5.2.0.alpha from source at `/home/rubys/git/rails`
Using activemodel 5.2.0.alpha from source at `/home/rubys/git/rails`
Using activemodel-serializers-xml 1.0.2
Using arel 9.0.0.alpha from source at `/home/rubys/git/arel`
Using activerecord 5.2.0.alpha from source at `/home/rubys/git/rails`
Using activestorage 5.2.0.alpha from source at `/home/rubys/git/rails`
Using public_suffix 3.0.1
Using addressable 2.5.2
Using io-like 0.3.0
Using archive-zip 0.7.0
Using bindex 0.5.0
Using msgpack 1.1.0
Using bootsnap 1.1.5
Using bundler 1.16.0
Using byebug 9.1.0
Using xpath 2.1.0
Using capybara 2.15.4
Using ffi 1.9.18
Using childprocess 0.8.0
Using chromedriver-helper 1.1.0
Using coffee-script-source 1.12.2
Using execjs 2.7.0
Using coffee-script 2.4.1
Using method_source 0.9.0
Using thor 0.19.4
Using railties 5.2.0.alpha from source at `/home/rubys/git/rails`
Using coffee-rails 4.2.2
Using et-orbi 1.0.8
Using multi_json 1.12.2
Using jbuilder 2.7.0
Using mono_logger 1.1.0
Using mustermann 1.0.1
Using pg 0.19.0
Using puma 3.10.0
Using queue_classic 3.2.0.RC1 from source at `/home/rubys/git/queue_classic`
Using rack-protection 2.0.0
Using sprockets 3.7.1
Using sprockets-rails 3.2.1
Using rails 5.2.0.alpha from source at `/home/rubys/git/rails`
Using rb-fsevent 0.10.2
Using rb-inotify 0.9.9 from source at `/home/rubys/git/rb-inotify`
Using redis 4.0.1
Using redis-namespace 1.6.0
Using tilt 2.0.8
Using sinatra 2.0.0
Using vegas 0.1.11
Using resque 1.27.4
Using rufus-scheduler 3.4.2
Using resque-scheduler 4.3.0 from source at `/home/rubys/git/resque-scheduler`
Using rubyzip 1.2.1
Using sass-listen 4.0.0
Using sass 3.5.3
Using sass-rails 5.0.6 from source at `/home/rubys/git/sass-rails`
Using selenium-webdriver 3.7.0
Using spring 2.0.2
Using sqlite3 1.3.13
Using turbolinks-source 5.0.3
Using turbolinks 5.0.1
Using uglifier 3.2.0
Using web-console 3.5.1 from source at `/home/rubys/git/web-console`
Bundle complete! 21 Gemfile dependencies, 87 gems now installed.
Use `bundle info [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 Rails, Angular, Postgres, and Bootstrap</title>
<updated>2017-11-13T14:41:48Z</updated>
<entry>
<id>tag:localhost,2005:Order/1</id>
<published>2017-11-13T14:41:47Z</published>
<updated>2017-11-13T14:41:47Z</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>Rails, Angular, Postgres, and Bootstrap</td>
<td>2</td>
<td>$90.00</td>
</tr>
<tr>
<th colspan="2">total</th>
<th>$90.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>2017-11-13T14:41:48Z</published>
<updated>2017-11-13T14:41:48Z</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>Rails, Angular, Postgres, and Bootstrap</td>
<td>1</td>
<td>$45.00</td>
</tr>
<tr>
<th colspan="2">total</th>
<th>$45.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>Rails, Angular, Postgres, and Bootstrap</title>
<description><p>
<em>Powerful, Effective, and Efficient Full-Stack Web Development</em>
As a Rails developer, you care about user experience and performance,
but you also want simple and maintainable code. Achieve all that by
embracing the full stack of web development, from styling with
Bootstrap, building an interactive user interface with AngularJS, to
storing data quickly and reliably in PostgreSQL. Take a holistic view of
full-stack development to create usable, high-performing applications,
and learn to use these technologies effectively in a Ruby on Rails
environment.
</p></description>
<image-url>dcbang.jpg</image-url>
<price type="decimal">45.0</price>
<created-at type="dateTime">2017-11-13T14:40:47Z</created-at>
<updated-at type="dateTime">2017-11-13T14:40:47Z</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">2017-11-13T14:41:47Z</created-at>
<updated-at type="dateTime">2017-11-13T14:41:47Z</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">2017-11-13T14:41:48Z</created-at>
<updated-at type="dateTime">2017-11-13T14:41:48Z</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>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="47iQl53B9P7HzYGTdED0cYMK2tp3O9Pxh6Ta4Od0cnF808QO+JAis93iNyKMaGg6jdhZr8D7f6Z9VEeAI6z+yA==" />
<link rel="stylesheet" media="all" href="/assets/carts.self-c0a993f29270d01ba901199e776e3408adcd59c50d0328cb78ef8daf6d0b8d78.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/line_items.self-23c7c113151fcfd9c211b5787199d4fe746a8205d6284cab5f26cd11f63fcc16.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/orders.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/products.self-387bbd33f321c5301c2e0e3f749d270b86287b551b7d1882893982319c72d8f1.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/scaffolds.self-19fc0a723fbfeacc7c1cc176d8fda44fd631be78491564d3b8c5ddc3d9a2a927.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/store.self-448631757ec599657e1b23dc372d06b70d6e5391be1130c48b8239e5b6605066.css?body=1" data-turbolinks-track="reload" />
<link rel="stylesheet" media="all" href="/assets/application.self-9e5079f1d4fe3f7f353af6ceedfb82bb2511288f008d448959efdf6b577c825f.css?body=1" data-turbolinks-track="reload" />
<script src="/assets/rails-ujs/BANNER.self-b8bd05ec86fcc0ee2c622d117a0d8c811b183f28d65682257e794cd796d7be63.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs.self-1b5fb16fba4d2f2f015acb1cc99954c9011dd6759eaba877e98ed3aea121816d.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/utils/dom.self-0e219eb69a2d19adeaf4b1eb491783c1fe8f3158ef78b679a1dc82696067d900.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/utils/csrf.self-55a720dd18b0c75e0011a8020643b42c8831732eb36c445cff2b9b4e79619b9b.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/utils/event.self-dd5358215c0c7304ec44a19280d2a561cce6dfa9a0b6c744f7d4f40bb88337da.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/utils/ajax.self-8dac9a32ba5ad07fa8b7e35010d1b57e47990cbdf1cd343b7643e609c5d8ad3b.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/utils/form.self-426b0bb77eb6f2bcee0698a8e546e7d5f068a65556a84f4f7c489ba77fecbfa5.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/features/confirm.self-8bf1d7c04006b7e2b386cf7f728d9fe0bbc523ad52ccabd61a899b5214b94b1a.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/features/disable.self-2f84bd1b3679f2861f88be10bdccad15732948f2c28fd933f450f9432f9dfe0f.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/features/method.self-0dd1b5e2a6f8b7506548e903fed0db37cc3033ed189a02bed3ab2896770b4fc3.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/features/remote.self-bf90fce5da43261fcb79a2264df657ff16a7444a7066e67a471295388b8649e9.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/rails-ujs/start.self-f21a973effa4b558dbf5f856bd81376530bf67816594b179cf3cd0f3deee84bb.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/activestorage.self-e05f7be16dda98097b763bd7f65120b113b8912f128e0ae40ad5a267a8ff94d4.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/turbolinks.self-1d1fddf91adc38ac2045c51f0a3e05ca97d07d24d15a4dcbf705009106489e69.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable.self-c059bff9477e2f2fee35b8f12271e9342e14cab684c88b7d2cd9f767b40e9f55.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable/connection_monitor.self-dc0643774d220a91b7ba63973ddbc09f4a52965221d646c7830a22fb96ab7e58.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable/connection.self-b5647e57db10d083141c025937491cacdf77d04a548ce2635bb38a24247e10d9.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable/subscriptions.self-5c7dd80f075eacd4fc06ad385498d4c3a354011b505d8a8cef90df105c361c80.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable/subscription.self-0a82319db058bc79ff11535c02d44534daf61a646a32010e44e671e7181cc742.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/action_cable/consumer.self-def499e29967995b51c5ad790077021a5b1980f2e3330f4ac33010f6813867cd.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/cable.self-8484513823f404ed0c0f039f75243bfdede7af7919dda65f2e66391252443ce9.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/channels/products.self-c2d7e8873e32e8d2fe93bc490bf0920a58d4b356563b7cf6333868a5af3ef7f7.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/carts.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/line_items.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/orders.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/products.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/store.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="reload"></script>
<script src="/assets/application.self-66347cf0a4cb1f26f76868b4697a9eee457c8c3a6da80c6fdd76ff77e911715e.js?body=1" data-turbolinks-track="reload"></script>
</head>
<body>
<header class="main">
<img alt="The Pragmatic Bookshelf" src="/assets/logo-a8906fb6c933f00883d2ad0385ae52201c0e1d1f9235c37f4a5762f02ad80936.svg" />
<h1></h1>
</header>
<section class="content">
<nav class="side_nav">
<div id="cart" class="carts">
</div>
<!-- START_HIGHLIGHT -->
<!-- END_HIGHLIGHT -->
<ul>
<li><a href="/">Home</a></li>
<li><a href="/questions">Questions</a></li>
<li><a href="/news">News</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- END:side -->
<main class='products'>
<h3>People Who Bought Rails, Angular, Postgres, and Bootstrap</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>
</main>
</section>
</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":"Rails, Angular, Postgres, and Bootstrap","description":"\u003cp\u003e\n \u003cem\u003ePowerful, Effective, and Efficient Full-Stack Web Development\u003c/em\u003e\n As a Rails developer, you care about user experience and performance,\n but you also want simple and maintainable code. Achieve all that by\n embracing the full stack of web development, from styling with\n Bootstrap, building an interactive user interface with AngularJS, to\n storing data quickly and reliably in PostgreSQL. Take a holistic view of\n full-stack development to create usable, high-performing applications,\n and learn to use these technologies effectively in a Ruby on Rails\n environment.\n \u003c/p\u003e","image_url":"dcbang.jpg","price":"45.0","created_at":"2017-11-13T14:40:47.358Z","updated_at":"2017-11-13T14:40:47.358Z","orders":[{"id":1,"name":"Dave Thomas","address":"123 Main St","email":"customer@example.com","pay_type":"Check","created_at":"2017-11-13T14:41:47.723Z","updated_at":"2017-11-13T14:41:47.723Z"},{"id":2,"name":"Dave Thomas","address":"123 Main St","email":"customer@example.com","pay_type":"Check","created_at":"2017-11-13T14:41:48.266Z","updated_at":"2017-11-13T14:41:48.266Z"}]}
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="Rails, Angular, Postgres, and Bootstrap">
<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 62888
# Running:
.......................................
Finished in 0.719319s, 54.2180 runs/s, 109.8261 assertions/s.
39 runs, 79 assertions, 0 failures, 0 errors, 0 skips
Commit
git commit -a -m "Orders"
[master 1372f6e] Orders
7 files changed, 82 insertions(+), 34 deletions(-)
git tag iteration-g
13.1 Iteration H1: Webpacker and App-Like JavaScript 12.4 Iteration G2: Downloading an eBook