Agile Web Development with Rails, Edition 4
12.4 Playtime
12.2 Iteration G2: Atom Feeds
12.3 Iteration G3: Downloading an eBook
demonstrate streaming with ActionController::Live
Switch to puma as a server
edit Gemfile
gem 'puma'
Restart the server.
add a route for downloading a product
edit config/routes.rb
mock streaming implementation for download
edit app/controllers/products_controller.rb
include ActionController::Live
def download
response.headers['Content-Type'] = 'text/plain'
40.times do |i|
response.stream.write "Line #{i}\n\n"
sleep 0.10
end
response.stream.write "Fini.\n"
ensure
response.stream.close
end
add order to the session
edit app/controllers/orders_controller.rb
def create
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)
respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
session[:order_id] = @order.id
format.html { redirect_to store_url, notice:
'Thank you for your order.' }
format.json { render :show, status: :created,
location: @order }
else
format.html { render :new }
format.json { render json: @order.errors,
status: :unprocessable_entity }
end
end
end
render order in the side bar
edit app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body class="<%= controller.controller_name %>">
<div id="banner">
<%= image_tag("logo.png") %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">
<% if @cart %>
<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
<%= render @cart %>
<% end %>
<% end %>
<%= render Order.find(session[:order_id]) if session[:order_id] -%>
<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">
<%= yield %>
</div>
</div>
</body>
</html>
implement order partial
edit app/views/orders/_order.html.erb
<div id="order">
<h2>Your Downloads</h2>
<table data-no-turbolink>
<% order.line_items.each do |item| %>
<tr>
<td><%= link_to item.product.title, download_product_path(item.product) %></td>
</tr>
<% end %>
</table>
</div>
css tweaks
edit app/assets/stylesheets/application.css.scss
#side {
float: left;
padding: 1em 2em;
width: 13em;
background: #141;
form, div {
display: inline;
}
input {
font-size: small;
}
#cart, #order {
font-size: smaller;
color: white;
a, a:hover {
color: white;
background-color: #141;
}
table {
border-top: 1px dotted #595;
border-bottom: 1px dotted #595;
margin-bottom: 10px;
}
}
ul {
padding: 0;
li {
list-style: none;
a {
color: #bfb;
font-size: small;
}
}
}
}
place an order
get /
Getting started
Here’s how to get rolling:
-
Use bin/rails generate
to create your models and controllers
To see all available options, run it without parameters.
-
Set up a root route to replace this page
You're seeing this page because you're running in development mode and you haven't set a root route yet.
Routes are set up in config/routes.rb.
-
Configure your database
If you're not using SQLite (the default), edit config/database.yml with your username and password.
get /orders/new
NameError
in OrdersController#new
undefined local variable or method `store_url' for #<OrdersController:0x007fb90c3d4498>
Did you mean? store_index_url
Extracted source (around line #26):
24
25
26
27
28
29
|
#START_HIGHLIGHT
if @cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end
|
Extracted source (around line #4):
2
3
4
5
6
7
|
module BasicImplicitRender
def send_action(method, *args)
super.tap { default_render unless performed? }
end
def default_render(*args)
|
Extracted source (around line #183):
181
182
183
184
185
186
|
# which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
# Actually call the method associated with the action. Override
|
Extracted source (around line #30):
28
29
30
31
32
33
|
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
super
end
# Check for double render errors and set the content_type after rendering.
|
Extracted source (around line #20):
18
19
20
21
22
23
|
def process_action(*args)
run_callbacks(:process_action) do
super
end
end
|
Extracted source (around line #126):
124
125
126
127
128
129
|
def call(env)
block = env.run_block
env.value = !env.halted && (!block || block.call)
env
end
end
|
Extracted source (around line #506):
504
505
506
507
508
509
|
def compile
@callbacks || @mutex.synchronize do
final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
callback.apply callback_sequence
end
|
Extracted source (around line #455):
453
454
455
456
457
458
|
def call(arg)
@before.each { |b| b.call(arg) }
value = @call.call(arg)
@after.each { |a| a.call(arg) }
value
end
|
Extracted source (around line #101):
99
100
101
102
103
104
|
runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
runner.call(e).value
end
end
|
Extracted source (around line #750):
748
749
750
751
752
753
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
__run_callbacks__(_#{name}_callbacks, &block)
end
RUBY
end
|
Extracted source (around line #90):
88
89
90
91
92
93
|
# end
def run_callbacks(kind, &block)
send "_run_#{kind}_callbacks", &block
end
private
|
Extracted source (around line #19):
17
18
19
20
21
22
|
# process_action callbacks around the normal behavior.
def process_action(*args)
run_callbacks(:process_action) do
super
end
end
|
Extracted source (around line #27):
25
26
27
28
29
30
|
private
def process_action(*args)
super
rescue Exception => exception
request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
rescue_with_handler(exception) || raise(exception)
|
Extracted source (around line #31):
29
30
31
32
33
34
|
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
begin
result = super
payload[:status] = response.status
result
ensure
|
Extracted source (around line #164):
162
163
164
165
166
167
|
def instrument(name, payload = {})
if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
end
|
Extracted source (around line #21):
19
20
21
22
23
24
|
listeners_state = start name, payload
begin
yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
payload[:exception_object] = e
|
Extracted source (around line #164):
162
163
164
165
166
167
|
def instrument(name, payload = {})
if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
end
|
Extracted source (around line #29):
27
28
29
30
31
32
|
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
begin
result = super
payload[:status] = response.status
|
Extracted source (around line #248):
246
247
248
249
250
251
|
request.filtered_parameters.merge! wrapped_filtered_hash
end
super
end
private
|
Extracted source (around line #18):
16
17
18
19
20
21
|
# and it won't be cleaned up by the method below.
ActiveRecord::LogSubscriber.reset_runtime
super
end
def cleanup_view_runtime
|
Extracted source (around line #128):
126
127
128
129
130
131
|
@_response_body = nil
process_action(action_name, *args)
end
# Delegates to the class' ::controller_path
|
Extracted source (around line #30):
28
29
30
31
32
33
|
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
super
ensure
I18n.config = old_config
end
|
Extracted source (around line #192):
190
191
192
193
194
195
|
set_request!(request)
set_response!(response)
process(name)
request.commit_flash
to_a
end
|
Extracted source (around line #264):
262
263
264
265
266
267
|
middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
else
new.dispatch(name, req, res)
end
end
end
|
Extracted source (around line #50):
48
49
50
51
52
53
|
def dispatch(controller, action, req, res)
controller.dispatch(action, req, res)
end
end
|
Extracted source (around line #32):
30
31
32
33
34
35
|
controller = controller req
res = controller.make_response! req
dispatch(controller, params[:action], req, res)
rescue ActionController::RoutingError
if @raise_on_name_error
raise
|
Extracted source (around line #42):
40
41
42
43
44
45
|
req.path_parameters = set_params.merge parameters
status, headers, body = route.app.serve(req)
if 'pass' == headers['X-Cascade']
req.script_name = script_name
|
Extracted source (around line #29):
27
28
29
30
31
32
|
def serve(req)
find_routes(req).each do |match, parameters, route|
set_params = req.path_parameters
path_info = req.path_info
script_name = req.script_name
|
Extracted source (around line #29):
27
28
29
30
31
32
|
def serve(req)
find_routes(req).each do |match, parameters, route|
set_params = req.path_parameters
path_info = req.path_info
script_name = req.script_name
|
Extracted source (around line #724):
722
723
724
725
726
727
|
req = make_request(env)
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
@router.serve(req)
end
def recognize_path(path, environment = {})
|
Extracted source (around line #14):
12
13
14
15
16
17
|
def call(env)
ActionView::Digestor.cache.clear
app.call(env)
end
end
|
Extracted source (around line #25):
23
24
25
26
27
28
|
def call(env)
status, headers, body = @app.call(env)
if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
original_body = body
|
Extracted source (around line #25):
23
24
25
26
27
28
|
case env[REQUEST_METHOD]
when "GET", "HEAD"
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
if status == 200 && fresh?(env, headers)
status = 304
|
Extracted source (around line #12):
10
11
12
13
14
15
|
def call(env)
status, headers, body = @app.call(env)
if env[REQUEST_METHOD] == HEAD
[
|
Extracted source (around line #220):
218
219
220
221
222
223
|
req = make_request env
prepare_session(req)
status, headers, body = app.call(req.env)
res = Rack::Response::Raw.new status, headers
commit_session(req, res)
[status, headers, body]
|
Extracted source (around line #214):
212
213
214
215
216
217
|
def call(env)
context(env)
end
def context(env, app=@app)
|
Extracted source (around line #608):
606
607
608
609
610
611
|
request = ActionDispatch::Request.new env
status, headers, body = @app.call(env)
if request.have_cookie_jar?
cookie_jar = request.cookie_jar
|
Extracted source (around line #36):
34
35
36
37
38
39
|
connection.enable_query_cache!
response = @app.call(env)
response[2] = Rack::BodyProxy.new(response[2]) do
restore_query_cache_settings(connection_id, enabled)
end
|
Extracted source (around line #963):
961
962
963
964
965
966
|
testing = env['rack.test']
status, headers, body = @app.call(env)
proxy = ::Rack::BodyProxy.new(body) do
ActiveRecord::Base.clear_active_connections! unless testing
end
|
Extracted source (around line #558):
556
557
558
559
560
561
|
end
end
@app.call(env)
end
private
|
Extracted source (around line #29):
27
28
29
30
31
32
|
result = run_callbacks :call do
begin
@app.call(env)
rescue => error
end
end
|
Extracted source (around line #97):
95
96
97
98
99
100
|
def __run_callbacks__(callbacks, &block)
if callbacks.empty?
yield if block_given?
else
runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
|
Extracted source (around line #750):
748
749
750
751
752
753
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
__run_callbacks__(_#{name}_callbacks, &block)
end
RUBY
end
|
Extracted source (around line #90):
88
89
90
91
92
93
|
# end
def run_callbacks(kind, &block)
send "_run_#{kind}_callbacks", &block
end
private
|
Extracted source (around line #27):
25
26
27
28
29
30
|
def call(env)
error = nil
result = run_callbacks :call do
begin
@app.call(env)
rescue => error
|
Extracted source (around line #71):
69
70
71
72
73
74
|
prepare!
response = @app.call(env)
response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
response
|
Extracted source (around line #79):
77
78
79
80
81
82
|
req = ActionDispatch::Request.new env
req.remote_ip = GetIp.new(req, check_ip, proxies)
@app.call(req.env)
end
# The GetIp class exists as a way to defer processing of the request data
|
Extracted source (around line #49):
47
48
49
50
51
52
|
def call(env)
request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env)
if headers['X-Cascade'] == 'pass'
body.close if body.respond_to?(:close)
|
Extracted source (around line #131):
129
130
131
132
133
134
|
def call_app(env)
@app.call(env)
rescue => e
throw :app_exception, e
end
|
Extracted source (around line #28):
26
27
28
29
30
31
|
end
status, headers, body = call_app(env)
if session = Session.from(Thread.current) and acceptable_content_type?(headers)
response = Response.new(body, status, headers)
|
Extracted source (around line #18):
16
17
18
19
20
21
|
def call(env)
app_exception = catch :app_exception do
request = create_regular_or_whiny_request(env)
return call_app(env) unless request.from_whitelisted_ip?
|
Extracted source (around line #18):
16
17
18
19
20
21
|
def call(env)
app_exception = catch :app_exception do
request = create_regular_or_whiny_request(env)
return call_app(env) unless request.from_whitelisted_ip?
|
Extracted source (around line #31):
29
30
31
32
33
34
|
def call(env)
request = ActionDispatch::Request.new env
@app.call(env)
rescue Exception => exception
if request.show_exceptions?
render_exception(request, exception)
|
Extracted source (around line #42):
40
41
42
43
44
45
|
instrumenter.start 'request.action_dispatch', request: request
logger.info { started_request_message(request) }
resp = @app.call(env)
resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
resp
rescue Exception
|
Extracted source (around line #24):
22
23
24
25
26
27
|
if logger.respond_to?(:tagged)
logger.tagged(compute_tags(request)) { call_app(request, env) }
else
call_app(request, env)
end
|
Extracted source (around line #70):
68
69
70
71
72
73
|
def tagged(*tags)
formatter.tagged(*tags) { yield self }
end
def flush
|
Extracted source (around line #26):
24
25
26
27
28
29
|
def tagged(*tags)
new_tags = push_tags(*tags)
yield self
ensure
pop_tags(new_tags.size)
end
|
Extracted source (around line #70):
68
69
70
71
72
73
|
def tagged(*tags)
formatter.tagged(*tags) { yield self }
end
def flush
|
Extracted source (around line #24):
22
23
24
25
26
27
|
if logger.respond_to?(:tagged)
logger.tagged(compute_tags(request)) { call_app(request, env) }
else
call_app(request, env)
end
|
Extracted source (around line #24):
22
23
24
25
26
27
|
req = ActionDispatch::Request.new env
req.request_id = make_request_id(req.x_request_id)
@app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = req.request_id }
end
private
|
Extracted source (around line #22):
20
21
22
23
24
25
|
end
@app.call(env)
end
def method_override(env)
|
Extracted source (around line #22):
20
21
22
23
24
25
|
def call(env)
start_time = Utils.clock_time
status, headers, body = @app.call(env)
request_time = Utils.clock_time - start_time
unless headers.has_key?(@header_name)
|
Extracted source (around line #28):
26
27
28
29
30
31
|
def call(env)
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
response = @app.call(env)
response[2] = ::Rack::BodyProxy.new(response[2]) do
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
|
Extracted source (around line #13):
11
12
13
14
15
16
|
interlock = ActiveSupport::Dependencies.interlock
interlock.start_running
response = @app.call(env)
body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
response[2] = body
response
|
Extracted source (around line #136):
134
135
136
137
138
139
|
end
@app.call(req.env)
end
end
end
|
Extracted source (around line #111):
109
110
111
112
113
114
|
def call(env)
status, headers, body = @app.call(env)
if body.respond_to?(:to_path)
case type = variation(env)
when 'X-Accel-Redirect'
|
Extracted source (around line #522):
520
521
522
523
524
525
|
def call(env)
req = build_request env
app.call req.env
end
# Defines additional Rack env configuration that is added on each call.
|
Extracted source (around line #541):
539
540
541
542
543
544
|
begin
begin
status, headers, res_body = @app.call(env)
return :async if req.hijacked
|
Extracted source (around line #388):
386
387
388
389
390
391
|
while true
case handle_request(client, buffer)
when false
return
when :async
|
Extracted source (around line #270):
268
269
270
271
272
273
|
else
if process_now
process_client client, buffer
else
client.set_timeout @first_data_timeout
@reactor.add client
|
Extracted source (around line #106):
104
105
106
107
108
109
|
begin
block.call(work, *extra)
rescue Exception
end
end
|
Rails.root: /home/rubys/git/awdwr/edition4/work-230/depot
Request
Parameters:
None
_csrf_token: "u70b4cghf6vBhb0o9ek+1v+ZllSLc8zQEv3YTxsPx2o="
cart_id: 3
session_id: "b519f56909f0cc26d35e76b9481983db"
GATEWAY_INTERFACE: "CGI/1.2"
HTTP_ACCEPT: "text/html"
HTTP_ACCEPT_ENCODING: "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
HTTP_VERSION: "HTTP/1.1"
ORIGINAL_SCRIPT_NAME: ""
REMOTE_ADDR: "127.0.0.1"
SERVER_NAME: "localhost"
SERVER_PROTOCOL: "HTTP/1.1"
Response
Headers:
None
click download
get /products/2/download
Line 0
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15
Line 16
Line 17
Line 18
Line 19
Line 20
Line 21
Line 22
Line 23
Line 24
Line 25
Line 26
Line 27
Line 28
Line 29
Line 30
Line 31
Line 32
Line 33
Line 34
Line 35
Line 36
Line 37
Line 38
Line 39
Fini.
Switch back to WEBRick
edit Gemfile
# gem 'puma'
Restart the server.
12.4 Playtime
12.2 Iteration G2: Atom Feeds