roda 3.65.0 → 3.68.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1c9c4301b4c90cb2f2c7a2d190986b4b6c68c4e1eb4b0be59bdba2aebdf6780
4
- data.tar.gz: 576b15ba70cdab634638bce683ba8ee8ac93feb9fe5f3ae8982cf6ae07c6d686
3
+ metadata.gz: faad040bd1e251d705afbfe6ced40794aa2f135658168c85603a6c8a83a497be
4
+ data.tar.gz: 6e1d78ffdf0442835a754e54fc6216d8f668f3d0cb8e9c5635c1a582b17d5746
5
5
  SHA512:
6
- metadata.gz: 91b24c2bbcb2d2c27c14f242d620fab9d5e838f68bc102887018e41e4e05f5eb93718ab312c727a32ac4ab6df0dbcf4d0bb134499674dee71e991e953ecac85b
7
- data.tar.gz: 9fe4592c67425c171926368d1121841dcafae686eb6822439536c24040788049041f34dffae22fde1ba2905f63c0e3abd13a5938ea263f4b143fdb94219e66f1
6
+ metadata.gz: a663960b5f5c5391a44102b1b255fbb5546f241c2386b6fdbe8a5050f434407332f239eb0c862ab877de239ec1b4ea39c4f9202c488672a846c5f8eabaef6379
7
+ data.tar.gz: 508fa08ad66e1b11b5abc552345067fc2517aeb10ea060c6298a337b44db453c5daf35a0bbafe399dd9167286fd9ea35c07d75c340f640a6198beb52367352d5
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ = 3.68.0 (2023-05-11)
2
+
3
+ * Make Roda.run in multi_run plugin accept blocks to allow autoloading the apps to dispatch to (jeremyevans)
4
+
5
+ = 3.67.0 (2023-04-12)
6
+
7
+ * Add custom_block_results plugin for registering custom block result handlers (jeremyevans)
8
+
9
+ = 3.66.0 (2023-03-13)
10
+
11
+ * Support overriding exception page assets via exception_page_{css,js} instance methods (jeremyevans) (#306)
12
+
13
+ * Avoid keeping reference to Roda instance that caches an inline template (jeremyevans)
14
+
15
+ * Add render_coverage plugin, using tilt 2.1 features to allow for compiled templates in Ruby <3.2 (jeremyevans)
16
+
1
17
  = 3.65.0 (2023-02-13)
2
18
 
3
19
  * Make indifferent_params plugin work with changes in rack main branch (jeremyevans)
@@ -0,0 +1,23 @@
1
+ = New Features
2
+
3
+ * A render_coverage plugin has been added, which will cause compiled
4
+ template code to be saved to a folder and loaded using load instead
5
+ of eval. This allows for coverage to work for the compiled template
6
+ code in Ruby versions before 3.2. It can also allow for verbose
7
+ syntax warnings in compiled template code (ignored by eval), and
8
+ can also be useful for static analysis of compiled template code.
9
+ This plugin requires tilt 2.1+.
10
+
11
+ * The exception_page plugin now supports exception_page_{css,js}
12
+ instance methods for overriding the CSS and JavaScript on the
13
+ generated exception page.
14
+
15
+ = Other Improvements
16
+
17
+ * Using inline templates (render/view :inline option) no longer keeps
18
+ a reference to the Roda instance that caches the template.
19
+
20
+ = Backwards Compatibility
21
+
22
+ * The Render::TemplateMtimeWrapper API has changed. Any external
23
+ use of this class needs to be updated.
@@ -0,0 +1,25 @@
1
+ = New Feature
2
+
3
+ * A custom_block_results plugin has been added for custom handling
4
+ of block results. This allows routing blocks to return
5
+ arbitrary objects instead of just String, nil, and false, and
6
+ to have custom handling for them. For example, if you want to
7
+ be able to have your routing blocks return the status code to use,
8
+ you could do:
9
+
10
+ plugin :custom_block_results
11
+
12
+ handle_block_result Integer do |result|
13
+ response.status_code = result
14
+ end
15
+
16
+ route do |r|
17
+ 200
18
+ end
19
+
20
+ While the expected use of the handle_block_result method is with
21
+ class arguments, you can use any argument that implements an
22
+ appropriate === method.
23
+
24
+ The symbol_views and json plugins, which support additional block
25
+ results, now use the custom_block_results plugin internally.
@@ -0,0 +1,21 @@
1
+ = New Feature
2
+
3
+ * Roda.run in the multi_run plugin now accepts blocks, to allow
4
+ autoloading of apps to dispatch to:
5
+
6
+ class App < Roda
7
+ plugin :multi_run
8
+
9
+ run("other_app"){OtherApp}
10
+
11
+ route do |r|
12
+ r.multi_run
13
+ end
14
+ end
15
+
16
+ With the above example, the block is not evaluated until a
17
+ request for the /other_app branch is received. If OtherApp is
18
+ autoloaded, this can speed up application startup and partial
19
+ testing. When freezing the application (for production use),
20
+ the block is eagerly loaded, so that requests to the
21
+ /other_app branch do not call the block on every request.
@@ -0,0 +1,68 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The custom_block_results plugin allows you to specify handling
7
+ # for different block results. By default, Roda only supports
8
+ # nil, false, and string block results, but using this plugin,
9
+ # you can support other block results.
10
+ #
11
+ # For example, if you wanted to support returning Integer
12
+ # block results, and have them set the response status code,
13
+ # you could do:
14
+ #
15
+ # plugin :custom_block_results
16
+ #
17
+ # handle_block_result Integer do |result|
18
+ # response.status_code = result
19
+ # end
20
+ #
21
+ # route do |r|
22
+ # 200
23
+ # end
24
+ #
25
+ # The expected use case for this is to customize behavior by
26
+ # class, but matching uses ===, so it is possible to use non-class
27
+ # objects that respond to === appropriately.
28
+ #
29
+ # Note that custom block result handling only occurs if the types
30
+ # are not handled by Roda itself. You cannot use this to modify
31
+ # the handling of nil, false, or string results.
32
+ module CustomBlockResults
33
+ def self.configure(app)
34
+ app.opts[:custom_block_results] ||= {}
35
+ end
36
+
37
+ module ClassMethods
38
+ # Freeze the configured custom block results when freezing the app.
39
+ def freeze
40
+ opts[:custom_block_results].freeze
41
+ super
42
+ end
43
+
44
+ # Specify a block that will be called when an instance of klass
45
+ # is returned as a block result. The block defines a method.
46
+ def handle_block_result(klass, &block)
47
+ opts[:custom_block_results][klass] = define_roda_method(opts[:custom_block_results][klass] || "custom_block_result_#{klass}", 1, &block)
48
+ end
49
+ end
50
+
51
+ module RequestMethods
52
+ private
53
+
54
+ # Try each configured custom block result, and call the related method
55
+ # to get the block result.
56
+ def unsupported_block_result(result)
57
+ roda_class.opts[:custom_block_results].each do |klass, meth|
58
+ return scope.send(meth, result) if klass === result
59
+ end
60
+
61
+ super
62
+ end
63
+ end
64
+ end
65
+
66
+ register_plugin(:custom_block_results, CustomBlockResults)
67
+ end
68
+ end
@@ -227,7 +227,7 @@ END
227
227
 
228
228
  css = case css_file
229
229
  when nil
230
- "<style type=\"text/css\">#{ExceptionPage.css}</style>"
230
+ "<style type=\"text/css\">#{exception_page_css}</style>"
231
231
  when false
232
232
  # :nothing
233
233
  else
@@ -236,7 +236,7 @@ END
236
236
 
237
237
  js = case js_file
238
238
  when nil
239
- "<script type=\"text/javascript\">\n//<!--\n#{ExceptionPage.js}\n//-->\n</script>"
239
+ "<script type=\"text/javascript\">\n//<!--\n#{exception_page_js}\n//-->\n</script>"
240
240
  when false
241
241
  # :nothing
242
242
  else
@@ -399,6 +399,16 @@ END
399
399
  end
400
400
  end
401
401
 
402
+ # The CSS to use on the exception page
403
+ def exception_page_css
404
+ ExceptionPage.css
405
+ end
406
+
407
+ # The JavaScript to use on the exception page
408
+ def exception_page_js
409
+ ExceptionPage.js
410
+ end
411
+
402
412
  private
403
413
 
404
414
  if RUBY_VERSION >= '3.2'
@@ -420,11 +430,11 @@ END
420
430
  def exception_page_assets
421
431
  get 'exception_page.css' do
422
432
  response['Content-Type'] = "text/css"
423
- ExceptionPage.css
433
+ scope.exception_page_css
424
434
  end
425
435
  get 'exception_page.js' do
426
436
  response['Content-Type'] = "application/javascript"
427
- ExceptionPage.js
437
+ scope.exception_page_js
428
438
  end
429
439
  end
430
440
  end
@@ -55,11 +55,17 @@ class Roda
55
55
  module Json
56
56
  # Set the classes to automatically convert to JSON, and the serializer to use.
57
57
  def self.configure(app, opts=OPTS)
58
+ app.plugin :custom_block_results
59
+
58
60
  classes = opts[:classes] || [Array, Hash]
59
61
  app.opts[:json_result_classes] ||= []
60
62
  app.opts[:json_result_classes] += classes
61
- app.opts[:json_result_classes].uniq!
62
- app.opts[:json_result_classes].freeze
63
+ classes = app.opts[:json_result_classes]
64
+ classes.uniq!
65
+ classes.freeze
66
+ classes.each do |klass|
67
+ app.opts[:custom_block_results][klass] = :handle_json_block_result
68
+ end
63
69
 
64
70
  app.opts[:json_result_serializer] = opts[:serializer] || app.opts[:json_result_serializer] || app.opts[:json_serializer] || :to_json.to_proc
65
71
 
@@ -71,32 +77,34 @@ class Roda
71
77
  module ClassMethods
72
78
  # The classes that should be automatically converted to json
73
79
  def json_result_classes
80
+ # RODA4: remove, only used by previous implementation.
74
81
  opts[:json_result_classes]
75
82
  end
76
83
  end
77
84
 
85
+ module InstanceMethods
86
+ # Handle a result for one of the registered JSON result classes
87
+ # by converting the result to JSON.
88
+ def handle_json_block_result(result)
89
+ @_response['Content-Type'] ||= opts[:json_result_content_type]
90
+ @_request.send(:convert_to_json, result)
91
+ end
92
+ end
93
+
78
94
  module RequestMethods
79
95
  private
80
96
 
81
- # If the result is an instance of one of the json_result_classes,
82
- # convert the result to json and return it as the body, using the
83
- # application/json content-type.
84
- def block_result_body(result)
85
- case result
86
- when *roda_class.json_result_classes
87
- response['Content-Type'] ||= roda_class.opts[:json_result_content_type]
88
- convert_to_json(result)
89
- else
90
- super
91
- end
92
- end
93
-
94
97
  # Convert the given object to JSON. Uses to_json by default,
95
98
  # but can use a custom serializer passed to the plugin.
96
- def convert_to_json(obj)
97
- args = [obj]
98
- args << self if roda_class.opts[:json_result_include_request]
99
- roda_class.opts[:json_result_serializer].call(*args)
99
+ def convert_to_json(result)
100
+ opts = roda_class.opts
101
+ serializer = opts[:json_result_serializer]
102
+
103
+ if opts[:json_result_include_request]
104
+ serializer.call(result, self)
105
+ else
106
+ serializer.call(result)
107
+ end
100
108
  end
101
109
  end
102
110
  end
@@ -17,7 +17,7 @@ class Roda
17
17
  # App.run "ro", OtherRodaApp
18
18
  # App.run "si", SinatraApp
19
19
  #
20
- # Inside your route block, you can call r.multi_run to dispatch to all
20
+ # Inside your route block, you can call +r.multi_run+ to dispatch to all
21
21
  # three rack applications based on the prefix:
22
22
  #
23
23
  # App.route do |r|
@@ -28,7 +28,7 @@ class Roda
28
28
  # starting with +/ro+ to +OtherRodaApp+, and routes starting with +/si+ to
29
29
  # SinatraApp.
30
30
  #
31
- # You can pass a block to +multi_run+ that will be called with the prefix,
31
+ # You can pass a block to +r.multi_run+ that will be called with the prefix,
32
32
  # before dispatching to the rack app:
33
33
  #
34
34
  # App.route do |r|
@@ -39,12 +39,26 @@ class Roda
39
39
  #
40
40
  # This is useful for modifying the environment before passing it to the rack app.
41
41
  #
42
- # The multi_run plugin is similar to the multi_route plugin, with the difference
43
- # being the multi_route plugin keeps all routing subtrees in the same Roda app/class,
44
- # while multi_run dispatches to other rack apps. If you want to isolate your routing
45
- # subtrees, multi_run is a better approach, but it does not let you set instance
46
- # variables in the main Roda app and have those instance variables usable in
47
- # the routing subtrees.
42
+ # You can also call +Roda.run+ with a block:
43
+ #
44
+ # App.run("ra"){PlainRackApp}
45
+ # App.run("ro"){OtherRodaApp}
46
+ # App.run("si"){SinatraApp}
47
+ #
48
+ # When called with a block, Roda will call the block to get the app to dispatch to
49
+ # every time the block is called. The expected usage is with autoloaded classes,
50
+ # so that the related classes are not loaded until there is a request for the
51
+ # related route. This can sigficantly speedup startup or testing a subset of the
52
+ # application. When freezing an application, the blocks are called once to get the
53
+ # app to dispatch to, and that is cached, to ensure the any autoloads are completed
54
+ # before the application is frozen.
55
+ #
56
+ # The multi_run plugin is similar to the hash_branches and multi_route plugins, with
57
+ # the difference being the hash_branches and multi_route plugins keep all routing
58
+ # subtrees in the same Roda app/class, while multi_run dispatches to other rack apps.
59
+ # If you want to isolate your routing subtrees, multi_run is a better approach, but
60
+ # it does not let you set instance variables in the main Roda app and have those
61
+ # instance variables usable in the routing subtrees.
48
62
  #
49
63
  # To handle development environments that reload code, you can call the
50
64
  # +run+ class method without an app to remove dispatching for the prefix.
@@ -52,12 +66,21 @@ class Roda
52
66
  # Initialize the storage for the dispatched applications
53
67
  def self.configure(app)
54
68
  app.opts[:multi_run_apps] ||= {}
69
+ app.opts[:multi_run_app_blocks] ||= {}
55
70
  end
56
71
 
57
72
  module ClassMethods
73
+ # Convert app blocks into apps by calling them, in order to force autoloads
74
+ # and to speed up subsequent calls.
58
75
  # Freeze the multi_run apps so that there can be no thread safety issues at runtime.
59
76
  def freeze
60
- opts[:multi_run_apps].freeze
77
+ app_blocks = opts[:multi_run_app_blocks]
78
+ apps = opts[:multi_run_apps]
79
+ app_blocks.each do |prefix, block|
80
+ apps[prefix] = block.call
81
+ end
82
+ app_blocks.clear.freeze
83
+ apps.freeze
61
84
  self::RodaRequest.refresh_multi_run_regexp!
62
85
  super
63
86
  end
@@ -69,12 +92,22 @@ class Roda
69
92
  end
70
93
 
71
94
  # Add a rack application to dispatch to for the given prefix when
72
- # r.multi_run is called.
73
- def run(prefix, app=nil)
74
- if app
75
- multi_run_apps[prefix.to_s] = app
95
+ # r.multi_run is called. If a block is given, it is called every time
96
+ # there is a request for the route to get the app to call. If neither
97
+ # a block or an app is provided, any stored route for the prefix is
98
+ # removed. It is an error to provide both an app and block in the same call.
99
+ def run(prefix, app=nil, &block)
100
+ prefix = prefix.to_s
101
+ if app
102
+ raise Roda::RodaError, "cannot provide both app and block to Roda.run" if block
103
+ opts[:multi_run_apps][prefix] = app
104
+ opts[:multi_run_app_blocks].delete(prefix)
105
+ elsif block
106
+ opts[:multi_run_apps].delete(prefix)
107
+ opts[:multi_run_app_blocks][prefix] = block
76
108
  else
77
- multi_run_apps.delete(prefix.to_s)
109
+ opts[:multi_run_apps].delete(prefix)
110
+ opts[:multi_run_app_blocks].delete(prefix)
78
111
  end
79
112
  self::RodaRequest.refresh_multi_run_regexp!
80
113
  end
@@ -84,7 +117,7 @@ class Roda
84
117
  # Refresh the multi_run_regexp, using the stored route prefixes,
85
118
  # preferring longer routes before shorter routes.
86
119
  def refresh_multi_run_regexp!
87
- @multi_run_regexp = /(#{Regexp.union(roda_class.multi_run_apps.keys.sort.reverse)})/
120
+ @multi_run_regexp = /(#{Regexp.union((roda_class.opts[:multi_run_apps].keys + roda_class.opts[:multi_run_app_blocks].keys).sort.reverse)})/
88
121
  end
89
122
 
90
123
  # Refresh the multi_run_regexp if it hasn't been loaded yet.
@@ -95,11 +128,12 @@ class Roda
95
128
 
96
129
  module RequestMethods
97
130
  # If one of the stored route prefixes match the current request,
98
- # dispatch the request to the stored rack application.
131
+ # dispatch the request to the appropriate rack application.
99
132
  def multi_run
100
133
  on self.class.multi_run_regexp do |prefix|
101
134
  yield prefix if defined?(yield)
102
- run scope.class.multi_run_apps[prefix]
135
+ opts = scope.opts
136
+ run(opts[:multi_run_apps][prefix] || opts[:multi_run_app_blocks][prefix].call)
103
137
  end
104
138
  end
105
139
  end
@@ -320,14 +320,16 @@ class Roda
320
320
  # template file has been modified. This is an internal class and
321
321
  # the API is subject to change at any time.
322
322
  class TemplateMtimeWrapper
323
- def initialize(template_class, path, dependencies, *template_args)
324
- @template_class = template_class
325
- @path = path
326
- @template_args = template_args
327
- @dependencies = ([path] + Array(dependencies)) if dependencies
323
+ def initialize(roda_class, opts, template_opts)
324
+ @roda_class = roda_class
325
+ @opts = opts
326
+ @template_opts = template_opts
327
+ reset_template
328
328
 
329
- @mtime = template_last_modified if File.file?(path)
330
- @template = template_class.new(path, *template_args)
329
+ @path = opts[:path]
330
+ deps = opts[:dependencies]
331
+ @dependencies = ([@path] + Array(deps)) if deps
332
+ @mtime = template_last_modified
331
333
  end
332
334
 
333
335
  # If the template file exists and the modification time has
@@ -358,7 +360,7 @@ class Roda
358
360
  else
359
361
  if mtime != @mtime
360
362
  @mtime = mtime
361
- @template = @template_class.new(@path, *@template_args)
363
+ reset_template
362
364
  return true
363
365
  end
364
366
  end
@@ -407,6 +409,14 @@ class Roda
407
409
  end
408
410
  end
409
411
  end
412
+
413
+ private
414
+
415
+ # Reset the template, done every time the template or one of its
416
+ # dependencies is modified.
417
+ def reset_template
418
+ @template = @roda_class.create_template(@opts, @template_opts)
419
+ end
410
420
  end
411
421
 
412
422
  module ClassMethods
@@ -427,6 +437,17 @@ class Roda
427
437
  end
428
438
  end
429
439
 
440
+ # Return an Tilt::Template object based on the given opts and template_opts.
441
+ def create_template(opts, template_opts)
442
+ opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
443
+ end
444
+
445
+ # A proc that returns content, used for inline templates, so that the template
446
+ # doesn't hold a reference to the instance of the class
447
+ def inline_template_block(content)
448
+ Proc.new{content}
449
+ end
450
+
430
451
  # Copy the rendering options into the subclass, duping
431
452
  # them as necessary to prevent changes in the subclass
432
453
  # affecting the parent class.
@@ -656,7 +677,7 @@ class Roda
656
677
  if content = opts[:inline]
657
678
  path = opts[:path] = content
658
679
  template_class = opts[:template_class] ||= ::Tilt[engine]
659
- opts[:template_block] = Proc.new{content}
680
+ opts[:template_block] = self.class.inline_template_block(content)
660
681
  else
661
682
  opts[:views] ||= render_opts[:views]
662
683
  path = opts[:path] ||= template_path(opts)
@@ -730,14 +751,14 @@ class Roda
730
751
  !opts[:inline]
731
752
 
732
753
  if render_opts[:check_template_mtime] && !opts[:template_block] && !cache
733
- template = TemplateMtimeWrapper.new(opts[:template_class], opts[:path], opts[:dependencies], 1, template_opts)
754
+ template = TemplateMtimeWrapper.new(self.class, opts, template_opts)
734
755
 
735
756
  if define_compiled_method
736
757
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
737
758
  method_cache[method_cache_key] = template.define_compiled_method(self.class, method_name)
738
759
  end
739
760
  else
740
- template = opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
761
+ template = self.class.create_template(opts, template_opts)
741
762
 
742
763
  if define_compiled_method && cache != false
743
764
  begin
@@ -0,0 +1,86 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'tilt'
4
+ # :nocov:
5
+ raise 'Tilt version does not support coverable templates' unless Tilt::Template.method_defined?(:compiled_path=)
6
+ # :nocov:
7
+
8
+ #
9
+ class Roda
10
+ module RodaPlugins
11
+ # The render_coverage plugin builds on top of the render plugin
12
+ # and sets compiled_path on created templates. This allows
13
+ # Ruby's coverage library before Ruby 3.2 to consider code created
14
+ # by templates. You may not need this plugin on Ruby 3.2+, since
15
+ # on Ruby 3.2+, coverage can consider code loaded with +eval+.
16
+ # This plugin is only supported when using tilt 2.1+, since it
17
+ # requires the compiled_path supported added in tilt 2.1.
18
+ #
19
+ # By default, the render_coverage plugin will use +coverage/views+
20
+ # as the directory containing the compiled template files. You can
21
+ # change this by passing the :dir option when loading the plugin.
22
+ # By default, the plugin will set the compiled_path by taking the
23
+ # template file path, stripping off any of the allowed_paths used
24
+ # by the render plugin, and converting slashes to dashes. You can
25
+ # override the allowed_paths to strip by passing the :strip_paths
26
+ # option when loading the plugin. Paths outside :strip_paths (or
27
+ # the render plugin allowed_paths if :strip_paths is not set) will
28
+ # not have a compiled_path set.
29
+ #
30
+ # Due to how Ruby's coverage library works in regards to loading
31
+ # a compiled template file with identical code more than once,
32
+ # it may be beneficial to run coverage testing with the
33
+ # +RODA_RENDER_COMPILED_METHOD_SUPPORT+ environment variable set
34
+ # to +no+ if using this plugin.
35
+ module RenderCoverage
36
+ def self.load_dependencies(app, opts=OPTS)
37
+ app.plugin :render
38
+ end
39
+
40
+ # Use the :dir option to set the directory to store the compiled
41
+ # template files, and the :strip_paths directory for paths to
42
+ # strip.
43
+ def self.configure(app, opts=OPTS)
44
+ app.opts[:render_coverage_strip_paths] = opts[:strip_paths].map{|f| File.expand_path(f)} if opts.has_key?(:strip_paths)
45
+ coverage_dir = app.opts[:render_coverage_dir] = opts[:dir] || app.opts[:render_coverage_dir] || 'coverage/views'
46
+ Dir.mkdir(coverage_dir) unless File.directory?(coverage_dir)
47
+ end
48
+
49
+ module ClassMethods
50
+ # Set a compiled path on the created template, if the path for
51
+ # the template is in one of the allowed_views.
52
+ def create_template(opts, template_opts)
53
+ template = super
54
+ return template if opts[:template_block]
55
+
56
+ path = File.expand_path(opts[:path])
57
+ (self.opts[:render_coverage_strip_paths] || render_opts[:allowed_paths]).each do |dir|
58
+ if path.start_with?(dir + '/')
59
+ template.compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].gsub('/', '-'))
60
+ break
61
+ end
62
+ end
63
+
64
+ template
65
+ end
66
+ end
67
+
68
+ module InstanceMethods
69
+ private
70
+
71
+ # Convert template paths to real paths to try to ensure the same template is cached.
72
+ def template_path(opts)
73
+ path = super
74
+
75
+ if File.file?(path)
76
+ File.realpath(path)
77
+ else
78
+ path
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:render_coverage, RenderCoverage)
85
+ end
86
+ end
@@ -90,7 +90,7 @@ class Roda
90
90
  # action attribute, and returns a path you can pass to csrf_token
