roda 3.36.0 → 3.41.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abd198c51a38d272c8601fb367c12b9ad244f8383bcfb2f0583e7c7e23744eca
4
- data.tar.gz: 75cee1c957032abd4144f01fbd720794d2f66e7422514fa6e58b8bd5f8087b6e
3
+ metadata.gz: f09e850b5f4ee0406c5686317145571fa0bc5e8158b0b39c5161bb9a3cbb3878
4
+ data.tar.gz: d839bbfa3ff4e7ef4a37501a46ce65c12425dc620ccbdf9aae174a059898aa84
5
5
  SHA512:
6
- metadata.gz: e3b7fed2ed29b869b205114d3bf644bb81f1f8d3a267682976aa4eef019e9ca015ade6526d75a99d5f6f1128991f70ba692e1c527acdc62874fbd39e49c24dba
7
- data.tar.gz: 341da7749e2432b1a3139f29b09f6a219480bb6984a74e121fc2b30bc4f7c4f7c6e51b1be01fb836ef85399cef1cfd15249495d335c80b44eed75a1a657960c7
6
+ metadata.gz: dee1ec11e6ca9ca18f74fedf260f10e25e9c49efa3297ca2df02aab02efa6282464dd88f0f1f79e3529c8c56239749c72779d8bfb7fbf8508b40047470e4f6f6
7
+ data.tar.gz: c7b4d4e1d4cdf7f60707621a57cfdd6a622ef91f6d6724abd1b344fc18b59ab4271ee7536513417023c20c3f470cac73c94ff50675661162d6ec1e62df02cd70
data/CHANGELOG CHANGED
@@ -1,3 +1,35 @@
1
+ = 3.41.0 (2021-02-17)
2
+
3
+ * Improve view performance with :content option up to 3x by calling compiled template methods directly (jeremyevans)
4
+
5
+ = 3.40.0 (2021-01-14)
6
+
7
+ * Add freeze_template_caches! to the precompile_templates plugin, which ensures all templates are precompiled, and speeds up template access (jeremyevans)
8
+
9
+ * Add precompile_views to the precompile_templates plugin, which precompiles the optimized render methods (jeremyevans)
10
+
11
+ * Have RodaCache#freeze return the frozen internal hash (which no longer needs a mutex for thread-safety) (jeremyevans)
12
+
13
+ * Speed up the view method in the render plugin even more when freezing the application (jeremyevans)
14
+
15
+ * Speed up the view method in the render plugin when called with a single argument (jeremyevans)
16
+
17
+ = 3.39.0 (2020-12-15)
18
+
19
+ * Speed up relative_path plugin if relative_path or relative_prefix is called more than once (jeremyevans)
20
+
21
+ * Avoid method redefinition warnings in verbose warning mode (jeremyevans)
22
+
23
+ * Make typecast_params.convert! handle explicit nil values the same as missing values (jeremyevans)
24
+
25
+ = 3.38.0 (2020-11-16)
26
+
27
+ * Make error_email and error_mail plugins rescue invalid parameter errors when preparing the email body (jeremyevans)
28
+
29
+ = 3.37.0 (2020-10-16)
30
+
31
+ * Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
32
+
1
33
  = 3.36.0 (2020-09-14)
2
34
 
