roda 3.87.0 → 3.89.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: bee7ae126ad0e6e8081bba94be0ad60e2a5f7523c483b2441571549b1bd29274
4
- data.tar.gz: f5441e96617877d1347f4a5b6e54e5d48713ffc91a8ad3b9282781c0c26ca108
3
+ metadata.gz: 5c056e15afde950dcb27063296ba5b911fe3b900d16c121dd8833374731cdbaa
4
+ data.tar.gz: 95225a9c11b07979b50359df7451283eff570557c926f82c49cc34b67d1868fb
5
5
  SHA512:
6
- metadata.gz: 141408b4d85e7467c0b753c05180201b1a63a3096f713241b94d379bad8d65224d96f1d60978ac336a7d8b9fb3093f35c1d2887d422276cf255cd1c64c557719
7
- data.tar.gz: a64207e6d0a46d22c59bc0acc9d56ed43f4dbc75a9d22737908097564a2e4e801f3c19fd3bf95a28722dbf4a828d9930146dd088e421847709948d3a8f2cf24b
6
+ metadata.gz: e38354627d456f2b2bd593703d4ef64f5e934c071d439f1d9647cc8ef010de5fb463d001068ce2e06fd57b0cbccd926005d199b76761ebadefeda699937f5501
7
+ data.tar.gz: d542a024a5dd63bfa069baa38d2929bc0925d410641e303839f4a408139614249f223c3054f1f827550522aa21c0f52429184d3484001f7ed6996bbb9b366add
@@ -45,7 +45,7 @@ class Roda
45
45
  end
46
46
 
47
47
  # yield nothing
48
- def each
48
+ def each(&_)
49
49
  end
50
50
 
51
51
  # this should be called by the Rack server
@@ -53,27 +53,28 @@ class Roda
53
53
  # parse the request body as JSON. Ignore an empty request body.
54
54
  def POST
55
55
  env = @env
56
- if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
57
- post_params
58
- elsif (input = env["rack.input"]) && content_type =~ /json/
59
- str = _read_json_input(input)
60
- return super if str.empty?
61
- begin
62
- json_params = parse_json(str)
63
- rescue
64
- roda_class.opts[:json_parser_error_handler].call(self)
65
- end
56
+ if post_params = env["roda.json_params"]
57
+ return post_params
58
+ end
59
+
60
+ unless (input = env["rack.input"]) && (content_type = self.content_type) && content_type.include?('json')
61
+ return super
62
+ end
63
+
64
+ str = _read_json_input(input)
65
+ return super if str.empty?
66
+ begin
67
+ json_params = parse_json(str)
68
+ rescue
69
+ roda_class.opts[:json_parser_error_handler].call(self)
70
+ end
66
71
 
67
- wrap = roda_class.opts[:json_parser_wrap]
68
- if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
69
- json_params = {"_json"=>json_params}
70
- end
71
- env["roda.json_params"] = json_params
72
- env["rack.request.form_input"] = input
73
- json_params
74
- else
75
- super
72
+ wrap = roda_class.opts[:json_parser_wrap]
73
+ if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
74
+ json_params = {"_json"=>json_params}
76
75
  end
76
+ env["roda.json_params"] = json_params
77
+ json_params
77
78
  end
78
79
 
79
80
  private
@@ -151,6 +151,9 @@ class Roda
151
151
  mail
152
152
  end
153
153
  end
154
+ # :nocov:
155
+ ruby2_keywords(:mail) if respond_to?(:ruby2_keywords, true)
156
+ # :nocov:
154
157
 
155
158
  # Calls +mail+ with given arguments and immediately sends the resulting mail.
156
159
  def sendmail(*args)
@@ -158,6 +161,9 @@ class Roda
158
161
  m.deliver
159
162
  end
160
163
  end
164
+ # :nocov:
165
+ ruby2_keywords(:sendmail) if respond_to?(:ruby2_keywords, true)
166
+ # :nocov:
161
167
  end
162
168
 
163
169
  module RequestMethods