91
91
  # that should be valid for the form submission. The argument should
92
92
  # either be nil or a string representing a relative path, absolute
93
- # path, or full URL.
93
+ # path, or full URL (using appropriate URL encoding).
94
94
  # csrf_tag(path=nil, method='POST') :: An HTML hidden input tag string containing the CSRF token, suitable
95
95
  # for placing in an HTML form. Takes the same arguments as csrf_token.
96
96
  # csrf_token(path=nil, method='POST') :: The value of the csrf token, in case it needs to be accessed
@@ -23,18 +23,9 @@ class Roda
23
23
  # :foo
24
24
  # end
25
25
  module SymbolViews
26
- module RequestMethods
27
- private
28
-
29
- # If the block result is a symbol, consider the symbol a
30
- # template name and use the template view as the body.
31
- def block_result_body(result)
32
- if result.is_a?(Symbol)
33
- scope.view(result)
34
- else
35
- super
36
- end
37
- end
26
+ def self.configure(app)
27
+ app.plugin :custom_block_results
28
+ app.opts[:custom_block_results][Symbol] = :view
38
29
  end
39
30
  end
40
31
 
data/lib/roda/request.rb CHANGED
@@ -547,7 +547,7 @@ class Roda
547
547
  when nil, false
548
548
  # nothing