3
35
  * Add multi_public plugin, for serving files from multiple public directories (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2020 Jeremy Evans
1
+ Copyright (c) 2014-2021 Jeremy Evans
2
2
  Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
3
3
  Copyright (c) 2008-2009 Christian Neukirchen
4
4
 
@@ -248,7 +248,7 @@
248
248
  Note that if there are multiple conversion errors raised inside a
249
249
  convert! or convert_each! block, they are recorded and a single
250
250
  Roda::RodaPlugins::TypecastParams::Error instance is raised after
251
- processing the block. TypecastParams::Error#params_names can be
251
+ processing the block. TypecastParams::Error#param_names can be
252
252
  called on the exception to get an array of all parameter names
253
253
  with conversion issues, and TypecastParams::Error#all_errors
254
254
  can be used to get an array of all Error instances.
@@ -0,0 +1,42 @@
1
+ = New Features
2
+
3
+ * A custom_matchers plugin has been added, which allows using
4
+ arbitrary objects as matchers, as long as the matcher has been
5
+ registered. You can register matchers using the custom_matcher
6
+ class method, which takes the class of the matcher, and a block
7
+ which is yielded the matcher object. The block should return
8
+ nil or false if the matcher doesn't match, and any other value
9
+ if the matcher does match. Example:
10
+
11
+ plugin :custom_matchers
12
+ method_segment = Struct.new(:request_method, :next_segment)
13
+ custom_matcher(method_segment) do |matcher|
14
+ # self is the request instance ("r" yielded in the route block below)
15
+ if matcher.request_method == self.request_method
16
+ match(matcher.next_segment)
17
+ end
18
+ end
19
+
20
+ get_foo = method_segment.new('GET', 'foo')
21
+ post_any = method_segment.new('POST', String)
22
+ route do |r|
23
+ r.on('baz') do
24
+ r.on(get_foo) do
25
+ # GET method, /baz/foo prefix
26
+ end
27
+
28
+ r.is(post_any) do |seg|
29
+ # for POST /baz/bar, seg is "bar"
30
+ end
31
+ end
32
+
33
+ r.on('quux') do
34
+ r.is(get_foo) do
35
+ # GET method, /quux/foo route
36
+ end
37
+
38
+ r.on(post_any) do |seg|
39
+ # for POST /quux/xyz, seg is "xyz"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ = Improvements
2
+
3
+ * The error_email and error_mail plugins now rescue invalid parameter
4
+ errors when preparing the email body, because you generally don't
5
+ want your error handler to raise an exception.
@@ -0,0 +1,16 @@
1
+ = Improvements
2
+
3
+ * The relative_path plugin is now faster if you are calling
4
+ relative_path or relative_prefix more than once when handling a
5
+ request.
6
+
7
+ * The typecast_params.convert! method in the typecast_params plugin
8
+ now handles explicit nil values the same as missing values.
9
+ Explicit nil values do not generally occur in normal Rack parameter
10
+ parsing, but they can occur when using the json_parser plugin to
11
+ parse JSON requests.
12
+
13
+ * Roda now avoids method redefinition warnings in verbose mode by
14
+ using a self alias. As Ruby 3 is dropping uninitialized instance
15
+ variable warnings, Roda will be verbose warning free if you are
16
+ using Ruby 3.
@@ -0,0 +1,24 @@
1
+ = New Features
2
+
3
+ * A precompile_views method has been added to the
4
+ precompile_templates plugin. This method works with Roda's
5
+ optimized compiled view methods, allowing additional memory
6
+ sharing between parent and child processes.
7
+
8
+ * A freeze_template_caches! method has been added to the
9
+ precompile_templates plugin. This freezes the template caches,
10
+ preventing the compilation of additional templates, useful for
11
+ enforcing that only precompiled templates are used. Additionally,
12
+ this speeds up access to the template caches.
13
+
14
+ * RodaCache#freeze now returns the frozen internal hash, which can
15
+ then be accessed without a mutex. Previously, freeze only froze
16
+ the receiver and not the internal hash, so it didn't have the
17
+ expected effect.
18
+
19
+ = Other Improvements
20
+
21
+ * The view method in the render plugin is now faster in most cases
22
+ when a single argument is used. When freezing the application,
23
+ an additional optimization is performed to increase the
24
+ performance of the view method even further.
@@ -0,0 +1,9 @@
1
+ = Improvements
2
+
3
+ * The performance of the render plugin's view method when passed the
4
+ :content option and no other options or arguments has been improved
5
+ by about 3x, by calling compiled template methods directly.
6
+
7
+ * The compiled template method for the layout is cleared when the
8
+ render plugin is loaded again, which can fix issues when it is
9
+ loaded with different options that affect the layout.
data/lib/roda.rb CHANGED
@@ -114,6 +114,7 @@ class Roda
114
114
  alias_method meth, temp_method
115
115
  undef_method temp_method
116
116
  private meth
117
+ alias_method meth, meth
117
118
  meth = :"#{meth}_arity"
118
119
  elsif required_args > 1
119
120
  b = block
@@ -144,6 +145,7 @@ class Roda
144
145
 
145
146
  define_method(meth, &block)
146
147
  private meth
148
+ alias_method meth, meth
147
149
 
148
150
  if arity_meth
149
151
  required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
@@ -167,6 +169,7 @@ class Roda
167
169
  send(meth, *a)
168
170
  end
169
171
  private arity_meth
172
+ alias_method arity_meth, arity_meth
170
173
  end
171
174
 
172
175
  call_meth
@@ -199,6 +202,7 @@ class Roda
199
202
 
200
203
  private
201
204
 
205
+ alias set_default_headers set_default_headers
202
206
  def set_default_headers
203
207
  @headers['Content-Type'] ||= 'text/html'
204
208
  end
@@ -403,6 +407,7 @@ class Roda
403
407
  class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
404
408
  end
405
409
  private :_roda_before
410
+ alias_method :_roda_before, :_roda_before
406
411
  end
407
412
  end
408
413
 
@@ -419,6 +424,7 @@ class Roda
419
424
  class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
420
425
  end
421
426
  private :_roda_after
427
+ alias_method :_roda_after, :_roda_after
422
428
  end
423
429
  end
424
430
 
data/lib/roda/cache.rb CHANGED
@@ -22,6 +22,13 @@ class Roda
22
22
  @mutex.synchronize{@hash[key] = value}
23
23
  end
24
24
 
25
+ # Return the frozen internal hash. The internal hash can then
26
+ # be accessed directly since it is frozen and there are no
27
+ # thread safety issues.
28
+ def freeze
29
+ @hash.freeze
30
+ end
31
+
25
32
  private
26
33
 
27
34
  # Create a copy of the cache with a separate mutex.
@@ -0,0 +1,87 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The custom_matchers plugin supports using arbitrary objects
7
+ # as matchers, as long as the application has been configured
8
+ # to accept such objects.
9
+ #
10
+ # After loading the plugin, support for custom matchers can be
11
+ # configured using the +custom_matcher+ class method. This
12
+ # method is generally passed the class of the object you want
13
+ # to use as a custom matcher, as well as a block. The block
14
+ # will be called in the context of the request instance
15
+ # with the specific matcher used in the match method.
16
+ #
17
+ # Blocks can append to the captures in order to yield the appropriate
18
+ # values to match blocks, or call request methods that append to the
19
+ # captures.
20
+ #
21
+ # Example:
22
+ #
23
+ # plugin :custom_matchers
24
+ # method_segment = Struct.new(:request_method, :next_segment)
25
+ # custom_matcher(method_segment) do |matcher|
26
+ # # self is the request instance ("r" yielded in the route block below)
27
+ # if matcher.request_method == self.request_method
28
+ # match(matcher.next_segment)
29
+ # end
30
+ # end
31
+ #
32
+ # get_foo = method_segment.new('GET', 'foo')
33
+ # post_any = method_segment.new('POST', String)
34
+ # route do |r|
35
+ # r.on('baz') do
36
+ # r.on(get_foo) do
37
+ # # GET method, /baz/foo prefix
38
+ # end
39
+ #
40
+ # r.is(post_any) do |seg|
41
+ # # for POST /baz/bar, seg is "bar"
42
+ # end
43
+ # end
44
+ #
45
+ # r.on('quux') do
46
+ # r.is(get_foo) do
47
+ # # GET method, /quux/foo route
48
+ # end
49
+ #
50
+ # r.on(post_any) do |seg|
51
+ # # for POST /quux/xyz, seg is "xyz"
52
+ # end
53
+ # end
54
+ # end
55
+ module CustomMatchers
56
+ def self.configure(app)
57
+ app.opts[:custom_matchers] ||= OPTS
58
+ end
59
+
60
+ module ClassMethods
61
+ def custom_matcher(match_class, &block)
62
+ custom_matchers = Hash[opts[:custom_matchers]]
63
+ meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
64
+ opts[:custom_matchers] = custom_matchers.freeze
65
+ self::RodaRequest.send(:define_method, meth, &block)
66
+ nil
67
+ end
68
+ end
69
+
70
+ module RequestMethods
71
+ # Try custom matchers before calling super
72
+ def unsupported_matcher(matcher)
73
+ roda_class.opts[:custom_matchers].each do |match_class, meth|
74
+ if match_class === matcher
75
+ return send(meth, matcher)
76
+ end
77
+ end
78
+
79
+ super
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:custom_matchers, CustomMatchers)
85
+ end
86
+ end
87
+
@@ -33,6 +33,7 @@ class Roda
33
33
  response_class.class_eval(<<-END, __FILE__, __LINE__+1)
