actionview 5.2.8.1 → 6.0.6.1

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.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +280 -94
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +108 -11
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/digestor.rb +12 -20
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +8 -31
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +19 -12
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +70 -27
  20. data/lib/action_view/helpers/form_helper.rb +240 -8
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +17 -15
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +8 -2
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  28. data/lib/action_view/helpers/tag_helper.rb +8 -7
  29. data/lib/action_view/helpers/tags/base.rb +9 -6
  30. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  34. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  35. data/lib/action_view/helpers/tags/date_field.rb +0 -1
  36. data/lib/action_view/helpers/tags/date_select.rb +0 -1
  37. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -1
  39. data/lib/action_view/helpers/tags/label.rb +0 -1
  40. data/lib/action_view/helpers/tags/month_field.rb +0 -1
  41. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  42. data/lib/action_view/helpers/tags/select.rb +0 -1
  43. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  44. data/lib/action_view/helpers/tags/time_field.rb +0 -1
  45. data/lib/action_view/helpers/tags/translator.rb +1 -6
  46. data/lib/action_view/helpers/tags/week_field.rb +0 -1
  47. data/lib/action_view/helpers/text_helper.rb +3 -4
  48. data/lib/action_view/helpers/translation_helper.rb +19 -17
  49. data/lib/action_view/helpers/url_helper.rb +14 -14
  50. data/lib/action_view/helpers.rb +0 -2
  51. data/lib/action_view/layouts.rb +5 -8
  52. data/lib/action_view/log_subscriber.rb +6 -7
  53. data/lib/action_view/lookup_context.rb +75 -32
  54. data/lib/action_view/path_set.rb +5 -11
  55. data/lib/action_view/railtie.rb +24 -1
  56. data/lib/action_view/record_identifier.rb +2 -3
  57. data/lib/action_view/renderer/abstract_renderer.rb +56 -4
  58. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +63 -17
  59. data/lib/action_view/renderer/partial_renderer.rb +67 -57
  60. data/lib/action_view/renderer/renderer.rb +16 -4
  61. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -7
  62. data/lib/action_view/renderer/template_renderer.rb +25 -20
  63. data/lib/action_view/rendering.rb +51 -32
  64. data/lib/action_view/routing_url_for.rb +12 -11
  65. data/lib/action_view/template/error.rb +30 -15
  66. data/lib/action_view/template/handlers/builder.rb +2 -2
  67. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  68. data/lib/action_view/template/handlers/erb.rb +17 -8
  69. data/lib/action_view/template/handlers/html.rb +1 -1
  70. data/lib/action_view/template/handlers/raw.rb +2 -2
  71. data/lib/action_view/template/handlers.rb +27 -1
  72. data/lib/action_view/template/html.rb +14 -5
  73. data/lib/action_view/template/inline.rb +22 -0
  74. data/lib/action_view/template/raw_file.rb +28 -0
  75. data/lib/action_view/template/resolver.rb +134 -135
  76. data/lib/action_view/template/sources/file.rb +17 -0
  77. data/lib/action_view/template/sources.rb +13 -0
  78. data/lib/action_view/template/text.rb +5 -3
  79. data/lib/action_view/template.rb +102 -71
  80. data/lib/action_view/test_case.rb +3 -4
  81. data/lib/action_view/testing/resolvers.rb +33 -21
  82. data/lib/action_view/unbound_template.rb +31 -0
  83. data/lib/action_view/view_paths.rb +25 -2
  84. data/lib/action_view.rb +4 -2
  85. data/lib/assets/compiled/rails-ujs.js +30 -4
  86. metadata +27 -18
  87. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -5,7 +5,6 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class MonthField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
9
  value.try(:strftime, "%Y-%m")
11
10
  end
@@ -23,7 +23,6 @@ module ActionView
23
23
  end
24
24
 
25
25
  private
26
-
27
26
  def checked?(value)
28
27
  value.to_s == @tag_value.to_s
29
28
  end
@@ -29,7 +29,6 @@ module ActionView
29
29
  end
30
30
 
31
31
  private
32
-
33
32
  # Grouped choices look like this:
34
33
  #
35
34
  # [nil, []]
@@ -24,7 +24,6 @@ module ActionView
24
24
  end
25
25
 
26
26
  private
27
-
28
27
  def field_type
