actionview 5.2.8.1 → 6.0.0.beta2

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -152
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view/base.rb +107 -10
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/context.rb +5 -9
  8. data/lib/action_view/digestor.rb +8 -11
  9. data/lib/action_view/file_template.rb +33 -0
  10. data/lib/action_view/gem_version.rb +4 -4
  11. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  12. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  13. data/lib/action_view/helpers/cache_helper.rb +18 -10
  14. data/lib/action_view/helpers/capture_helper.rb +4 -0
  15. data/lib/action_view/helpers/csp_helper.rb +4 -2
  16. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  17. data/lib/action_view/helpers/date_helper.rb +69 -25
  18. data/lib/action_view/helpers/form_helper.rb +240 -8
  19. data/lib/action_view/helpers/form_options_helper.rb +23 -15
  20. data/lib/action_view/helpers/form_tag_helper.rb +9 -9
  21. data/lib/action_view/helpers/javascript_helper.rb +10 -11
  22. data/lib/action_view/helpers/number_helper.rb +5 -0
  23. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  24. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  25. data/lib/action_view/helpers/tag_helper.rb +13 -43
  26. data/lib/action_view/helpers/tags/base.rb +9 -5
  27. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  28. data/lib/action_view/helpers/tags/translator.rb +1 -6
  29. data/lib/action_view/helpers/text_helper.rb +3 -3
  30. data/lib/action_view/helpers/translation_helper.rb +12 -19
  31. data/lib/action_view/helpers/url_helper.rb +14 -14
  32. data/lib/action_view/helpers.rb +0 -2
  33. data/lib/action_view/layouts.rb +5 -5
  34. data/lib/action_view/log_subscriber.rb +6 -6
  35. data/lib/action_view/lookup_context.rb +63 -28
  36. data/lib/action_view/railtie.rb +23 -0
  37. data/lib/action_view/record_identifier.rb +2 -2
  38. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  39. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +49 -10
  40. data/lib/action_view/renderer/partial_renderer.rb +66 -52
  41. data/lib/action_view/renderer/renderer.rb +16 -4
  42. data/lib/action_view/renderer/streaming_template_renderer.rb +4 -4
  43. data/lib/action_view/renderer/template_renderer.rb +18 -18
  44. data/lib/action_view/rendering.rb +49 -30
  45. data/lib/action_view/routing_url_for.rb +12 -11
  46. data/lib/action_view/template/handlers/builder.rb +2 -2
  47. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  48. data/lib/action_view/template/handlers/erb.rb +17 -7
  49. data/lib/action_view/template/handlers/html.rb +1 -1
  50. data/lib/action_view/template/handlers/raw.rb +2 -2
  51. data/lib/action_view/template/handlers.rb +27 -1
  52. data/lib/action_view/template/html.rb +14 -5
  53. data/lib/action_view/template/inline.rb +22 -0
  54. data/lib/action_view/template/resolver.rb +70 -23
  55. data/lib/action_view/template/text.rb +5 -3
  56. data/lib/action_view/template.rb +75 -36
  57. data/lib/action_view/test_case.rb +1 -1
  58. data/lib/action_view/testing/resolvers.rb +7 -5
  59. data/lib/action_view/view_paths.rb +25 -1
  60. data/lib/action_view.rb +2 -2
  61. data/lib/assets/compiled/rails-ujs.js +39 -22
  62. metadata +19 -18
  63. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -41,31 +41,24 @@ module ActionView
41
41
  @view_context = view_context
42
42
  end
43
43
 
44
- def tag_string(name, content = nil, **options, &block)
45
- escape = handle_deprecated_escape_options(options)
44
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
46
45
  content = @view_context.capture(self, &block) if block_given?
47
-
48
46
  if VOID_ELEMENTS.include?(name) && content.nil?
49
- "<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe
47
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
50
48
  else
51
- content_tag_string(name.to_s.dasherize, content || "", options, escape)
49
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
52
50
  end
53
51
  end