@@ -0,0 +1,76 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The part plugin adds a part method, which is a
7
+ # render-like method that treats all keywords as locals.
8
+ #
9
+ # # Can replace this:
10
+ # render(:template, locals: {foo: 'bar'})
11
+ #
12
+ # # With this:
13
+ # part(:template, foo: 'bar')
14
+ #
15
+ # On Ruby 2.7+, the part method takes a keyword splat, so you
16
+ # must pass keywords and not a positional hash for the locals.
17
+ #
18
+ # If you are using the :assume_fixed_locals render plugin option,
19
+ # template caching is enabled, you are using Ruby 3+, and you
20
+ # are freezing your Roda application, in addition to providing a
21
+ # simpler API, this also provides a significant performance
22
+ # improvement (more significant on Ruby 3.4+).
23
+ module Part
24
+ def self.load_dependencies(app)
25
+ app.plugin :render
26
+ end
27
+
28
+ module ClassMethods
29
+ # When freezing, optimize the part method if assuming fixed locals
30
+ # and caching templates.
31
+ def freeze
32
+ if render_opts[:assume_fixed_locals] && !render_opts[:check_template_mtime]
33
+ include AssumeFixedLocalsInstanceMethods
34
+ end
35
+
36
+ super
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ if RUBY_VERSION >= '2.7'
42
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
43
+ def part(template, **locals, &block)
44
+ render(template, :locals=>locals, &block)
45
+ end
46
+ RUBY
47
+ # :nocov:
48
+ else
49
+ def part(template, locals=OPTS, &block)
50
+ render(template, :locals=>locals, &block)
51
+ end
52
+ end
53
+ # :nocov:
54
+ end
55
+
56
+ module AssumeFixedLocalsInstanceMethods
57
+ # :nocov:
58
+ if RUBY_VERSION >= '3.0'
59
+ # :nocov:
60
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
61
+ def part(template, ...)
62
+ if optimized_method = _optimized_render_method_for_locals(template, OPTS)
63
+ send(optimized_method[0], ...)
64
+ else
65
+ super
66
+ end
67
+ end
68
+ RUBY
69
+ end
70
+ end
71
+ end
72
+
73
+ register_plugin(:part, Part)
74
+ end
75
+ end
76
+
@@ -48,6 +48,9 @@ class Roda
48
48
  #
49
49
  # :allowed_paths :: Set the template paths to allow. Attempts to render paths outside
50
50
  # of these paths will raise an error. Defaults to the +:views+ directory.
51
+ # :assume_fixed_locals :: Set if you are sure all templates in your application use fixed locals
52
+ # to allow for additional optimization. This is ignored unless both
53
+ # compiled methods and fixed locals are not supported.
51
54
  # :cache :: nil/false to explicitly disable permanent template caching. By default, permanent
52
55
  # template caching is disabled by default if RACK_ENV is development. When permanent
53
56
  # template caching is disabled, for templates with paths in the file system, the
@@ -118,6 +121,50 @@ class Roda
118
121
  # have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as
119
122
  # one of the keys.
120
123
  #
124
+ # = Fixed Locals in Templates
125
+ #
126
+ # By default, you can pass any local variables to any templates. A separate
127
+ # template method is compiled for each combination of locals. This causes
128
+ # multiple issues:
129
+ #
130
+ # * It is inefficient, especially for large templates that are called with
131
+ # many combinations of locals.
132
+ # * It hides issues if unused local variable names are passed to the template
133
+ # * It does not support default values for local variables
134
+ # * It does not support required local variables
135
+ # * It does not support cases where you want to pass values via a keyword splat
136
+ # * It does not support named blocks
137
+ #
138
+ # If you are using Tilt 2.6+, you can used fixed locals in templates, by
139
+ # passing the appropriate options in :template_opts. For example, if you
140
+ # are using ERB templates, the recommended way to use the render plugin is to
141
+ # use the +:extract_fixed_locals+ and +:default_fixed_locals+ template options:
142
+ #
143
+ # plugin :render, template_opts: {extract_fixed_locals: true, default_fixed_locals: '()'}
144
+ #
145
+ # This will default templates to not allowing any local variables to be passed.
146
+ # If the template requires local variables, you can specify them using a magic
147
+ # comment in the template, such as:
148
+ #
149
+ # <%# locals(required_local:, optional_local: nil) %>
150
+ #
151
+ # The magic comment is used as method parameters when defining the compiled template
152
+ # method.
153
+ #
154
+ # For better debugging of issues with invalid keywords being passed to templates that
155
+ # have not been updated to support fixed locals, it can be helpful to set
156
+ # +:default_fixed_locals+ to use a single optional keyword argument
157
+ # <tt>'(_no_kw: nil)'</tt>. This makes the error message show which keywords
158
+ # were passed, instead of showing that the takes no arguments (if you use <tt>'()'</tt>),
159
+ # or that no keywords are accepted (if you pass <tt>(**nil)</tt>).
160
+ #
161
+ # If you are sure your application works with all templates using fixed locals,
162
+ # set the :assume_fixed_locals render plugin option, which will allow the plugin
163
+ # to optimize cache lookup for renders with locals, and avoid duplicate compiled
164
+ # methods for templates rendered both with and without locals.
165
+ #
166
+ # See Tilt's documentation for more information regarding fixed locals.
167
+ #
121
168
  # = Speeding Up Template Rendering