29
28
  self.class.field_type
30
29
  end
@@ -5,7 +5,6 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class TimeField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
9
  value.try(:strftime, "%T.%L")
11
10
  end
@@ -16,13 +16,8 @@ module ActionView
16
16
  translated_attribute || human_attribute_name
17
17
  end
18
18
 
19
- # TODO Change this to private once we've dropped Ruby 2.2 support.
20
- # Workaround for Ruby 2.2 "private attribute?" warning.
21
- protected
22
-
23
- attr_reader :object_name, :method_and_value, :scope, :model
24
-
25
19
  private
20
+ attr_reader :object_name, :method_and_value, :scope, :model
26
21
 
27
22
  def i18n_default
28
23
  if model
@@ -5,7 +5,6 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class WeekField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
9
  value.try(:strftime, "%Y-W%V")
11
10
  end
@@ -188,7 +188,7 @@ module ActionView
188
188
 
189
189
  unless separator.empty?
190
190
  text.split(separator).each do |value|
191
- if value.match(regex)
191
+ if value.match?(regex)
192
192
  phrase = value
193
193
  break
194
194
  end
@@ -228,7 +228,7 @@ module ActionView
228
228
  # pluralize(2, 'Person', locale: :de)
229
229
  # # => 2 Personen
230
230
  def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
- word = if (count == 1 || count.to_s =~ /^1(\.0+)?$/)
231
+ word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
232
232
  singular
233
233
  else
234
234
  plural || singular.pluralize(locale)
@@ -259,7 +259,7 @@ module ActionView
259
259
  # # => Once\r\nupon\r\na\r\ntime
260
260
  def word_wrap(text, line_width: 80, break_sequence: "\n")
261
261
  text.split("\n").collect! do |line|
262
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
263
263
  end * break_sequence
264
264
  end
265
265
 
@@ -426,7 +426,6 @@ module ActionView
426
426
  end
427
427
 
428
428
  private
429
-
430
429
  def next_index
431
430
  step_index(1)
432
431
  end
@@ -57,13 +57,10 @@ module ActionView
57
57
  # that include HTML tags so that you know what kind of output to expect
58
58
  # when you call translate in a template and translators know which keys
59
59
  # they can provide HTML values for.
60
- def translate(key, options = {})
61
- options = options.dup
62
- has_default = options.has_key?(:default)
63
- remaining_defaults = Array(options.delete(:default)).compact
64
-
65
- if has_default && !remaining_defaults.first.kind_of?(Symbol)
66
- options[:default] = remaining_defaults
60
+ def translate(key, **options)
61
+ if options.has_key?(:default)
62
+ remaining_defaults = Array.wrap(options.delete(:default)).compact
63
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
67
64
  end
68
65
 
69
66
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
@@ -88,24 +85,26 @@ module ActionView
88
85
 
89
86
  html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
90
87
 
91
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
88
+ translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise))
92
89
 
93
90
  if translation.equal?(MISSING_TRANSLATION)
94
91
  options[:default].first
92
+ elsif translation.respond_to?(:map)
93
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
95
94
  else
96
95
  translation.respond_to?(:html_safe) ? translation.html_safe : translation
97
96
  end
98
97
  else
99
- I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
98
+ I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise))
100
99
  end
101
100
  rescue I18n::MissingTranslationData => e
102
101
  if remaining_defaults.present?
103
- translate remaining_defaults.shift, options.merge(default: remaining_defaults)
102
+ translate remaining_defaults.shift, **options.merge(default: remaining_defaults)
104
103
  else
105
104
  raise e if raise_error
106
105
 
107
106
  keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
108
- title = "translation missing: #{keys.join('.')}".dup
107
+ title = +"translation missing: #{keys.join('.')}"
109
108
 
110
109
  interpolations = options.except(:default, :scope)
111
110
  if interpolations.any?
@@ -121,10 +120,10 @@ module ActionView
121
120
 
122
121
  # Delegates to <tt>I18n.localize</tt> with no additional functionality.
123
122
  #
124
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
123
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
125
124
  # for more information.
126
- def localize(*args)
127
- I18n.localize(*args)
125
+ def localize(object, **options)
126
+ I18n.localize(object, **options)
128
127
  end
129
128
  alias :l :localize
130
129
 