54
52
 
55
53
  def content_tag_string(name, content, options, escape = true)
56
54
  tag_options = tag_options(options, escape) if options
57
-
58
- if escape
59
- name = ERB::Util.xml_name_escape(name)
60
- content = ERB::Util.unwrapped_html_escape(content)
61
- end
62
-
55
+ content = ERB::Util.unwrapped_html_escape(content) if escape
63
56
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
64
57
  end
65
58
 
66
59
  def tag_options(options, escape = true)
67
60
  return if options.blank?
68
- output = "".dup
61
+ output = +""
69
62
  sep = " "
70
63
  options.each_pair do |key, value|
71
64
  if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
@@ -92,14 +85,13 @@ module ActionView
92
85
  end
93
86
 
94
87
  def tag_option(key, value, escape)
95
- key = ERB::Util.xml_name_escape(key) if escape
96
-
97
88
  if value.is_a?(Array)
98
- value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
89
+ value = escape ? safe_join(value, " ") : value.join(" ")
99
90
  else
100
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
91
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
101
92
  end
102
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
93
+ value.gsub!('"', "&quot;")
94
+ %(#{key}="#{value}")
103
95
  end
104
96
 
105
97
  private
@@ -115,29 +107,8 @@ module ActionView
115
107
  true
116
108
  end
117
109
 
118
- def handle_deprecated_escape_options(options)
119
- # The option :escape_attributes has been merged into the options hash to be
120
- # able to warn when it is used, so we need to handle default values here.
121
- escape_option_provided = options.has_key?(:escape)
122
- escape_attributes_option_provided = options.has_key?(:escape_attributes)
123
-
124
- if escape_attributes_option_provided
125
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
126
- Use of the option :escape_attributes is deprecated. It currently \
127
- escapes both names and values of tags and attributes and it is \
128
- equivalent to :escape. If any of them are enabled, the escaping \
129
- is fully enabled.
130
- MSG
131
- end
132
-
133
- return true unless escape_option_provided || escape_attributes_option_provided
134
- escape_option = options.delete(:escape)
135
- escape_attributes_option = options.delete(:escape_attributes)
136
- escape_option || escape_attributes_option
137
- end
138
-
139
- def method_missing(called, *args, **options, &block)
140
- tag_string(called, *args, **options, &block)
110
+ def method_missing(called, *args, &block)
111
+ tag_string(called, *args, &block)
141
112
  end
142
113
  end
143
114
 
@@ -257,16 +228,15 @@ module ActionView
257
228
  # tag("img", src: "open & shut.png")
258
229
  # # => <img src="open &amp; shut.png" />
259
230
  #
260
- # tag("img", {src: "open &amp; shut.png"}, false, false)
231
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
261
232
  # # => <img src="open &amp; shut.png" />
262
233
  #
263
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
234
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
264
235
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
265
236
  def tag(name = nil, options = nil, open = false, escape = true)
266
237
  if name.nil?
267
238
  tag_builder
268
239
  else
269
- name = ERB::Util.xml_name_escape(name) if escape
270
240
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
271
241
  end
272
242
  end
@@ -109,11 +109,11 @@ module ActionView
109
109
  # a little duplication to construct less strings
110
110
  case
111
111
  when @object_name.empty?
112
- "#{sanitized_method_name}#{"[]" if multiple}"
112
+ "#{sanitized_method_name}#{multiple ? "[]" : ""}"
113
113
  when index
114
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
114
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
115
115
  else
116
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
116
+ "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
117
117
  end
118
118
  end
119
119
 
@@ -138,7 +138,7 @@ module ActionView
138
138
  end
139
139
 
140
140
  def sanitized_value(value)
141
- value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s
141
+ value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s
142
142
  end
143
143
 
144
144
  def select_content_tag(option_tags, options, html_options)
@@ -170,7 +170,11 @@ module ActionView
170
170
  option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags
171
171
  end
172
172
  if value.blank? && options[:prompt]
173
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags
173
+ tag_options = { value: "" }.tap do |prompt_opts|
174
+ prompt_opts[:disabled] = true if options[:disabled] == ""
175
+ prompt_opts[:selected] = true if options[:selected] == ""
176
+ end
177
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
174
178
  end
175
179
  option_tags
176
180
  end
@@ -15,7 +15,7 @@ module ActionView
15
15
 
16
16
  def validate_color_string(string)
17
17
  regex = /#[0-9a-fA-F]{6}/
18
- if regex.match(string)
18
+ if regex.match?(string)
19
19
  string.downcase
20
20
  else
21
21
  "#000000"
@@ -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
@@ -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
 
@@ -59,11 +59,9 @@ module ActionView
59
59
  # they can provide HTML values for.
60
60
  def translate(key, options = {})
61
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
62
+ if options.has_key?(:default)
63
+ remaining_defaults = Array(options.delete(:default)).compact
64
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
67
65
  end
68
66
 
69
67
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
@@ -79,19 +77,14 @@ module ActionView
79
77
 
80
78
  if html_safe_translation_key?(key)
81
79
  html_safe_options = options.dup
82
-
83
80
  options.except(*I18n::RESERVED_KEYS).each do |name, value|
84
81
  unless name == :count && value.is_a?(Numeric)
85
82
  html_safe_options[name] = ERB::Util.html_escape(value.to_s)
86
83
  end
87
84
  end
88
-
89
- html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
90
-
91
85
  translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
92
-
93
- if translation.equal?(MISSING_TRANSLATION)
94
- options[:default].first
86
+ if translation.respond_to?(:map)
87
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
95
88
  else
96
89
  translation.respond_to?(:html_safe) ? translation.html_safe : translation
97
90
  end
@@ -105,7 +98,7 @@ module ActionView
105
98
  raise e if raise_error
106
99
 
107
100
  keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
108
- title = "translation missing: #{keys.join('.')}".dup
101
+ title = +"translation missing: #{keys.join('.')}"
109
102
 
110
103
  interpolations = options.except(:default, :scope)
111
104
  if interpolations.any?
@@ -129,13 +122,13 @@ module ActionView
129
122
  alias :l :localize
130
123
 
131
124
  private
132
- MISSING_TRANSLATION = Object.new
133
- private_constant :MISSING_TRANSLATION
134
-
135
125
  def scope_key_by_partial(key)
136
- if key.to_s.first == "."
126
+ stringified_key = key.to_s
127
+ if stringified_key.first == "."
137
128
  if @virtual_path
138
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
129
+ @_scope_key_by_partial_cache ||= {}
130
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
131
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
139
132
  else
140
133
  raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
141
134
  end
@@ -145,7 +138,7 @@ module ActionView
145
138
  end
146
139
 
147
140
  def html_safe_translation_key?(key)
148
- /(\b|_|\.)html$/.match?(key.to_s)
141
+ /([_.]|\b)html\z/.match?(key.to_s)
149
142
  end
150
143
  end
151
144
  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
@@ -253,7 +253,7 @@ module ActionView
253
253
  # # <input value="New" type="submit" />
254
254
  # # </form>"
255
255
  #
256
- # <%= button_to "New", new_article_path %>
256
+ # <%= button_to "New", new_articles_path %>
257
257
  # # => "<form method="post" action="/articles/new" class="button_to">
258
258
  # # <input value="New" type="submit" />
259
259
  # # </form>"
@@ -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+.
@@ -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
@@ -322,7 +322,7 @@ module ActionView
322
322
  end
323
323
 
324
324
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
325
- def _layout(formats)
325
+ def _layout(lookup_context, formats)
326
326
  if _conditional_layout?
327
327
  #{layout_definition}
328
328
  else
@@ -388,8 +388,8 @@ module ActionView
388
388
  case name
389
389
  when String then _normalize_layout(name)
390
390
  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) }