34
34
  private
35
35
 
36
+ alias set_default_headers set_default_headers
36
37
  def set_default_headers
37
38
  h = @headers
38
39
  #{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
@@ -54,6 +54,13 @@ class Roda
54
54
  :body=>lambda do |s, e|
55
55
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
56
56
 
57
+ begin
58
+ params = s.request.params
59
+ params = (format[params] unless params.empty?)
60
+ rescue
61
+ params = 'Invalid Parameters!'
62
+ end
63
+
57
64
  message = String.new
58
65
  message << <<END
59
66
  Path: #{s.request.path}
@@ -73,12 +80,12 @@ ENV:
73
80
  #{format[s.env]}
74
81
  END
75
82
 
76
- unless s.request.params.empty?
83
+ if params
77
84
  message << <<END
78
85
 
79
86
  Params:
80
87
 
81
- #{format[s.request.params]}
88
+ #{params}
82
89
  END
83
90
  end
84
91
 
@@ -71,6 +71,13 @@ class Roda
71
71
 
72
72
  format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
73
73
 
74
+ begin
75
+ params = request.params
76
+ params = (format[params] unless params.empty?)
77
+ rescue
78
+ params = 'Invalid Parameters!'
79
+ end
80
+
74
81
  message = String.new
75
82
  message << <<END