@@ -133,9 +132,12 @@ module ActionView
133
132
  private_constant :MISSING_TRANSLATION
134
133
 
135
134
  def scope_key_by_partial(key)
136
- if key.to_s.first == "."
135
+ stringified_key = key.to_s
136
+ if stringified_key.first == "."
137
137
  if @virtual_path
138
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
138
+ @_scope_key_by_partial_cache ||= {}
139
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
140
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
139
141
  else
140
142
  raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
141
143
  end
@@ -145,7 +147,7 @@ module ActionView
145
147
  end
146
148
 
147
149
  def html_safe_translation_key?(key)
148
- /(\b|_|\.)html$/.match?(key.to_s)
150
+ /(?:_|\b)html\z/.match?(key.to_s)
149
151
  end
150
152
  end
151
153
  end
@@ -200,9 +200,9 @@ module ActionView
200
200
  html_options = convert_options_to_data_attributes(options, html_options)
201
201
 
202
202
  url = url_for(options)
203
- html_options["href".freeze] ||= url
203
+ html_options["href"] ||= url
204
204
 
205
- content_tag("a".freeze, name || url, html_options, &block)
205
+ content_tag("a", name || url, html_options, &block)
206
206
  end
207
207
 
208
208
  # Generates a form containing a single button that submits to the URL created
@@ -308,7 +308,7 @@ module ActionView
308
308
  params = html_options.delete("params")
309
309
 
310
310
  method = html_options.delete("method").to_s
311
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe
311
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
312
312
 
313
313
  form_method = method == "get" ? "get" : "post"
314
314
  form_options = html_options.delete("form") || {}
@@ -321,7 +321,7 @@ module ActionView
321
321
  request_method = method.empty? ? "post" : method
322
322
  token_tag(nil, form_options: { action: url, method: request_method })
323
323
  else
324
- "".freeze
324
+ ""
325
325
  end
326
326
 
327
327
  html_options = convert_options_to_data_attributes(options, html_options)
@@ -487,12 +487,12 @@ module ActionView
487
487
  option = html_options.delete(item).presence || next
488
488
  "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
489
489
  }.compact
490
- extras = extras.empty? ? "".freeze : "?" + extras.join("&")
490
+ extras = extras.empty? ? "" : "?" + extras.join("&")
491
491
 
492
492
  encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
493
493
  html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
494
494
 
495
- content_tag("a".freeze, name || email_address, html_options, &block)
495
+ content_tag("a", name || email_address, html_options, &block)
496
496
  end
497
497
 
498
498
  # True if the current request URI was generated by the given +options+.
@@ -553,7 +553,7 @@ module ActionView
553
553
  url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
554
554
 
555
555
  # We ignore any extra parameters in the request_uri if the
556
- # submitted url doesn't have any either. This lets the function
556
+ # submitted URL doesn't have any either. This lets the function
557
557
  # work with things like ?order=asc
558
558
  # the behaviour can be disabled with check_parameters: true
559
559
  request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
@@ -575,21 +575,21 @@ module ActionView
575
575
  def convert_options_to_data_attributes(options, html_options)
576
576
  if html_options
577
577
  html_options = html_options.stringify_keys
578
- html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
578
+ html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
579
579
 
580
- method = html_options.delete("method".freeze)
580
+ method = html_options.delete("method")
581
581
 
582
582
  add_method_to_attributes!(html_options, method) if method
583
583
 
584
584
  html_options
585
585
  else
586
- link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
586
+ link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
587
587
  end
588
588
  end
589
589
 
590
590
  def link_to_remote_options?(options)
591
591
  if options.is_a?(Hash)
592
- options.delete("remote".freeze) || options.delete(:remote)
592
+ options.delete("remote") || options.delete(:remote)
593
593
  end
594
594
  end
595
595
 
@@ -618,11 +618,11 @@ module ActionView
618
618
  end
619
619
 
620
620
  def token_tag(token = nil, form_options: {})
621
- if token != false && protect_against_forgery?
621
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
622
622
  token ||= form_authenticity_token(form_options: form_options)
623
623
  tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
624
624
  else
625
- "".freeze
625
+ ""
626
626
  end
627
627
  end
628
628
 
@@ -636,7 +636,7 @@ module ActionView
636
636
  # to_form_params(name: 'David', nationality: 'Danish')
637
637
  # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
