Agile Web Development with Rails, Edition 4
12.1 Iteration G1: Capturing an Order
11.5 Iteration F5: Making Images Clickable
11.6 Iteration F6: Testing AJAX changes
Verify that yes, indeed, the product index is broken.
get /products
NoMethodError in
Products#index
Showing /home/rubys/git/awdwr/edition4/work-192-31/depot/app/views/layouts/application.html.erb where line #21 raised:
undefined method `line_items' for nil:NilClass
Extracted source (around line #21):
18: <div id="side">
19: <!-- START_HIGHLIGHT -->
20: <!-- START:hidden_div -->
21: <%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
22: <%= render @cart %>
23: <% end %>
24: <!-- END:hidden_div -->
Rails.root: /home/rubys/git/awdwr/edition4/work-192-31/depot
Application Trace |
Framework Trace |
Full Trace
app/views/layouts/application.html.erb:21:in `_app_views_layouts_application_html_erb___1160470672154448099_70330161124220'
app/controllers/products_controller.rb:7:in `index'
/home/rubys/git/rails/actionpack/lib/action_view/template.rb:144:in `block in render'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:55:in `instrument'
/home/rubys/git/rails/actionpack/lib/action_view/template.rb:142:in `render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:52:in `render_with_layout'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:38:in `render_template'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:12:in `block in render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/abstract_renderer.rb:22:in `wrap_formats'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:9:in `render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/renderer.rb:36:in `render_template'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/renderer.rb:17:in `render'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:120:in `_render_template'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/streaming.rb:250:in `_render_template'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:114:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/renderers.rb:30:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/compatibility.rb:43:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:99:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rendering.rb:16:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:40:in `block (2 levels) in render'
/home/rubys/git/rails/activesupport/lib/active_support/core_ext/benchmark.rb:5:in `block in ms'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/benchmark.rb:310:in `realtime'
/home/rubys/git/rails/activesupport/lib/active_support/core_ext/benchmark.rb:5:in `ms'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:40:in `block in render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:78:in `cleanup_view_runtime'
/home/rubys/git/rails/activerecord/lib/active_record/railties/controller_runtime.rb:24:in `cleanup_view_runtime'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:39:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/implicit_render.rb:10:in `default_render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:268:in `block in retrieve_response_from_mimes'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:195:in `call'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:195:in `respond_to'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/base.rb:167:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rendering.rb:10:in `process_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/callbacks.rb:18:in `block in process_action'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:416:in `_run__3532434991100784667__process_action__71253464684219794__callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:386:in `_run_process_action_callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/rubys/git/rails/actionpack/lib/abstract_controller/callbacks.rb:17:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rescue.rb:17:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:30:in `block in process_action'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:53:in `block in instrument'
/home/rubys/git/rails/activesupport/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:53:in `instrument'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:29:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/params_wrapper.rb:201:in `process_action'
/home/rubys/git/rails/activerecord/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/base.rb:121:in `process'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:45:in `process'
/home/rubys/git/rails/actionpack/lib/action_controller/metal.rb:193:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_controller/metal.rb:236:in `block in action'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:71:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:71:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:35:in `call'
rack-mount (0.8.3) lib/rack/mount/route_set.rb:152:in `block in call'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:96:in `block in recognize'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:75:in `optimized_each'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:95:in `recognize'
rack-mount (0.8.3) lib/rack/mount/route_set.rb:141:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:538:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
rack (1.3.10) lib/rack/etag.rb:23:in `call'
rack (1.3.10) lib/rack/conditionalget.rb:25:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/head.rb:14:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/params_parser.rb:21:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/flash.rb:243:in `call'
rack (1.3.10) lib/rack/session/abstract/id.rb:195:in `context'
rack (1.3.10) lib/rack/session/abstract/id.rb:190:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/cookies.rb:331:in `call'
/home/rubys/git/rails/activerecord/lib/active_record/query_cache.rb:64:in `call'
/home/rubys/git/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb:477:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:392:in `_run_call_callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:28:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/reloader.rb:68:in `call'
rack (1.3.10) lib/rack/sendfile.rb:101:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/remote_ip.rb:48:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/show_exceptions.rb:47:in `call'
/home/rubys/git/rails/railties/lib/rails/rack/logger.rb:13:in `call'
rack (1.3.10) lib/rack/methodoverride.rb:24:in `call'
rack (1.3.10) lib/rack/runtime.rb:17:in `call'
/home/rubys/git/rails/activesupport/lib/active_support/cache/strategy/local_cache.rb:72:in `call'
rack (1.3.10) lib/rack/lock.rb:15:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/static.rb:61:in `call'
/home/rubys/git/rails/railties/lib/rails/engine.rb:456:in `call'
/home/rubys/git/rails/railties/lib/rails/application.rb:143:in `call'
rack (1.3.10) lib/rack/content_length.rb:14:in `call'
/home/rubys/git/rails/railties/lib/rails/rack/log_tailer.rb:14:in `call'
rack (1.3.10) lib/rack/handler/webrick.rb:59:in `service'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb:111:in `service'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb:70:in `run'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/server.rb:183:in `block in start_thread'
app/views/layouts/application.html.erb:21:in `_app_views_layouts_application_html_erb___1160470672154448099_70330161124220'
/home/rubys/git/rails/actionpack/lib/action_view/template.rb:144:in `block in render'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:55:in `instrument'
/home/rubys/git/rails/actionpack/lib/action_view/template.rb:142:in `render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:52:in `render_with_layout'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:38:in `render_template'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:12:in `block in render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/abstract_renderer.rb:22:in `wrap_formats'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/template_renderer.rb:9:in `render'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/renderer.rb:36:in `render_template'
/home/rubys/git/rails/actionpack/lib/action_view/renderer/renderer.rb:17:in `render'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:120:in `_render_template'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/streaming.rb:250:in `_render_template'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:114:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/renderers.rb:30:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/compatibility.rb:43:in `render_to_body'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:99:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rendering.rb:16:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:40:in `block (2 levels) in render'
/home/rubys/git/rails/activesupport/lib/active_support/core_ext/benchmark.rb:5:in `block in ms'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/benchmark.rb:310:in `realtime'
/home/rubys/git/rails/activesupport/lib/active_support/core_ext/benchmark.rb:5:in `ms'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:40:in `block in render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:78:in `cleanup_view_runtime'
/home/rubys/git/rails/activerecord/lib/active_record/railties/controller_runtime.rb:24:in `cleanup_view_runtime'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:39:in `render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/implicit_render.rb:10:in `default_render'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:268:in `block in retrieve_response_from_mimes'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:195:in `call'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/mime_responds.rb:195:in `respond_to'
app/controllers/products_controller.rb:7:in `index'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/base.rb:167:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rendering.rb:10:in `process_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/callbacks.rb:18:in `block in process_action'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:416:in `_run__3532434991100784667__process_action__71253464684219794__callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:386:in `_run_process_action_callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/rubys/git/rails/actionpack/lib/abstract_controller/callbacks.rb:17:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rescue.rb:17:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:30:in `block in process_action'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:53:in `block in instrument'
/home/rubys/git/rails/activesupport/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
/home/rubys/git/rails/activesupport/lib/active_support/notifications.rb:53:in `instrument'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/instrumentation.rb:29:in `process_action'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/params_wrapper.rb:201:in `process_action'
/home/rubys/git/rails/activerecord/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
/home/rubys/git/rails/actionpack/lib/abstract_controller/base.rb:121:in `process'
/home/rubys/git/rails/actionpack/lib/abstract_controller/rendering.rb:45:in `process'
/home/rubys/git/rails/actionpack/lib/action_controller/metal.rb:193:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_controller/metal.rb:236:in `block in action'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:71:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:71:in `dispatch'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:35:in `call'
rack-mount (0.8.3) lib/rack/mount/route_set.rb:152:in `block in call'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:96:in `block in recognize'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:75:in `optimized_each'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:95:in `recognize'
rack-mount (0.8.3) lib/rack/mount/route_set.rb:141:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/routing/route_set.rb:538:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
rack (1.3.10) lib/rack/etag.rb:23:in `call'
rack (1.3.10) lib/rack/conditionalget.rb:25:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/head.rb:14:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/params_parser.rb:21:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/flash.rb:243:in `call'
rack (1.3.10) lib/rack/session/abstract/id.rb:195:in `context'
rack (1.3.10) lib/rack/session/abstract/id.rb:190:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/cookies.rb:331:in `call'
/home/rubys/git/rails/activerecord/lib/active_record/query_cache.rb:64:in `call'
/home/rubys/git/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb:477:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:392:in `_run_call_callbacks'
/home/rubys/git/rails/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:28:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/reloader.rb:68:in `call'
rack (1.3.10) lib/rack/sendfile.rb:101:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/remote_ip.rb:48:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/show_exceptions.rb:47:in `call'
/home/rubys/git/rails/railties/lib/rails/rack/logger.rb:13:in `call'
rack (1.3.10) lib/rack/methodoverride.rb:24:in `call'
rack (1.3.10) lib/rack/runtime.rb:17:in `call'
/home/rubys/git/rails/activesupport/lib/active_support/cache/strategy/local_cache.rb:72:in `call'
rack (1.3.10) lib/rack/lock.rb:15:in `call'
/home/rubys/git/rails/actionpack/lib/action_dispatch/middleware/static.rb:61:in `call'
/home/rubys/git/rails/railties/lib/rails/engine.rb:456:in `call'
/home/rubys/git/rails/railties/lib/rails/application.rb:143:in `call'
rack (1.3.10) lib/rack/content_length.rb:14:in `call'
/home/rubys/git/rails/railties/lib/rails/rack/log_tailer.rb:14:in `call'
rack (1.3.10) lib/rack/handler/webrick.rb:59:in `service'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb:111:in `service'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb:70:in `run'
/home/rubys/.rvm/rubies/ruby-1.9.2-p320/lib/ruby/1.9.1/webrick/server.rb:183:in `block in start_thread'
Request
Parameters:
None
Show session dump
_csrf_token: "1Cr//B8wqLHyz7sIyEAoo4CgOzngSUTr26Yhs37Ibxg="
cart_id: 4
session_id: "00ff88f25f796d3232d0050405f08bed"
Show env dump
GATEWAY_INTERFACE: "CGI/1.1"
HTTP_ACCEPT: "text/html"
REMOTE_ADDR: "127.0.0.1"
REMOTE_HOST: "localhost"
SERVER_NAME: "localhost"
SERVER_PROTOCOL: "HTTP/1.1"
Response
Headers:
None
Conditionally display the cart.
edit app/views/layouts/application.html.erb
<% if @cart %>
<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
<%= render @cart %>
<% end %>
<% end %>
Update the redirect test.
edit test/functional/line_items_controller_test.rb
test "should create line_item" do
assert_difference('LineItem.count') do
post :create, product_id: products(:ruby).id
end
assert_redirected_to store_path
end
Add an XHR test.
edit test/functional/line_items_controller_test.rb
test "should create line_item via ajax" do
assert_difference('LineItem.count') do
xhr :post, :create, product_id: products(:ruby).id
end
assert_response :success
assert_select_jquery :html, '#cart' do
assert_select 'tr#current_item td', /Programming Ruby 1.9/
end
end
Add an test in support for the coffeescript changes.
edit test/functional/store_controller_test.rb
test "markup needed for store.js.coffee is in place" do
get :index
assert_select '.store .entry > img', 3
assert_select '.entry input[type=submit]', 3
end
Run the tests again.
rake test
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
CartTest:
PASS add duplicate product (0.27s)
PASS add unique products (0.01s)
ProductTest:
PASS image url (0.04s)
PASS product attributes must not be empty (0.00s)
PASS product is not valid without a unique title (0.00s)
PASS product is not valid without a unique title - i18n (0.00s)
PASS product price must be positive (0.00s)
Finished in 0.328884 seconds.
7 tests, 28 assertions, 0 failures, 0 errors, 0 skips
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
CartsControllerTest:
PASS should create cart (0.23s)
PASS should destroy cart (0.05s)
PASS should get edit (0.09s)
PASS should get index (0.01s)
PASS should get new (0.01s)
PASS should show cart (0.01s)
PASS should update cart (0.01s)
LineItemsControllerTest:
PASS should create line item (0.01s)
PASS should create line item via ajax (0.09s)
PASS should destroy line item (0.00s)
PASS should get edit (0.01s)
PASS should get index (0.01s)
PASS should get new (0.01s)
PASS should show line item (0.01s)
PASS should update line item (0.01s)
ProductsControllerTest:
PASS can't delete product in cart (0.01s)
PASS should create product (0.04s)
PASS should destroy product (0.00s)
PASS should get edit (0.01s)
PASS should get index (0.01s)
PASS should get new (0.01s)
PASS should show product (0.01s)
PASS should update product (0.01s)
StoreControllerTest:
PASS markup needed for store.js.coffee is in place (0.02s)
PASS should get index (0.01s)
Finished in 0.671230 seconds.
25 tests, 42 assertions, 0 failures, 0 errors, 0 skips
Save our progress
git commit -a -m "AJAX"
[master ce3f07a] AJAX
5 files changed, 48 insertions(+), 20 deletions(-)
rewrite app/assets/javascripts/application.js (67%)
git tag iteration-f
12.1 Iteration G1: Capturing an Order
11.5 Iteration F5: Making Images Clickable