76
83
  Path: #{request.path}
@@ -91,12 +98,12 @@ ENV:
91
98
  #{format[env]}
92
99
  END
93
100
 
94
- unless request.params.empty?
101
+ if params
95
102
  message << <<END
96
103
 
97
104
  Params:
98
105
 
99
- #{format[request.params]}
106
+ #{params}
100
107
  END
101
108
  end
102
109
 
@@ -476,6 +476,8 @@ class Roda
476
476
 
477
477
  undef_method :match_rcpt
478
478
  undef_method :match_text
479
+ undef_method :match_body
480
+ undef_method :match_subject
479
481
 
480
482
  # Same as +header+, but also mark the message as being handled.
481
483
  def handle_header(key, value=nil)
@@ -13,32 +13,33 @@ class Roda
13
13
  # all of the child processes can use the same precompiled templates, which
14
14
  # saves memory.
15
15
  #
16
- # After loading the plugin, you can call +precompile_templates+ with
17
- # the pattern of templates you would like to precompile:
16
+ # Another advantage of the precompile_templates plugin is that after
17
+ # template precompilation, access to the template file in the file system is
18
+ # no longer needed, so this can be used with security features that do not
19
+ # allow access to the template files at runtime.
20
+ #
21
+ # After loading the plugin, you should call precompile_views with an array
22
+ # of views to compile, using the same argument you are passing to view or
23
+ # render:
18
24
  #
19
25
  # plugin :precompile_templates
20
- # precompile_templates "views/\*\*/*.erb"
26
+ # precompile_views %w'view1 view2'
27
+ #
28
+ # If the view requires local variables, you should call precompile_views with a second
29
+ # argument for the local variables:
21
30
  #
22
- # That will precompile all erb template files in the views directory or
23
- # any subdirectory.
31
+ # plugin :precompile_templates
32
+ # precompile_views :view3, [:local_var1, :local_var2]
24
33
  #
25
- # If the templates use local variables, you need to specify which local
26
- # variables to precompile, which should be an array of symbols:
34
+ # After all templates are precompiled, you can optionally use freeze_template_caches!,
35
+ # which will freeze the template caches so that any template compilation at runtime
36
+ # will result in an error. This also speeds up template cache access, since the
37
+ # template caches no longer need a mutex.
27
38
  #
28
- # precompile_templates 'views/users/_*.erb', locals: [:user]
39
+ # freeze_template_caches!
29
40
  #
30
41
  # Note that you should use Tilt 2.0.1+ if you are using this plugin, so
31
42
  # that locals are handled in the same order.
32
- #
33
- # You can specify other render options when calling +precompile_templates+,
34
- # including +:cache_key+, +:template_class+, and +:template_opts+. If you
35
- # are passing any of those options to render/view for the template, you
36
- # should pass the same options when precompiling the template.
37
- #
38
- # To compile inline templates, just pass a single hash containing an :inline
39
- # to +precompile_templates+:
40
- #
41
- # precompile_templates inline: some_template_string
42
43
  module PrecompileTemplates
43
44
  # Load the render plugin as precompile_templates depends on it.