122
169
  #
123
170
  # The render/view method calls are optimized for usage with a single symbol/string
@@ -131,6 +178,23 @@ class Roda
131
178
  # the hash, making sure the +:cache_key+ is unique to the template you are
132
179
  # rendering.
133
180
  #
181
+ # = Recommended +template_opts+
182
+ #
183
+ # Here are the recommended values of :template_opts for new applications (a couple
184
+ # are Erubi-specific and can be ignored if you are using other templates engines):
185
+ #
186
+ # plugin :render,
187
+ # assume_fixed_locals: true, # Optimize plugin by assuming all templates use fixed locals
188
+ # template_opts: {
189
+ # scope_class: self, # Always uses current class as scope class for compiled templates
190
+ # freeze: true, # Freeze string literals in templates
191
+ # extract_fixed_locals: true, # Support fixed locals in templates
192
+ # default_fixed_locals: '()', # Default to templates not supporting local variables
193
+ # escape: true, # For Erubi templates, escapes <%= by default (use <%== for unescaped
194
+ # chain_appends: true, # For Erubi templates, improves performance
195
+ # skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
196
+ # }
197
+ #
134
198
  # = Accepting Template Blocks in Methods
135
199
  #
136
200
  # If you are used to Rails, you may be surprised that this type of template code
@@ -228,6 +292,19 @@ class Roda
228
292
  ([1, -2].include?(((compiled_method_arity = Tilt::Template.instance_method(:compiled_method).arity) rescue false)))
229
293
  NO_CACHE = {:cache=>false}.freeze
230
294
  COMPILED_METHOD_SUPPORT = RUBY_VERSION >= '2.3' && tilt_compiled_method_support && ENV['RODA_RENDER_COMPILED_METHOD_SUPPORT'] != 'no'
295
+ FIXED_LOCALS_COMPILED_METHOD_SUPPORT = COMPILED_METHOD_SUPPORT && Tilt::Template.method_defined?(:fixed_locals?)
296
+
297
+ if FIXED_LOCALS_COMPILED_METHOD_SUPPORT
298
+ def self.tilt_template_fixed_locals?(template)
299
+ template.fixed_locals?
300
+ end
301
+ # :nocov:
302
+ else
303
+ def self.tilt_template_fixed_locals?(template)
304
+ false
305
+ end
306
+ end
307
+ # :nocov:
231
308
 
232
309
  if compiled_method_arity == -2
233
310
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
@@ -257,6 +334,7 @@ class Roda
257
334
  opts[:allowed_paths] ||= [opts[:views]].freeze
258
335
  opts[:allowed_paths] = opts[:allowed_paths].map{|f| app.expand_path(f, nil)}.uniq.freeze
259
336
  opts[:check_paths] = true unless opts.has_key?(:check_paths)
337
+ opts[:assume_fixed_locals] &&= FIXED_LOCALS_COMPILED_METHOD_SUPPORT
260
338
 