549
549
  else
550
- raise RodaError, "unsupported block result: #{result.inspect}"
550
+ unsupported_block_result(result)
551
551
  end
552
552
  end
553
553
 
@@ -652,6 +652,12 @@ class Roda
652
652
  end
653
653
  end
654
654
 
655
+ # How to handle block results that are not nil, false, or a String.
656
+ # By default raises an exception.
657
+ def unsupported_block_result(result)
658
+ raise RodaError, "unsupported block result: #{result.inspect}"
659
+ end
660
+
655
661
  # Handle an unsupported matcher.
656
662
  def unsupported_matcher(matcher)
657
663
  raise RodaError, "unsupported matcher: #{matcher.inspect}"
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 = 65
7
+ RodaMinorVersion = 68
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.65.0
4
+ version: 3.68.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: 2023-02-13 00:00:00.000000000 Z
11
+ date: 2023-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -238,6 +238,9 @@ extra_rdoc_files:
238
238
  - doc/release_notes/3.63.0.txt
239
239
  - doc/release_notes/3.64.0.txt
240
240
  - doc/release_notes/3.65.0.txt
241
+ - doc/release_notes/3.66.0.txt
242
+ - doc/release_notes/3.67.0.txt
243
+ - doc/release_notes/3.68.0.txt
241
244
  - doc/release_notes/3.7.0.txt