44
45
  def self.load_dependencies(app, opts=OPTS)
@@ -46,8 +47,49 @@ class Roda
46
47
  end
47
48
 
48
49
  module ClassMethods
49
- # Precompile the templates using the given options. See PrecompileTemplates
50
- # for details.
50
+ # Freeze the template caches. Should be called after precompiling all templates during
51
+ # application startup, if you don't want to allow templates to be cached at runtime.
52
+ # In addition to ensuring that no templates are compiled at runtime, this also speeds
53
+ # up rendering by freezing the template caches, so that a mutex is not needed to access
54
+ # them.
55
+ def freeze_template_caches!
56
+ _freeze_layout_method
57
+
58
+ opts[:render] = render_opts.merge(
59
+ :cache=>render_opts[:cache].freeze,
60
+ :template_method_cache=>render_opts[:template_method_cache].freeze,
61
+ ).freeze
62
+ self::RodaCompiledTemplates.freeze
63
+
64
+ nil
65
+ end
66
+
67
+ # Precompile the templates using the given options. Note that this doesn't
68
+ # handle optimized template methods supported in newer versions of Roda, but
69
+ # there are still cases where makes sense to use it.
70
+ #
71
+ # You can call +precompile_templates+ with the pattern of templates you would
72
+ # like to precompile:
73
+ #
74
+ # precompile_templates "views/**/*.erb"
75
+ #
76
+ # That will precompile all erb template files in the views directory or
77
+ # any subdirectory.
78
+ #
79
+ # If the templates use local variables, you need to specify which local
80
+ # variables to precompile, which should be an array of symbols:
81
+ #
82
+ # precompile_templates 'views/users/_*.erb', locals: [:user]
83
+ #
84
+ # You can specify other render options when calling +precompile_templates+,
85
+ # including +:cache_key+, +:template_class+, and +:template_opts+. If you
86
+ # are passing any of those options to render/view for the template, you
87
+ # should pass the same options when precompiling the template.
88
+ #
89
+ # To compile inline templates, just pass a single hash containing an :inline
90
+ # to +precompile_templates+:
91
+ #
92
+ # precompile_templates inline: some_template_string
51
93
  def precompile_templates(pattern, opts=OPTS)
52
94
  if pattern.is_a?(Hash)
53
95
  opts = pattern.merge(opts)
@@ -68,7 +110,40 @@ class Roda
68
110
  instance = allocate
69
111
  compile_opts.each do |compile_opt|
70
112
  template = instance.send(:retrieve_template, compile_opt)
71
- Render.tilt_template_compiled_method(template, locals, self)
113
+ begin
114
+ Render.tilt_template_compiled_method(template, locals, self)
115
+ rescue NotImplementedError
116
+ # When freezing template caches, you may want to precompile a template for a
117
+ # template type that doesn't support template precompilation, just to populate
118
+ # the cache. Tilt rescues NotImplementedError in this case, which we can ignore.
119
+ nil
120
+ end
121
+ end
122
+
123
+ nil
124
+ end
125
+
126
+ # Precompile the given views with the given locals, handling optimized template methods.
127
+ def precompile_views(views, locals=EMPTY_ARRAY)
128
+ instance = allocate
129
+ views = Array(views)
130
+
131
+ if locals.empty?
132
+ opts = OPTS
133
+ else
134
+ locals_hash = {}
135
+ locals.each{|k| locals_hash[k] = nil}
136
+ opts = {:locals=>locals_hash}
137
+ end
138
+
139
+ views.each do |view|
140
+ instance.send(:retrieve_template, instance.send(:render_template_opts, view, opts))
141
+ end
142
+
143
+ if locals_hash
144
+ views.each do |view|
145
+ instance.send(:_optimized_render_method_for_locals, view, locals_hash)
146
+ end
72
147
  end
73
148
 
74
149
  nil
@@ -41,6 +41,7 @@ class Roda
41
41
  # Return a relative prefix to append to an absolute path to a relative path
42
42
  # based on the current path of the request.
43
43
  def relative_prefix
44
+ return @_relative_prefix if @_relative_prefix
44
45
  env = @_request.env
45
46
  script_name = env["SCRIPT_NAME"]
46
47
  path_info = env["PATH_INFO"]
@@ -50,16 +51,16 @@ class Roda
50
51
  case script_name.getbyte(0)