638
638
  #
639
- # to_form_params(country: {name: 'Denmark'})
639
+ # to_form_params(country: { name: 'Denmark' })
640
640
  # # => [{name: 'country[name]', value: 'Denmark'}]
641
641
  #
642
642
  # to_form_params(countries: ['Denmark', 'Sweden']})
@@ -23,7 +23,6 @@ module ActionView #:nodoc:
23
23
  autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
24
24
  autoload :NumberHelper
25
25
  autoload :OutputSafetyHelper
26
- autoload :RecordTagHelper
27
26
  autoload :RenderingHelper
28
27
  autoload :SanitizeHelper
29
28
  autoload :TagHelper
@@ -57,7 +56,6 @@ module ActionView #:nodoc:
57
56
  include JavaScriptHelper
58
57
  include NumberHelper
59
58
  include OutputSafetyHelper
60
- include RecordTagHelper
61
59
  include RenderingHelper
62
60
  include SanitizeHelper
63
61
  include TagHelper
@@ -224,7 +224,6 @@ module ActionView
224
224
  # that if no layout conditions are used, this method is not used
225
225
  module LayoutConditions # :nodoc:
226
226
  private
227
-
228
227
  # Determines whether the current action has a layout definition by
229
228
  # checking the action name against the :only and :except conditions
230
229
  # set by the <tt>layout</tt> method.
@@ -322,7 +321,7 @@ module ActionView
322
321
  end
323
322
 
324
323
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
325
- def _layout(formats)
324
+ def _layout(lookup_context, formats)
326
325
  if _conditional_layout?
327
326
  #{layout_definition}
328
327
  else
@@ -334,7 +333,6 @@ module ActionView
334
333
  end
335
334
 
336
335
  private
337
-
338
336
  # If no layout is supplied, look for a template named the return
339
337
  # value of this method.
340
338
  #
@@ -372,7 +370,6 @@ module ActionView
372
370
  end
373
371
 
374
372
  private
375
-
376
373
  def _conditional_layout?
377
374
  true
378
375
  end
@@ -388,8 +385,8 @@ module ActionView
388
385
  case name
389
386
  when String then _normalize_layout(name)
390
387
  when Proc then name
391
- when true then Proc.new { |formats| _default_layout(formats, true) }
392
- when :default then Proc.new { |formats| _default_layout(formats, false) }
388
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
389
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
393
390
  when false, nil then nil
394
391
  else
395
392
  raise ArgumentError,
@@ -411,9 +408,9 @@ module ActionView
411
408
  #
412
409
  # ==== Returns
413
410
  # * <tt>template</tt> - The template object for the default layout (or +nil+)
414
- def _default_layout(formats, require_layout = false)
411
+ def _default_layout(lookup_context, formats, require_layout = false)
415
412
  begin
416
- value = _layout(formats) if action_has_layout?
413
+ value = _layout(lookup_context, formats) if action_has_layout?
417
414
  rescue NameError => e
418
415
  raise e, "Could not render layout: #{e.message}"
419
416
  end
@@ -16,17 +16,17 @@ module ActionView
16
16
 
17
17
  def render_template(event)
18
18
  info do
19
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
19
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
20
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
- message << " (#{event.duration.round(1)}ms)"
21
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
22
  end
23
23
  end
24
24
 
25
25
  def render_partial(event)
26
26
  info do
27
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
27
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
28
28
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
- message << " (#{event.duration.round(1)}ms)"
29
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
30
30
  message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
31
31
  message
32
32
  end
@@ -37,7 +37,7 @@ module ActionView
37
37
 
38
38
  info do
39
39
  " Rendered collection of #{from_rails_root(identifier)}" \
40
- " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
40
+ " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
41
41
  end
42
42
  end
43
43
 
@@ -54,7 +54,6 @@ module ActionView
54
54
  end
55
55
 
56
56
  private
57
-
58
57
  EMPTY = ""
59
58
  def from_rails_root(string) # :doc:
60
59
  string = string.sub(rails_root, EMPTY)
@@ -85,7 +84,7 @@ module ActionView
85
84
 
86
85
  def log_rendering_start(payload)
87
86
  info do
88
- message = " Rendering #{from_rails_root(payload[:identifier])}".dup
87
+ message = +" Rendering #{from_rails_root(payload[:identifier])}"
89
88
  message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