261
339
  unless opts.has_key?(:check_template_mtime)
262
340
  opts[:check_template_mtime] = if opts[:cache] == false || opts[:explicit_cache]
@@ -385,6 +463,11 @@ class Roda
385
463
  end
386
464
 
387
465
  if COMPILED_METHOD_SUPPORT
466
+ # Whether the underlying template uses fixed locals.
467
+ def fixed_locals?
468
+ Render.tilt_template_fixed_locals?(@template)
469
+ end
470
+
388
471
  # Compile a method in the given module with the given name that will
389
472
  # call the compiled template method, updating the compiled template method
390
473
  def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
@@ -403,6 +486,15 @@ class Roda
403
486
  method_name
404
487
  end
405
488
 
489
+ # Returns an appropriate value for the template method cache.
490
+ def define_compiled_method_cache_value(roda_class, method_name, locals_keys=EMPTY_ARRAY)
491
+ if compiled_method = define_compiled_method(roda_class, method_name, locals_keys)
492
+ [compiled_method, false].freeze
493
+ else
494
+ compiled_method
495
+ end
496
+ end
497
+
406
498
  private
407
499
 
408
500
  # Return the compiled method for the current template object.
@@ -422,7 +514,7 @@ class Roda
422
514
  mod.send(:private, method_name)
423
515
  end
424
516
 
425
- send(method_name, locals, &block)
517
+ _call_optimized_template_method([method_name, Render.tilt_template_fixed_locals?(template)], locals, &block)
426
518
  end
427
519
  end
428
520
  end
@@ -450,6 +542,12 @@ class Roda
450
542
  nil
451
543
  end
452
544
 
545
+ # Optimize _call_optimized_template_method if you know all templates
546
+ # are going to be using fixed locals.
547
+ if render_opts[:assume_fixed_locals] && !render_opts[:check_template_mtime]
548
+ include AssumeFixedLocalsInstanceMethods
549
+ end
550
+
453
551
  super
454
552
  end
455
553
  end
@@ -489,17 +587,17 @@ class Roda
489
587
  def _freeze_layout_method
490
588
  if render_opts[:layout]
491
589
  instance = allocate
590
+ # This needs to be called even if COMPILED_METHOD_SUPPORT is not set,
591
+ # in order for the precompile_templates plugin to work correctly.
492
592
  instance.send(:retrieve_template, instance.send(:view_layout_opts, OPTS))
493
593
 
494
- if COMPILED_METHOD_SUPPORT
495
- if (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
594
+ if COMPILED_METHOD_SUPPORT && (layout_template = render_opts[:optimize_layout]) && !opts[:render][:optimized_layout_method_created]
496
595
  instance.send(:retrieve_template, :template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
497
596
  layout_method = opts[:render][:template_method_cache][:_roda_layout]
498
597
  define_method(:_layout_method){layout_method}
499
598
  private :_layout_method
500
599
  alias_method(:_layout_method, :_layout_method)
501
600
  opts[:render] = opts[:render].merge(:optimized_layout_method_created=>true)
502
- end
503
601
  end
504
602
  end
505
603
  end
@@ -509,9 +607,9 @@ class Roda
509
607
  # Render the given template. See Render for details.
510
608
  def render(template, opts = (no_opts = true; optimized_template = _cached_template_method(template); OPTS), &block)
511
609
  if optimized_template
512
- send(optimized_template, OPTS, &block)
610
+ _call_optimized_template_method(optimized_template, OPTS, &block)
513
611
  elsif !no_opts && opts.length == 1 && (locals = opts[:locals]) && (optimized_template = _optimized_render_method_for_locals(template, locals))
514
- send(optimized_template, locals, &block)
612
+ _call_optimized_template_method(optimized_template, locals, &block)
515
613
  else
516
614
  opts = render_template_opts(template, opts)
517
615
  retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block)
@@ -534,7 +632,7 @@ class Roda
534
632
  # and use it if so. This way avoids the extra conditional and local variable
535
633
  # assignments in the next section.
536
634
  if layout_method = _layout_method
537
- return send(layout_method, OPTS){content}
635
+ return _call_optimized_template_method(layout_method, OPTS){content}
538
636
  end
539
637
 
540
638
  # If we have an optimized template method but no optimized layout method, create the
@@ -543,7 +641,7 @@ class Roda
543
641
  if layout_template = self.class.opts[:render][:optimize_layout]
544
642
  retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout)