51
52
  when nil # SCRIPT_NAME empty
52
53
  unless path_info.getbyte(0) == 47 # PATH_INFO starts with /
53
- return ''
54
+ return(@_relative_prefix = '')
54
55
  end
55
56
  when 47 # SCRIPT_NAME starts with /
56
57
  # nothing
57
58
  else
58
- return ''
59
+ return(@_relative_prefix = '')
59
60
  end
60
61
 
61
62
  slash_count = script_name.count('/') + path_info.count('/')
62
- if slash_count > 1
63
+ @_relative_prefix = if slash_count > 1
63
64
  ("../" * (slash_count - 2)) << ".."
64
65
  else
65
66
  "."
@@ -106,7 +106,7 @@ class Roda
106
106
  # :template_block :: Pass this block when creating the underlying template,
107
107
  # ignored when using :inline. Disables caching of the
108
108
  # template by default.
109
- # :template_class :: Provides the template class to use, inside of using
109
+ # :template_class :: Provides the template class to use, instead of using
110
110
  # Tilt or <tt>Tilt[:engine]</tt>.
111
111
  #
112
112
  # Here's an example of using these options:
@@ -183,6 +183,7 @@ class Roda
183
183
  app.const_set(:RodaCompiledTemplates, compiled_templates_module)
184
184
  end
185
185
  opts[:template_method_cache] = orig_method_cache || (opts[:cache_class] || RodaCache).new
186
+ opts[:template_method_cache][:_roda_layout] = nil if opts[:template_method_cache][:_roda_layout]
186
187
  opts[:cache] = orig_cache || (opts[:cache_class] || RodaCache).new
187
188
 
188
189
  opts[:layout_opts] = (opts[:layout_opts] || {}).dup
@@ -333,6 +334,25 @@ class Roda
333
334
  end
334
335
 
335
336
  module ClassMethods
337
+ # :nocov:
338
+ if COMPILED_METHOD_SUPPORT
339
+ # :nocov:
340
+ # If using compiled methods and there is an optimized layout, speed up
341
+ # access to the layout method to improve the performance of view.
342
+ def freeze
343
+ begin
344
+ _freeze_layout_method
345
+ rescue
346
+ # This is only for optimization, if any errors occur, they can be ignored.
347
+ # One possibility for error is the app doesn't use a layout, but doesn't
348
+ # specifically set the :layout=>false plugin option.
349
+ nil
350
+ end
351
+
352
+ super
353
+ end
354
+ end
355
+
336
356
  # Copy the rendering options into the subclass, duping
337
357
  # them as necessary to prevent changes in the subclass
338
358
  # affecting the parent class.
@@ -352,6 +372,27 @@ class Roda
352
372
  def render_opts
353
373
  opts[:render]
354
374
  end
375
+
376
+ private
377
+
378
+ # Precompile the layout method, to reduce method calls to look it up at runtime.
379
+ def _freeze_layout_method
380
+ if render_opts[:layout]
381
+ instance = allocate
382
+ instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
383
+
384
+ # :nocov:
385
+ if COMPILED_METHOD_SUPPORT
386
+ # :nocov:
387
+ if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
388
+ instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
389
+ layout_method = opts[:render][:template_method_cache][:_roda_layout]
390
+ define_method(:_layout_method){layout_method}
391
+ opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
392
+ end
393
+ end
394
+ end
395
+ end
355
396
  end
356
397
 
357
398
  module InstanceMethods
@@ -375,19 +416,21 @@ class Roda
375
416
  # Render the given template. If there is a default layout
376
417
  # for the class, take the result of the template rendering
377
418
  # and render it inside the layout. See Render for details.
378
- def view(template, opts = (optimized_template = _cached_template_method(template); OPTS))
379
- if optimized_template
380
- content = send(optimized_template, OPTS)
381
-
382
- render_opts = self.class.opts[:render]
383
- if layout_template = render_opts[:optimize_layout]
384
- method_cache = render_opts[:template_method_cache]
385
- unless layout_method = method_cache[:_roda_layout]
386
- retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
387
- layout_method = method_cache[:_roda_layout]
388
- end
419
+ def view(template, opts = (content = _optimized_view_content(template); OPTS))
420
+ if content
421
+ # First, check if the optimized layout method has already been created,
422
+ # and use it if so. This way avoids the extra conditional and local variable
423
+ # assignments in the next section.
424
+ if layout_method = _layout_method
425
+ return send(layout_method, OPTS){content}
426
+ end
389
427
 