242
245
  - doc/release_notes/3.8.0.txt
243
246
  - doc/release_notes/3.9.0.txt
@@ -310,6 +313,9 @@ files:
310
313
  - doc/release_notes/3.63.0.txt
311
314
  - doc/release_notes/3.64.0.txt
312
315
  - doc/release_notes/3.65.0.txt
316
+ - doc/release_notes/3.66.0.txt
317
+ - doc/release_notes/3.67.0.txt
318
+ - doc/release_notes/3.68.0.txt
313
319
  - doc/release_notes/3.7.0.txt
314
320
  - doc/release_notes/3.8.0.txt
315
321
  - doc/release_notes/3.9.0.txt
@@ -340,6 +346,7 @@ files:
340
346
  - lib/roda/plugins/content_security_policy.rb
341
347
  - lib/roda/plugins/cookies.rb
342
348
  - lib/roda/plugins/csrf.rb
349
+ - lib/roda/plugins/custom_block_results.rb
343
350
  - lib/roda/plugins/custom_matchers.rb
344
351
  - lib/roda/plugins/default_headers.rb
345
352
  - lib/roda/plugins/default_status.rb
@@ -409,6 +416,7 @@ files:
409
416
  - lib/roda/plugins/recheck_precompiled_assets.rb
410
417
  - lib/roda/plugins/relative_path.rb
411
418
  - lib/roda/plugins/render.rb
419
+ - lib/roda/plugins/render_coverage.rb
412
420
  - lib/roda/plugins/render_each.rb
413
421
  - lib/roda/plugins/render_locals.rb
414
422
  - lib/roda/plugins/request_aref.rb
@@ -466,7 +474,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
466
474
  - !ruby/object:Gem::Version
467
475
  version: '0'
468
476
  requirements: []
469
- rubygems_version: 3.4.6
477
+ rubygems_version: 3.4.10
470
478
  signing_key:
471
479
  specification_version: 4
472
480
  summary: Routing tree web toolkit