545
643
  if layout_method = _layout_method
546
- return send(layout_method, OPTS){content}
644
+ return _call_optimized_template_method(layout_method, OPTS){content}
547
645
  end
548
646
  end
549
647
  else
@@ -592,39 +690,54 @@ class Roda
592
690
  # of the template render if the optimized path is used, or nil if the optimized
593
691
  # path is not used and the long method needs to be used.
594
692
  def _optimized_render_method_for_locals(template, locals)
693
+ render_opts = self.render_opts
595
694
  return unless method_cache = render_opts[:template_method_cache]
596
695
 
597
- locals_keys = locals.keys.sort
598
- key = [:_render_locals, template, locals_keys]
599
-
600
- optimized_template = case template
696
+ case template
601
697
  when String, Symbol
602
- _cached_template_method_lookup(method_cache, key)
698
+ if fixed_locals = render_opts[:assume_fixed_locals]
699
+ key = template
700
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
701
+ return optimized_template
702
+ end
703
+ else
704
+ key = [:_render_locals, template]
705
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
706
+ # Fixed locals case
707
+ return optimized_template
708
+ end
709
+
710
+ locals_keys = locals.keys.sort
711
+ key << locals_keys
712
+ if optimized_template = _cached_template_method_lookup(method_cache, key)
713
+ # Regular locals case
714
+ return optimized_template
715
+ end
716
+ end
603
717
  else
604
718
  return
605
719
  end
606
720
 
607
- case optimized_template
608
- when Symbol
609
- optimized_template
610
- else
611
- if method_cache_key = _cached_template_method_key(key)
612
- template_obj = retrieve_template(render_template_opts(template, NO_CACHE))
613
- method_name = :"_roda_template_locals_#{self.class.object_id}_#{method_cache_key}"
721
+ if method_cache_key = _cached_template_method_key(key)
722
+ template_obj = retrieve_template(render_template_opts(template, NO_CACHE))
723
+ unless fixed_locals
724
+ key.pop if fixed_locals = Render.tilt_template_fixed_locals?(template_obj)
725
+ key.freeze
726
+ end
727
+ method_name = :"_roda_template_locals_#{self.class.object_id}_#{method_cache_key}"
614
728
 
615
- method_cache[method_cache_key] = case template_obj
616
- when Render::TemplateMtimeWrapper
617
- template_obj.define_compiled_method(self.class, method_name, locals_keys)
729
+ method_cache[method_cache_key] = case template_obj
730
+ when Render::TemplateMtimeWrapper
731
+ template_obj.define_compiled_method_cache_value(self.class, method_name, locals_keys)
732
+ else
733
+ begin
734
+ unbound_method = Render.tilt_template_compiled_method(template_obj, locals_keys, self.class)
735
+ rescue ::NotImplementedError
736
+ false
618
737
  else
619
- begin
620
- unbound_method = Render.tilt_template_compiled_method(template_obj, locals_keys, self.class)
621
- rescue ::NotImplementedError
622
- false
623
- else
624
- self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
625
- self.class::RodaCompiledTemplates.send(:private, method_name)
626
- method_name
627
- end
738
+ self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
739
+ self.class::RodaCompiledTemplates.send(:private, method_name)
740
+ [method_name, fixed_locals].freeze
628
741
  end
629
742
  end
630
743
  end
@@ -634,11 +747,47 @@ class Roda
634
747
  # a single argument is passed to view.
635
748
  def _optimized_view_content(template)
636
749
  if optimized_template = _cached_template_method(template)
637
- send(optimized_template, OPTS)
750
+ _call_optimized_template_method(optimized_template, OPTS)
638
751
  elsif template.is_a?(Hash) && template.length == 1
639
752
  template[:content]
640
753
  end
641
754
  end
