roda 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/README.rdoc +18 -13
- data/Rakefile +8 -0
- data/doc/conventions.rdoc +163 -0
- data/doc/release_notes/1.1.0.txt +226 -0
- data/lib/roda.rb +51 -22
- data/lib/roda/plugins/assets.rb +613 -0
- data/lib/roda/plugins/caching.rb +215 -0
- data/lib/roda/plugins/chunked.rb +278 -0
- data/lib/roda/plugins/error_email.rb +112 -0
- data/lib/roda/plugins/flash.rb +3 -3
- data/lib/roda/plugins/hooks.rb +1 -1
- data/lib/roda/plugins/indifferent_params.rb +3 -3
- data/lib/roda/plugins/middleware.rb +3 -8
- data/lib/roda/plugins/multi_route.rb +110 -18
- data/lib/roda/plugins/not_allowed.rb +3 -3
- data/lib/roda/plugins/path.rb +38 -0
- data/lib/roda/plugins/render.rb +18 -16
- data/lib/roda/plugins/render_each.rb +0 -2
- data/lib/roda/plugins/streaming.rb +1 -2
- data/lib/roda/plugins/view_subdirs.rb +7 -1
- data/lib/roda/version.rb +1 -1
- data/spec/assets/css/app.scss +1 -0
- data/spec/assets/css/no_access.css +1 -0
- data/spec/assets/css/raw.css +1 -0
- data/spec/assets/js/head/app.js +1 -0
- data/spec/integration_spec.rb +95 -3
- data/spec/matchers_spec.rb +2 -2
- data/spec/plugin/assets_spec.rb +413 -0
- data/spec/plugin/caching_spec.rb +335 -0
- data/spec/plugin/chunked_spec.rb +182 -0
- data/spec/plugin/default_headers_spec.rb +6 -5
- data/spec/plugin/error_email_spec.rb +76 -0
- data/spec/plugin/multi_route_spec.rb +120 -0
- data/spec/plugin/not_allowed_spec.rb +14 -3
- data/spec/plugin/path_spec.rb +29 -0
- data/spec/plugin/render_each_spec.rb +6 -1
- data/spec/plugin/symbol_matchers_spec.rb +7 -2
- data/spec/request_spec.rb +10 -0
- data/spec/response_spec.rb +47 -0
- data/spec/views/about.erb +1 -0
- data/spec/views/about.str +1 -0
- data/spec/views/content-yield.erb +1 -0
- data/spec/views/home.erb +2 -0
- data/spec/views/home.str +2 -0
- data/spec/views/layout-alternative.erb +2 -0
- data/spec/views/layout-yield.erb +3 -0
- data/spec/views/layout.erb +2 -0
- data/spec/views/layout.str +2 -0
- metadata +57 -2
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'net/smtp'
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module RodaPlugins
|
5
|
+
# The error_email plugin adds an +error_email+ instance method that
|
6
|
+
# send an email related to the exception. This is most useful if you are
|
7
|
+
# also using the error_handler plugin:
|
8
|
+
#
|
9
|
+
# plugin :error_email, :to=>'to@example.com', :from=>'from@example.com'
|
10
|
+
# plugin :error_handler do |e|
|
11
|
+
# error_email(e)
|
12
|
+
# 'Internal Server Error'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Options:
|
16
|
+
#
|
17
|
+
# :from :: The From address to use in the email (required)
|
18
|
+
# :headers :: A hash of additional headers to use in the email (default: empty hash)
|
19
|
+
# :host :: The SMTP server to use to send the email (default: localhost)
|
20
|
+
# :prefix :: A prefix to use in the email's subject line (default: no prefix)
|
21
|
+
# :to :: The To address to use in the email (required)
|
22
|
+
#
|
23
|
+
# The subject of the error email shows the exception class and message.
|
24
|
+
# The body of the error email shows the backtrace of the error and the
|
25
|
+
# request environment, as well the request params and session variables (if any).
|
26
|
+
#
|
27
|
+
# Note that emailing on every error as shown above is only appropriate
|
28
|
+
# for low traffic web applications. For high traffic web applications,
|
29
|
+
# use an error reporting service instead of this plugin.
|
30
|
+
module ErrorEmail
|
31
|
+
DEFAULTS = {
|
32
|
+
:headers=>{},
|
33
|
+
:host=>'localhost',
|
34
|
+
# :nocov:
|
35
|
+
:emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
|
36
|
+
# :nocov:
|
37
|
+
:default_headers=>lambda do |h, e|
|
38
|
+
{'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{e.class}: #{e.message}"}
|
39
|
+
end,
|
40
|
+
:body=>lambda do |s, e|
|
41
|
+
format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
|
42
|
+
|
43
|
+
message = <<END
|
44
|
+
Path: #{s.request.full_path_info}
|
45
|
+
|
46
|
+
Backtrace:
|
47
|
+
|
48
|
+
#{e.backtrace.join("\n")}
|
49
|
+
|
50
|
+
ENV:
|
51
|
+
|
52
|
+
#{format[s.env]}
|
53
|
+
END
|
54
|
+
unless s.request.params.empty?
|
55
|
+
message << <<END
|
56
|
+
|
57
|
+
Params:
|
58
|
+
|
59
|
+
#{format[s.request.params]}
|
60
|
+
END
|
61
|
+
end
|
62
|
+
|
63
|
+
if s.env['rack.session']
|
64
|
+
message << <<END
|
65
|
+
|
66
|
+
Session:
|
67
|
+
|
68
|
+
#{format[s.session]}
|
69
|
+
END
|
70
|
+
end
|
71
|
+
|
72
|
+
message
|
73
|
+
end
|
74
|
+
}
|
75
|
+
|
76
|
+
# Set default opts for plugin. See ErrorEmail module RDoc for options.
|
77
|
+
def self.configure(app, opts={})
|
78
|
+
email_opts = app.opts[:error_email] ||= DEFAULTS
|
79
|
+
email_opts = email_opts.merge(opts)
|
80
|
+
unless email_opts[:to] && email_opts[:from]
|
81
|
+
raise RodaError, "must provide :to and :from options to error_email plugin"
|
82
|
+
end
|
83
|
+
app.opts[:error_email] = email_opts
|
84
|
+
end
|
85
|
+
|
86
|
+
module ClassMethods
|
87
|
+
# Dup the error email opts in the subclass so changes to the subclass do not affect
|
88
|
+
# the superclass.
|
89
|
+
def inherited(subclass)
|
90
|
+
super
|
91
|
+
subclass.opts[:error_email] = subclass.opts[:error_email].dup
|
92
|
+
subclass.opts[:error_email][:headers] = subclass.opts[:error_email][:headers].dup
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
module InstanceMethods
|
97
|
+
# Send an email for the given error.
|
98
|
+
def error_email(e)
|
99
|
+
email_opts = self.class.opts[:error_email].dup
|
100
|
+
headers = email_opts[:default_headers].call(email_opts, e)
|
101
|
+
headers = headers.merge(email_opts[:headers])
|
102
|
+
headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
|
103
|
+
body = email_opts[:body].call(self, e)
|
104
|
+
email_opts[:message] = "#{headers}\r\n\r\n#{body}"
|
105
|
+
email_opts[:emailer].call(email_opts)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
register_plugin(:error_email, ErrorEmail)
|
111
|
+
end
|
112
|
+
end
|
data/lib/roda/plugins/flash.rb
CHANGED
@@ -26,6 +26,9 @@ class Roda
|
|
26
26
|
# end
|
27
27
|
# end
|
28
28
|
module Flash
|
29
|
+
# The internal session key used to store the flash.
|
30
|
+
KEY = :_flash
|
31
|
+
|
29
32
|
# Simple flash hash, where assiging to the hash updates the flash
|
30
33
|
# used in the following request.
|
31
34
|
class FlashHash < DelegateClass(Hash)
|
@@ -78,9 +81,6 @@ class Roda
|
|
78
81
|
end
|
79
82
|
|
80
83
|
module InstanceMethods
|
81
|
-
# The internal session key used to store the flash.
|
82
|
-
KEY = :_flash
|
83
|
-
|
84
84
|
# Access the flash hash for the current request, loading
|
85
85
|
# it from the session if it is not already loaded.
|
86
86
|
def flash
|
data/lib/roda/plugins/hooks.rb
CHANGED
@@ -30,9 +30,9 @@ class Roda
|
|
30
30
|
def indifferent_params(params)
|
31
31
|
case params
|
32
32
|
when Hash
|
33
|
-
|
34
|
-
params.each{|k, v|
|
35
|
-
|
33
|
+
hash = Hash.new{|h, k| h[k.to_s] if Symbol === k}
|
34
|
+
params.each{|k, v| hash[k] = indifferent_params(v)}
|
35
|
+
hash
|
36
36
|
when Array
|
37
37
|
params.map{|x| indifferent_params(x)}
|
38
38
|
else
|
@@ -62,14 +62,9 @@ class Roda
|
|
62
62
|
end
|
63
63
|
|
64
64
|
module ClassMethods
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
if app
|
69
|
-
Forwarder.new(self, app)
|
70
|
-
else
|
71
|
-
super()
|
72
|
-
end
|
65
|
+
# Create a Forwarder instead of a new instance.
|
66
|
+
def new(app)
|
67
|
+
Forwarder.new(self, app)
|
73
68
|
end
|
74
69
|
|
75
70
|
# Override the route block so that if no route matches, we throw so
|
@@ -51,35 +51,113 @@ class Roda
|
|
51
51
|
# r.multi_route do
|
52
52
|
# "default body"
|
53
53
|
# end
|
54
|
+
#
|
55
|
+
# If a block is not provided to multi_route, the return value of the named
|
56
|
+
# route block will be used.
|
57
|
+
#
|
58
|
+
# == Routing Files
|
59
|
+
#
|
60
|
+
# The convention when using the multi_route plugin is to have a single
|
61
|
+
# named route per file, and these routing files should be stored in
|
62
|
+
# a routes subdirectory in your application. So for the above example, you
|
63
|
+
# would use the following files:
|
64
|
+
#
|
65
|
+
# routes/bar.rb
|
66
|
+
# routes/foo.rb
|
67
|
+
#
|
68
|
+
# == Namespace Support
|
69
|
+
#
|
70
|
+
# The multi_route plugin also has support for namespaces, allowing you to
|
71
|
+
# use r.multi_route at multiple levels in your routing tree. Example:
|
72
|
+
#
|
73
|
+
# route('foo') do |r|
|
74
|
+
# r.multi_route('foo')
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# route('bar') do |r|
|
78
|
+
# r.multi_route('bar')
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# route('baz', 'foo') do |r|
|
82
|
+
# # handles /foo/baz prefix
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# route('quux', 'foo') do |r|
|
86
|
+
# # handles /foo/quux prefix
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# route('baz', 'bar') do |r|
|
90
|
+
# # handles /bar/baz prefix
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# route('quux', 'bar') do |r|
|
94
|
+
# # handles /bar/quux prefix
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# route do |r|
|
98
|
+
# r.multi_route
|
99
|
+
#
|
100
|
+
# # or
|
101
|
+
#
|
102
|
+
# r.on "foo" do
|
103
|
+
# r.on("baz"){r.route("baz", "foo")}
|
104
|
+
# r.on("quux"){r.route("quux", "foo")}
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# r.on "bar" do
|
108
|
+
# r.on("baz"){r.route("baz", "bar")}
|
109
|
+
# r.on("quux"){r.route("quux", "bar")}
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# === Routing Files
|
114
|
+
#
|
115
|
+
# The convention when using namespaces with the multi_route plugin is to
|
116
|
+
# store the routing files in subdirectories per namespace. So for the
|
117
|
+
# above example, you would have the following routing files:
|
118
|
+
#
|
119
|
+
# routes/bar.rb
|
120
|
+
# routes/bar/baz.rb
|
121
|
+
# routes/bar/quux.rb
|
122
|
+
# routes/foo.rb
|
123
|
+
# routes/foo/baz.rb
|
124
|
+
# routes/foo/quux.rb
|
54
125
|
module MultiRoute
|
55
126
|
# Initialize storage for the named routes.
|
56
127
|
def self.configure(app)
|
57
|
-
app.instance_exec
|
128
|
+
app.instance_exec do
|
129
|
+
@namespaced_routes ||= {}
|
130
|
+
app::RodaRequest.instance_variable_set(:@namespaced_route_regexps, {})
|
131
|
+
end
|
58
132
|
end
|
59
133
|
|
60
134
|
module ClassMethods
|
61
135
|
# Copy the named routes into the subclass when inheriting.
|
62
136
|
def inherited(subclass)
|
63
137
|
super
|
64
|
-
subclass.instance_variable_set(:@
|
138
|
+
nsr = subclass.instance_variable_set(:@namespaced_routes, {})
|
139
|
+
@namespaced_routes.each{|k, v| nsr[k] = v.dup}
|
140
|
+
subclass::RodaRequest.instance_variable_set(:@namespaced_route_regexps, {})
|
65
141
|
end
|
66
142
|
|
67
|
-
#
|
68
|
-
def named_routes
|
69
|
-
@
|
143
|
+
# The names for the currently stored named routes
|
144
|
+
def named_routes(namespace=nil)
|
145
|
+
@namespaced_routes[namespace].keys
|
70
146
|
end
|
71
147
|
|
72
148
|
# Return the named route with the given name.
|
73
|
-
def named_route(name)
|
74
|
-
@
|
149
|
+
def named_route(name, namespace=nil)
|
150
|
+
@namespaced_routes[namespace][name]
|
75
151
|
end
|
76
152
|
|
77
|
-
# If the given route has a
|
153
|
+
# If the given route has a name, treat it as a named route and
|
78
154
|
# store the route block. Otherwise, this is the main route, so
|
79
155
|
# call super.
|
80
|
-
def route(name=nil, &block)
|
156
|
+
def route(name=nil, namespace=nil, &block)
|
81
157
|
if name
|
82
|
-
@
|
158
|
+
@namespaced_routes[namespace] ||= {}
|
159
|
+
@namespaced_routes[namespace][name] = block
|
160
|
+
self::RodaRequest.clear_named_route_regexp!(namespace)
|
83
161
|
else
|
84
162
|
super(&block)
|
85
163
|
end
|
@@ -87,9 +165,19 @@ class Roda
|
|
87
165
|
end
|
88
166
|
|
89
167
|
module RequestClassMethods
|
168
|
+
# Clear cached regexp for named routes, it will be regenerated
|
169
|
+
# the next time it is needed.
|
170
|
+
#
|
171
|
+
# This shouldn't be an issue in production applications, but
|
172
|
+
# during development it's useful to support new named routes
|
173
|
+
# being added while the application is running.
|
174
|
+
def clear_named_route_regexp!(namespace=nil)
|
175
|
+
@namespaced_route_regexps.delete(namespace)
|
176
|
+
end
|
177
|
+
|
90
178
|
# A regexp matching any of the current named routes.
|
91
|
-
def named_route_regexp
|
92
|
-
@
|
179
|
+
def named_route_regexp(namespace=nil)
|
180
|
+
@namespaced_route_regexps[namespace] ||= /(#{Regexp.union(roda_class.named_routes(namespace).select{|s| s.is_a?(String)}.sort.reverse)})/
|
93
181
|
end
|
94
182
|
end
|
95
183
|
|
@@ -98,16 +186,20 @@ class Roda
|
|
98
186
|
# named routes. If so, call that named route. If not, do nothing.
|
99
187
|
# If the named route does not handle the request, and a block
|
100
188
|
# is given, yield to the block.
|
101
|
-
def multi_route
|
102
|
-
on self.class.named_route_regexp do |section|
|
103
|
-
route(section)
|
104
|
-
|
189
|
+
def multi_route(namespace=nil)
|
190
|
+
on self.class.named_route_regexp(namespace) do |section|
|
191
|
+
r = route(section, namespace)
|
192
|
+
if block_given?
|
193
|
+
yield
|
194
|
+
else
|
195
|
+
r
|
196
|
+
end
|
105
197
|
end
|
106
198
|
end
|
107
199
|
|
108
200
|
# Dispatch to the named route with the given name.
|
109
|
-
def route(name)
|
110
|
-
scope.instance_exec(self, &self.class.roda_class.named_route(name))
|
201
|
+
def route(name, namespace=nil)
|
202
|
+
scope.instance_exec(self, &self.class.roda_class.named_route(name, namespace))
|
111
203
|
end
|
112
204
|
end
|
113
205
|
end
|
@@ -84,13 +84,13 @@ class Roda
|
|
84
84
|
mod.class_eval(<<-END, __FILE__, __LINE__+1)
|
85
85
|
def #{verb}(*args, &block)
|
86
86
|
if args.empty?
|
87
|
-
@_is_verbs << "#{verb.to_s.upcase}" if @_is_verbs
|
87
|
+
@_is_verbs << "#{verb.to_s.upcase}" if defined?(@_is_verbs)
|
88
88
|
always(&block) if #{verb == :get ? :is_get : verb}?
|
89
89
|
else
|
90
90
|
args << ::Roda::RodaPlugins::Base::RequestMethods::TERM
|
91
|
-
if_match(args) do |*
|
91
|
+
if_match(args) do |*a|
|
92
92
|
if #{verb == :get ? :is_get : verb}?
|
93
|
-
block_result(yield(*
|
93
|
+
block_result(yield(*a))
|
94
94
|
throw :halt, response.finish
|
95
95
|
end
|
96
96
|
response.status = 405
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The path plugin adds support for named paths. Using the +path+ class method, you can
|
4
|
+
# easily create <tt>*_path</tt> instance methods for each named path. Those instance
|
5
|
+
# methods can then be called if you need to get the path for a form action, link,
|
6
|
+
# redirect, or anything else. Example:
|
7
|
+
#
|
8
|
+
# plugin :path
|
9
|
+
# path :foo, '/foo'
|
10
|
+
# path :bar do |bar|
|
11
|
+
# "/bar/#{bar.id}"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# route do |r|
|
15
|
+
# r.post 'bar' do
|
16
|
+
# bar = Bar.create(r.params['bar'])
|
17
|
+
# r.redirect bar_path(bar)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
module Path
|
21
|
+
module ClassMethods
|
22
|
+
def path(name, path=nil, &block)
|
23
|
+
raise RodaError, "cannot provide both path and block to Roda.path" if path && block
|
24
|
+
raise RodaError, "must provide either path or block to Roda.path" unless path || block
|
25
|
+
|
26
|
+
if path
|
27
|
+
path = path.dup.freeze
|
28
|
+
block = lambda{path}
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method("#{name}_path", &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
register_plugin(:path, Path)
|
37
|
+
end
|
38
|
+
end
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -18,20 +18,18 @@ class Roda
|
|
18
18
|
# end
|
19
19
|
# end
|
20
20
|
#
|
21
|
-
# You can provide options to the plugin method
|
22
|
-
# +render_opts+.
|
21
|
+
# You can provide options to the plugin method:
|
23
22
|
#
|
24
|
-
# plugin :render, :engine=>'haml'
|
25
|
-
#
|
26
|
-
# render_opts[:views] = 'admin_views'
|
23
|
+
# plugin :render, :engine=>'haml', :views=>'admin_views'
|
27
24
|
#
|
28
25
|
# The following options are supported:
|
29
26
|
#
|
30
27
|
# :cache :: nil/false to not cache templates (useful for development), defaults
|
31
28
|
# to true to automatically use the default template cache.
|
32
29
|
# :engine :: The tilt engine to use for rendering, defaults to 'erb'.
|
33
|
-
# :escape :: Use Roda's Erubis escaping support, which
|
34
|
-
#
|
30
|
+
# :escape :: Use Roda's Erubis escaping support, which makes <%= %> escape output,
|
31
|
+
# <%== %> not escape output, and handles postfix conditions inside
|
32
|
+
# <%= %> tags.
|
35
33
|
# :ext :: The file extension to assume for view files, defaults to the :engine
|
36
34
|
# option.
|
37
35
|
# :layout :: The base name of the layout file, defaults to 'layout'.
|
@@ -69,23 +67,25 @@ class Roda
|
|
69
67
|
# If you pass a hash as the first argument to +view+ or +render+, it should
|
70
68
|
# have either +:inline+ or +:path+ as one of the keys.
|
71
69
|
module Render
|
72
|
-
|
70
|
+
OPTS={}.freeze
|
71
|
+
|
72
|
+
def self.load_dependencies(app, opts=OPTS)
|
73
73
|
if opts[:escape]
|
74
74
|
app.plugin :_erubis_escaping
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
78
|
# Setup default rendering options. See Render for details.
|
79
|
-
def self.configure(app, opts=
|
79
|
+
def self.configure(app, opts=OPTS)
|
80
80
|
if app.opts[:render]
|
81
81
|
app.opts[:render].merge!(opts)
|
82
|
-
else
|
82
|
+
else
|
83
83
|
app.opts[:render] = opts.dup
|
84
84
|
end
|
85
85
|
|
86
86
|
opts = app.opts[:render]
|
87
87
|
opts[:engine] ||= "erb"
|
88
|
-
opts[:ext] = nil unless opts.has_key?(:ext)
|
88
|
+
opts[:ext] = nil unless opts.has_key?(:ext)
|
89
89
|
opts[:views] ||= File.expand_path("views", Dir.pwd)
|
90
90
|
opts[:layout] = "layout" unless opts.has_key?(:layout)
|
91
91
|
opts[:layout_opts] ||= (opts[:layout_opts] || {}).dup
|
@@ -120,7 +120,7 @@ class Roda
|
|
120
120
|
|
121
121
|
module InstanceMethods
|
122
122
|
# Render the given template. See Render for details.
|
123
|
-
def render(template, opts =
|
123
|
+
def render(template, opts = OPTS, &block)
|
124
124
|
if template.is_a?(Hash)
|
125
125
|
if opts.empty?
|
126
126
|
opts = template
|
@@ -143,10 +143,12 @@ class Roda
|
|
143
143
|
|
144
144
|
cached_template(path) do
|
145
145
|
template_class.new(path, 1, render_opts[:opts].merge(opts), &template_block)
|
146
|
-
end.render(self, opts[:locals], &block)
|
146
|
+
end.render(self, (opts[:locals]||OPTS), &block)
|
147
147
|
end
|
148
148
|
|
149
|
-
# Return the render options for the instance's class.
|
149
|
+
# Return the render options for the instance's class. While this
|
150
|
+
# is not currently frozen, it may be frozen in a future version,
|
151
|
+
# so you should not attempt to modify it.
|
150
152
|
def render_opts
|
151
153
|
self.class.render_opts
|
152
154
|
end
|
@@ -154,7 +156,7 @@ class Roda
|
|
154
156
|
# Render the given template. If there is a default layout
|
155
157
|
# for the class, take the result of the template rendering
|
156
158
|
# and render it inside the layout. See Render for details.
|
157
|
-
def view(template, opts=
|
159
|
+
def view(template, opts=OPTS)
|
158
160
|
if template.is_a?(Hash)
|
159
161
|
if opts.empty?
|
160
162
|
opts = template
|
@@ -170,7 +172,7 @@ class Roda
|
|
170
172
|
layout_opts = render_opts[:layout_opts].merge(layout_opts)
|
171
173
|
end
|
172
174
|
|
173
|
-
content = render(layout, layout_opts||
|
175
|
+
content = render(layout, layout_opts||OPTS){content}
|
174
176
|
end
|
175
177
|
|
176
178
|
content
|