390
- if layout_method
428
+ # If we have an optimized template method but no optimized layout method, create the
429
+ # optimized layout method if possible and use it. If you can't create the optimized
430
+ # layout method, fall through to the slower approach.
431
+ if layout_template = self.class.opts[:render][:optimize_layout]
432
+ retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
433
+ if layout_method = _layout_method
391
434
  return send(layout_method, OPTS){content}
392
435
  end
393
436
  end
@@ -428,6 +471,11 @@ class Roda
428
471
  method_cache[template]
429
472
  end
430
473
 
474
+ # Return a symbol containing the optimized layout method
475
+ def _layout_method
476
+ self.class.opts[:render][:template_method_cache][:_roda_layout]
477
+ end
478
+
431
479
  # Use an optimized render path for templates with a hash of locals. Returns the result
432
480
  # of the template render if the optimized path is used, or nil if the optimized
433
481
  # path is not used and the long method needs to be used.
@@ -469,19 +517,37 @@ class Roda
469
517
  end
470
518
  end
471
519
  end
520
+
521
+ # Get the content for #view, or return nil to use the unoptimized approach. Only called if
522
+ # a single argument is passed to view.
523
+ def _optimized_view_content(template)
524
+ if optimized_template = _cached_template_method(template)
525
+ send(optimized_template, OPTS)
526
+ elsif template.is_a?(Hash) && template.length == 1
527
+ template[:content]
528
+ end
529
+ end
472
530
  else
473
531
  # :nocov:
474
- def _cached_template_method(template)
532
+ def _cached_template_method(_)
475
533
  nil
476
534
  end
477
535
 
478
- def _cached_template_method_key(template)
536
+ def _cached_template_method_key(_)
537
+ nil
538
+ end
539
+
540
+ def _layout_method
479
541
  nil
480
542
  end
481
543
 
482
544
  def _optimized_render_method_for_locals(_, _)
483
545
  nil
484
546
  end
547
+
548
+ def _optimized_view_content(template)
549
+ nil
550
+ end
485
551
  # :nocov:
486
552
  end
487
553
 
@@ -51,6 +51,10 @@ class Roda
51
51
  def _cached_template_method(template)
52
52
  nil
53
53
  end
54
+
55
+ def _optimized_view_content(template)
56
+ nil
57
+ end
54
58
  end
55
59
 
56
60
  def render_locals
@@ -141,6 +141,7 @@ class Roda
141
141
  app::RodaRequest.send(:define_method, type) do |&block|
142
142
  on_type(type, &block)
143
143
  end
144
+ app::RodaRequest.send(:alias_method, type, type)
144
145
  end
145
146
 
146
147
  app.opts[:type_routing] = config.freeze
@@ -229,7 +229,7 @@ class Roda
229
229
  #
230
230
  # Note that if there are multiple conversion Error raised inside a +convert!+ or +convert_each!+
231
231
  # block, they are recorded and a single TypecastParams::Error instance is raised after
232
- # processing the block. TypecastParams::Error#params_names can be called on the exception to
232
+ # processing the block. TypecastParams::Error#param_names can be called on the exception to
233
233
  # get an array of all parameter names with conversion issues, and TypecastParams::Error#all_errors
234
234
  # can be used to get an array of all Error instances.
235
235
  #
@@ -609,10 +609,9 @@ class Roda
609
609
  when nil
610
610
  keys = (0...@obj.length)
611
611
 
612
- valid = case @obj
613
- when Array
612
+ valid = if @obj.is_a?(Array)
614
613
  true
615
- when Hash
614
+ else
616
615
  keys = keys.map(&:to_s)
617
616
  keys.all?{|k| @obj.has_key?(k)}
618
617
  end
@@ -725,7 +724,7 @@ class Roda
725
724
  raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
726
725
  return
727
726
  end
728
- present = @obj.has_key?(key)
727
+ present = !@obj[key].nil?
729
728
  when Integer
730
729
  unless @obj.is_a?(Array)
731
730
  raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 36
7
+ RodaMinorVersion = 41
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.36.0
4
+ version: 3.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-14 00:00:00.000000000 Z
11
+ date: 2021-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -174,16 +174,8 @@ extra_rdoc_files:
174
174
  - MIT-LICENSE