755
+
756
+ if RUBY_VERSION >= '3'
757
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
758
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
759
+ if fixed_locals
760
+ send(meth, **locals, &block)
761
+ else
762
+ send(meth, locals, &block)
763
+ end
764
+ end
765
+ RUBY
766
+ # :nocov:
767
+ elsif RUBY_VERSION >= '2'
768
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
769
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
770
+ if fixed_locals
771
+ if locals.empty?
772
+ send(meth, &block)
773
+ else
774
+ send(meth, **locals, &block)
775
+ end
776
+ else
777
+ send(meth, locals, &block)
778
+ end
779
+ end
780
+ RUBY
781
+ else
782
+ # Call the optimized template method. This is designed to be used with the
783
+ # method cache, which caches the method name and whether the method uses
784
+ # fixed locals. Methods with fixed locals need to be called with a keyword
785
+ # splat.
786
+ def _call_optimized_template_method((meth, fixed_locals), locals, &block)
787
+ send(meth, locals, &block)
788
+ end
789
+ end
790
+ # :nocov:
642
791
  else
643
792
  def _cached_template_method(_)
644
793
  nil
@@ -648,10 +797,6 @@ class Roda
648
797
  nil
649
798
  end
650
799
 
651
- def _layout_method
652
- nil
653
- end
654
-
655
800
  def _optimized_render_method_for_locals(_, _)
656
801
  nil
657
802
  end
@@ -661,7 +806,6 @@ class Roda
661
806
  end
662
807
  end
663
808
 
664
-
665
809
  # Convert template options to single hash when rendering templates using render.
666
810
  def render_template_opts(template, opts)
667
811
  parse_template_opts(template, opts)
@@ -772,7 +916,7 @@ class Roda
772
916
 
773
917
  if define_compiled_method
774
918
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
775
- method_cache[method_cache_key] = template.define_compiled_method(self.class, method_name)
919
+ method_cache[method_cache_key] = template.define_compiled_method_cache_value(self.class, method_name)
776
920
  end
777
921
  else
778
922
  template = self.class.create_template(opts, template_opts)
@@ -786,7 +930,7 @@ class Roda
786
930
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
787
931
  self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method)
788
932
  self.class::RodaCompiledTemplates.send(:private, method_name)
789
- method_cache[method_cache_key] = method_name
933
+ method_cache[method_cache_key] = [method_name, Render.tilt_template_fixed_locals?(template)].freeze
790
934
  end
791
935
  end
792
936
  end
@@ -835,6 +979,18 @@ class Roda
835
979
  end
836
980
  end
837
981
  end
982
+
983
+ module AssumeFixedLocalsInstanceMethods
984
+ # :nocov:
985
+ if RUBY_VERSION >= '3.0'
986
+ # :nocov:
987
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
988
+ def _call_optimized_template_method((meth,_), locals, &block)
989
+ send(meth, **locals, &block)
990
+ end
991
+ RUBY
992
+ end
993
+ end
838
994
  end
839
995
 
840
996
  register_plugin(:render, Render)
@@ -50,17 +50,36 @@ class Roda
50
50
  # Set a compiled path on the created template, if the path for
51
51
  # the template is in one of the allowed_views.
52
52
  def create_template(opts, template_opts)
53
- template = super
54
- return template if opts[:template_block]
53
+ return super if opts[:template_block]
55
54
 
56
55
  path = File.expand_path(opts[:path])
56
+ compiled_path = nil
57
57
  (self.opts[:render_coverage_strip_paths] || render_opts[:allowed_paths]).each do |dir|
58
58
  if path.start_with?(dir + '/')
59
- template.compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].gsub('/', '-'))
59
+ compiled_path = File.join(self.opts[:render_coverage_dir], path[dir.length+1, 10000000].gsub('/', '-'))
60
60
  break
61
61
  end
62
62
  end
63
63
 