90
89
  message
91
90
  end
@@ -3,6 +3,7 @@
3
3
  require "concurrent/map"
4
4
  require "active_support/core_ext/module/remove_method"
5
5
  require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/deprecation"
6
7
  require "action_view/template/resolver"
7
8
 
8
9
  module ActionView
@@ -15,6 +16,8 @@ module ActionView
15
16
  # only once during the request, it speeds up all cache accesses.
16
17
  class LookupContext #:nodoc:
17
18
  attr_accessor :prefixes, :rendered_format
19
+ deprecate :rendered_format
20
+ deprecate :rendered_format=
18
21
 
19
22
  mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
20
23
 
@@ -24,7 +27,7 @@ module ActionView
24
27
  registered_details << name
25
28
  Accessors::DEFAULT_PROCS[name] = block
26
29
 
27
- Accessors.send :define_method, :"default_#{name}", &block
30
+ Accessors.define_method(:"default_#{name}", &block)
28
31
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
32
  def #{name}
30
33
  @details.fetch(:#{name}, [])
@@ -57,21 +60,39 @@ module ActionView
57
60
  alias :eql? :equal?
58
61
 
59
62
  @details_keys = Concurrent::Map.new
63
+ @digest_cache = Concurrent::Map.new
64
+ @view_context_mutex = Mutex.new
60
65
 
61
- def self.get(details)
66
+ def self.digest_cache(details)
67
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
68
+ end
69
+
70
+ def self.details_cache_key(details)
62
71
  if details[:formats]
63
72
  details = details.dup
64
73
  details[:formats] &= Template::Types.symbols
65
74
  end
66
- @details_keys[details] ||= Concurrent::Map.new
75
+ @details_keys[details] ||= Object.new
67
76
  end
68
77
 
69
78
  def self.clear
79
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
80
+ path_set.each(&:clear_cache)
81
+ end
82
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
83
+ @view_context_class = nil
70
84
  @details_keys.clear
85
+ @digest_cache.clear
71
86
  end
72
87
 
73
88
  def self.digest_caches
74
- @details_keys.values
89
+ @digest_cache.values
90
+ end
91
+
92
+ def self.view_context_class(klass)
93
+ @view_context_mutex.synchronize do
94
+ @view_context_class ||= klass.with_empty_template_cache
95
+ end
75
96
  end
76
97
  end
77
98
 
@@ -82,7 +103,7 @@ module ActionView
82
103
  # Calculate the details key. Remove the handlers from calculation to improve performance
83
104
  # since the user cannot modify it explicitly.
84
105
  def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
106
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
107
  end
87
108
 
88
109
  # Temporary skip passing the details_key forward.
@@ -94,9 +115,9 @@ module ActionView
94
115
  end
95
116
 
96
117
  private
97
-
98
118
  def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
119
+ @details = @details.dup if @digest_cache || @details_key
120
+ @digest_cache = nil
100
121
  @details_key = nil
101
122
  @details[key] = value
102
123
  end
@@ -106,20 +127,13 @@ module ActionView
106
127
  module ViewPaths
107
128
  attr_reader :view_paths, :html_fallback_for_js
108
129
 
109
- # Whenever setting view paths, makes a copy so that we can manipulate them in
110
- # instance objects as we wish.
111
- def view_paths=(paths)
112
- @view_paths = ActionView::PathSet.new(Array(paths))
113
- end
114
-
115
130
  def find(name, prefixes = [], partial = false, keys = [], options = {})
116
131
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
132
  end
118
133
  alias :find_template :find
119
134
 
120
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
121
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
122
- end
135
+ alias :find_file :find
136
+ deprecate :find_file
123
137
 
124
138
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
139
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
@@ -138,18 +152,32 @@ module ActionView
138
152
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
153
  # a :file.
140
154
  def with_fallbacks
141
- added_resolvers = 0
142
- self.class.fallbacks.each do |resolver|
143
- next if view_paths.include?(resolver)
144
- view_paths.push(resolver)
145
- added_resolvers += 1
155
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
156
+
157
+ if block_given?
158
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
159
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
160
+ the lookup context returned by `with_fallbacks` instead.
161
+ eowarn
162
+
163
+ begin
164
+ _view_paths = @view_paths
165
+ @view_paths = view_paths
166
+ yield
167
+ ensure
168
+ @view_paths = _view_paths
169
+ end
170
+ else
171
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
172
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
173
  end
151
174
 
152
175
  private
176
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
177
+ # instance objects as we wish.
178
+ def build_view_paths(paths)
179
+ ActionView::PathSet.new(Array(paths))
180
+ end
153
181
 
154
182
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
183
  name, prefixes = normalize_name(name, prefixes)
@@ -163,7 +191,7 @@ module ActionView
163
191
  user_details = @details.merge(options)
164
192
 
165
193
  if @cache
166
- details_key = DetailsKey.get(user_details)
194
+ details_key = DetailsKey.details_cache_key(user_details)
167
195
  else
168
196
  details_key = nil
169
197
  end
@@ -190,7 +218,7 @@ module ActionView
190
218
  end
191
219
 
192
220
  if @cache
193
- [details, DetailsKey.get(details)]
221
+ [details, DetailsKey.details_cache_key(details)]
194
222
  else
195
223
  [details, nil]
196
224
  end
@@ -202,13 +230,13 @@ module ActionView
202
230
  # name instead of the prefix.
203
231
  def normalize_name(name, prefixes)
204
232
  prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
233
+ parts = name.to_s.split("/")
206
234
  parts.shift if parts.first.empty?
207
235
  name = parts.pop
208
236
 
209
237
  return name, prefixes || [""] if parts.empty?
210
238
 
211
- parts = parts.join("/".freeze)
239
+ parts = parts.join("/")
212
240
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
241
 
214
242
  return name, prefixes
@@ -221,16 +249,23 @@ module ActionView
221
249
 
222
250
  def initialize(view_paths, details = {}, prefixes = [])
223
251
  @details_key = nil
252
+ @digest_cache = nil
224
253
  @cache = true
225
254
  @prefixes = prefixes
226
- @rendered_format = nil
227
255
 
228
256
  @details = initialize_details({}, details)
229
- self.view_paths = view_paths
257
+ @view_paths = build_view_paths(view_paths)
230
258
  end
231
259
 
232
260
  def digest_cache
233
- details_key
261
+ @digest_cache ||= DetailsKey.digest_cache(@details)
262
+ end
263
+
264
+ def with_prepended_formats(formats)
265
+ details = @details.dup
266
+ details[:formats] = formats
267
+
268
+ self.class.new(@view_paths, details, @prefixes)
234
269
  end
235
270
 
236
271
  def initialize_details(target, details)
@@ -245,7 +280,15 @@ module ActionView
245
280
  # add :html as fallback to :js.
246
281
  def formats=(values)
247
282
  if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
283
+ values = values.dup
284
+ values.concat(default_formats) if values.delete "*/*"
285
+ values.uniq!
286
+
287
+ invalid_values = (values - Template::Types.symbols)
288
+ unless invalid_values.empty?
289
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
290
+ end
291
+
249
292
  if values == [:js]
250
293
  values << :html
251
294
  @html_fallback_for_js = true
@@ -48,12 +48,11 @@ module ActionView #:nodoc:
48
48
  find_all(*args).first || raise(MissingTemplate.new(self, *args))
49
49
  end
50
50
 
51
- def find_file(path, prefixes = [], *args)
52
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
53
- end
51
+ alias :find_file :find
52
+ deprecate :find_file
54
53
 
55
54
  def find_all(path, prefixes = [], *args)
56
- _find_all path, prefixes, args, false
55
+ _find_all path, prefixes, args
57
56
  end
58
57
 
59
58
  def exists?(path, prefixes, *args)
@@ -70,16 +69,11 @@ module ActionView #:nodoc:
70
69
  end
71
70
 
72
71
  private
73
-
74
- def _find_all(path, prefixes, args, outside_app)
72
+ def _find_all(path, prefixes, args)
75
73
  prefixes = [prefixes] if String === prefixes
76
74
  prefixes.each do |prefix|
77
75
  paths.each do |resolver|
78
- if outside_app
79
- templates = resolver.find_all_anywhere(path, prefix, *args)
80
- else
81
- templates = resolver.find_all(path, prefix, *args)
82
- end
76
+ templates = resolver.find_all(path, prefix, *args)
83
77
  return templates unless templates.empty?
84
78
  end
85
79
  end