391
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
392
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
393
393
  when false, nil then nil
394
394
  else
395
395
  raise ArgumentError,
@@ -411,9 +411,9 @@ module ActionView
411
411
  #
412
412
  # ==== Returns
413
413
  # * <tt>template</tt> - The template object for the default layout (or +nil+)
414
- def _default_layout(formats, require_layout = false)
414
+ def _default_layout(lookup_context, formats, require_layout = false)
415
415
  begin
416
- value = _layout(formats) if action_has_layout?
416
+ value = _layout(lookup_context, formats) if action_has_layout?
417
417
  rescue NameError => e
418
418
  raise e, "Could not render layout: #{e.message}"
419
419
  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
 
@@ -85,7 +85,7 @@ module ActionView
85
85
 
86
86
  def log_rendering_start(payload)
87
87
  info do
88
- message = " Rendering #{from_rails_root(payload[:identifier])}".dup
88
+ message = +" Rendering #{from_rails_root(payload[:identifier])}"
89
89
  message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
90
90
  message
91
91
  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,36 @@ module ActionView
57
60
  alias :eql? :equal?
58
61
 
59
62
  @details_keys = Concurrent::Map.new
63
+ @digest_cache = Concurrent::Map.new
60
64
 
61
- def self.get(details)
65
+ def self.digest_cache(details)
66
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
67
+ end
68
+
69
+ def self.details_cache_key(details)
62
70
  if details[:formats]
