roda 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|