64
+ # For Tilt 2.6+, when using :scope_class and fixed locals, must provide
65
+ # compiled path as option, since compilation happens during initalization
66
+ # in that case. This option should be ignored if the template does not
67
+ # support it, but some template class may break if the option is not
68
+ # handled, so for compatibility, only set the method if Tilt::Template
69
+ # will handle it.
70
+ if compiled_path && Tilt::Template.method_defined?(:fixed_locals?)
71
+ template_opts = template_opts.dup
72
+ template_opts[:compiled_path] = compiled_path
73
+ compiled_path = nil
74
+ end
75
+
76
+ template = super
77
+
78
+ # Set compiled path for template when using older tilt versions.
79
+ # :nocov:
80
+ template.compiled_path = compiled_path if compiled_path
81
+ # :nocov:
82
+
64
83
  template
65
84
  end
66
85
  end
@@ -105,7 +105,7 @@ class Roda
105
105
  def _optimized_render_each(enum, optimized_template, as, locals)
106
106
  enum.map do |v|
107
107
  locals[as] = v
108
- send(optimized_template, locals)
108
+ _call_optimized_template_method(optimized_template, locals)
109
109
  end.join
110
110
  end
111
111
  else
@@ -23,8 +23,11 @@ class Roda
23
23
  # :foo
24
24
  # end
25
25
  module SymbolViews
26
- def self.configure(app)
26
+ def self.load_dependencies(app)
27
27
  app.plugin :custom_block_results
28
+ end
29
+
30
+ def self.configure(app)
28
31
  app.opts[:custom_block_results][Symbol] = :view
29
32
  end
30
33
  end
@@ -133,7 +133,7 @@ class Roda
133
133
  return if @_view_options || @_layout_options
134
134
 
135
135
  if subdir = @_view_subdir
136
- template = [subdir, template]
136
+ template = [subdir, template].freeze
137
137
  end
138
138
 
139
139
  super
data/lib/roda/response.rb CHANGED
@@ -46,8 +46,6 @@ class Roda
46
46
 
47
47
  # Instance methods for RodaResponse
48
48
  module ResponseMethods
49
- DEFAULT_HEADERS = {RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}.freeze
50
-
51
49
  # The body for the current response.
52
50
  attr_reader :body
53
51
 
@@ -179,11 +177,15 @@ class Roda
179
177
  private
180
178
 
181
179
  if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
180
+ DEFAULT_HEADERS = Rack::Headers[{RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}].freeze
181
+
182
182
  # Use Rack::Headers for headers by default on Rack 3
183
183
  def _initialize_headers
184
184
  Rack::Headers.new
185
185
  end
186
186
  else
187
+ DEFAULT_HEADERS = {RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}.freeze
188
+
187
189
  # Use plain hash for headers by default on Rack 1-2
188
190
  def _initialize_headers
189
191
  {}
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 = 87
7
+ RodaMinorVersion = 89
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,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.87.0
4
+ version: 3.89.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-17 00:00:00.000000000 Z
10
+ date: 2025-02-12 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rack
@@ -150,7 +149,6 @@ dependencies:
150
149
  - - ">="
151
150
  - !ruby/object:Gem::Version
152
151
  version: '0'
153
- description:
154
152
  email:
155
153
  - code@jeremyevans.net
156
154
  executables: []
@@ -255,6 +253,7 @@ files:
255
253
  - lib/roda/plugins/padrino_render.rb
256
254
  - lib/roda/plugins/param_matchers.rb
257
255
  - lib/roda/plugins/params_capturing.rb
256
+ - lib/roda/plugins/part.rb
258
257
  - lib/roda/plugins/partials.rb
259
258
  - lib/roda/plugins/pass.rb
260
259
  - lib/roda/plugins/path.rb
@@ -313,7 +312,6 @@ metadata:
313
312
  documentation_uri: https://roda.jeremyevans.net/documentation.html
314
313
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
315
314
  source_code_uri: https://github.com/jeremyevans/roda
316
- post_install_message:
317
315
  rdoc_options: []
318
316
  require_paths:
319
317
  - lib
@@ -328,8 +326,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
326
  - !ruby/object:Gem::Version
329
327
  version: '0'
330
328
  requirements: []
331
- rubygems_version: 3.5.22
332
- signing_key:
329
+ rubygems_version: 3.6.2
333
330
  specification_version: 4
334
331
  summary: Routing tree web toolkit
335
332
  test_files: []