63
71
  details = details.dup
64
72
  details[:formats] &= Template::Types.symbols
65
73
  end
66
- @details_keys[details] ||= Concurrent::Map.new
74
+ @details_keys[details] ||= Object.new
67
75
  end
68
76
 
69
77
  def self.clear
78
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
79
+ path_set.each(&:clear_cache)
80
+ end
81
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
82
+ @view_context_class = nil
70
83
  @details_keys.clear
84
+ @digest_cache.clear
71
85
  end
72
86
 
73
87
  def self.digest_caches
74
- @details_keys.values
88
+ @digest_cache.values
89
+ end
90
+
91
+ def self.view_context_class(klass)
92
+ @view_context_class ||= klass.with_empty_template_cache
75
93
  end
76
94
  end
77
95
 
@@ -82,7 +100,7 @@ module ActionView
82
100
  # Calculate the details key. Remove the handlers from calculation to improve performance
83
101
  # since the user cannot modify it explicitly.
84
102
  def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
103
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
104
  end
87
105
 
88
106
  # Temporary skip passing the details_key forward.
@@ -96,7 +114,8 @@ module ActionView
96
114
  private
97
115
 
98
116
  def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
117
+ @details = @details.dup if @digest_cache || @details_key
118
+ @digest_cache = nil
100
119
  @details_key = nil
101
120
  @details[key] = value
102
121
  end
@@ -106,12 +125,6 @@ module ActionView
106
125
  module ViewPaths
107
126
  attr_reader :view_paths, :html_fallback_for_js
108
127
 
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
128
  def find(name, prefixes = [], partial = false, keys = [], options = {})
116
129
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
130
  end
@@ -138,19 +151,34 @@ module ActionView
138
151
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
152
  # a :file.
140
153
  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
154
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
155
+
156
+ if block_given?
157
+ ActiveSupport::Deprecation.warn <<~eowarn
158
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
159
+ the lookup context returned by `with_fallbacks` instead.
160
+ eowarn
161
+
162
+ begin
163
+ _view_paths = @view_paths
164
+ @view_paths = view_paths
165
+ yield
166
+ ensure
167
+ @view_paths = _view_paths
168
+ end
169
+ else
170
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
171
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
172
  end
151
173
 
152
174
  private
153
175
 
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
181
+
154
182
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
183
  name, prefixes = normalize_name(name, prefixes)
156
184
  details, details_key = detail_args_for(details_options)
@@ -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,7 @@ 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.concat(default_formats) if values.delete "*/*"
249
284
  if values == [:js]
250
285
  values << :html
251
286
  @html_fallback_for_js = true