175
175
  - CHANGELOG
176
176
  - doc/conventions.rdoc
177
- - doc/release_notes/3.7.0.txt
178
177
  - doc/release_notes/3.0.0.txt
179
178
  - doc/release_notes/3.1.0.txt
180
- - doc/release_notes/3.2.0.txt
181
- - doc/release_notes/3.3.0.txt
182
- - doc/release_notes/3.4.0.txt
183
- - doc/release_notes/3.5.0.txt
184
- - doc/release_notes/3.6.0.txt
185
- - doc/release_notes/3.8.0.txt
186
- - doc/release_notes/3.9.0.txt
187
179
  - doc/release_notes/3.10.0.txt
188
180
  - doc/release_notes/3.11.0.txt
189
181
  - doc/release_notes/3.12.0.txt
@@ -195,6 +187,7 @@ extra_rdoc_files:
195
187
  - doc/release_notes/3.17.0.txt
196
188
  - doc/release_notes/3.18.0.txt
197
189
  - doc/release_notes/3.19.0.txt
190
+ - doc/release_notes/3.2.0.txt
198
191
  - doc/release_notes/3.20.0.txt
199
192
  - doc/release_notes/3.21.0.txt
200
193
  - doc/release_notes/3.22.0.txt
@@ -205,6 +198,7 @@ extra_rdoc_files:
205
198
  - doc/release_notes/3.27.0.txt
206
199
  - doc/release_notes/3.28.0.txt
207
200
  - doc/release_notes/3.29.0.txt
201
+ - doc/release_notes/3.3.0.txt
208
202
  - doc/release_notes/3.30.0.txt
209
203
  - doc/release_notes/3.31.0.txt
210
204
  - doc/release_notes/3.32.0.txt
@@ -212,6 +206,17 @@ extra_rdoc_files:
212
206
  - doc/release_notes/3.34.0.txt
213
207
  - doc/release_notes/3.35.0.txt
214
208
  - doc/release_notes/3.36.0.txt
209
+ - doc/release_notes/3.37.0.txt
210
+ - doc/release_notes/3.38.0.txt
211
+ - doc/release_notes/3.39.0.txt
212
+ - doc/release_notes/3.4.0.txt
213
+ - doc/release_notes/3.40.0.txt
214
+ - doc/release_notes/3.41.0.txt
215
+ - doc/release_notes/3.5.0.txt
216
+ - doc/release_notes/3.6.0.txt
217
+ - doc/release_notes/3.7.0.txt
218
+ - doc/release_notes/3.8.0.txt
219
+ - doc/release_notes/3.9.0.txt
215
220
  files:
216
221
  - CHANGELOG
217
222
  - MIT-LICENSE
@@ -249,7 +254,12 @@ files:
249
254
  - doc/release_notes/3.34.0.txt
250
255
  - doc/release_notes/3.35.0.txt
251
256
  - doc/release_notes/3.36.0.txt
257
+ - doc/release_notes/3.37.0.txt
258
+ - doc/release_notes/3.38.0.txt
259
+ - doc/release_notes/3.39.0.txt
252
260
  - doc/release_notes/3.4.0.txt
261
+ - doc/release_notes/3.40.0.txt
262
+ - doc/release_notes/3.41.0.txt
253
263
  - doc/release_notes/3.5.0.txt
254
264
  - doc/release_notes/3.6.0.txt
255
265
  - doc/release_notes/3.7.0.txt
@@ -275,6 +285,7 @@ files:
275
285
  - lib/roda/plugins/content_security_policy.rb
276
286
  - lib/roda/plugins/cookies.rb
277
287
  - lib/roda/plugins/csrf.rb
288
+ - lib/roda/plugins/custom_matchers.rb
278
289
  - lib/roda/plugins/default_headers.rb
279
290
  - lib/roda/plugins/default_status.rb
280
291
  - lib/roda/plugins/delay_build.rb
@@ -387,7 +398,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
387
398
  - !ruby/object:Gem::Version
388
399
  version: '0'
389
400
  requirements: []
390
- rubygems_version: 3.1.2
401
+ rubygems_version: 3.2.3
391
402
  signing_key:
392
403
  specification_version: 4
393
404
  summary: Routing tree web toolkit