actionview 6.0.0.beta1 → 6.0.1.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -3
  3. data/README.rdoc +3 -1
  4. data/lib/action_view.rb +2 -1
  5. data/lib/action_view/base.rb +107 -10
  6. data/lib/action_view/cache_expiry.rb +54 -0
  7. data/lib/action_view/context.rb +0 -5
  8. data/lib/action_view/digestor.rb +8 -17
  9. data/lib/action_view/gem_version.rb +2 -2
  10. data/lib/action_view/helpers/asset_tag_helper.rb +5 -5
  11. data/lib/action_view/helpers/cache_helper.rb +5 -5
  12. data/lib/action_view/helpers/csp_helper.rb +4 -2
  13. data/lib/action_view/helpers/form_helper.rb +2 -2
  14. data/lib/action_view/helpers/form_options_helper.rb +4 -3
  15. data/lib/action_view/helpers/form_tag_helper.rb +5 -2
  16. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  17. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  18. data/lib/action_view/helpers/sanitize_helper.rb +10 -16
  19. data/lib/action_view/helpers/tag_helper.rb +1 -1
  20. data/lib/action_view/helpers/tags/base.rb +1 -1
  21. data/lib/action_view/helpers/translation_helper.rb +3 -3
  22. data/lib/action_view/helpers/url_helper.rb +2 -2
  23. data/lib/action_view/layouts.rb +5 -5
  24. data/lib/action_view/lookup_context.rb +69 -27
  25. data/lib/action_view/path_set.rb +5 -10
  26. data/lib/action_view/railtie.rb +9 -4
  27. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  28. data/lib/action_view/renderer/partial_renderer.rb +66 -55
  29. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +27 -20
  30. data/lib/action_view/renderer/renderer.rb +16 -4
  31. data/lib/action_view/renderer/streaming_template_renderer.rb +4 -4
  32. data/lib/action_view/renderer/template_renderer.rb +24 -18
  33. data/lib/action_view/rendering.rb +46 -27
  34. data/lib/action_view/template.rb +84 -69
  35. data/lib/action_view/template/error.rb +21 -1
  36. data/lib/action_view/template/handlers.rb +27 -1
  37. data/lib/action_view/template/handlers/builder.rb +2 -2
  38. data/lib/action_view/template/handlers/erb.rb +5 -5
  39. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  40. data/lib/action_view/template/handlers/html.rb +1 -1
  41. data/lib/action_view/template/handlers/raw.rb +2 -2
  42. data/lib/action_view/template/html.rb +14 -5
  43. data/lib/action_view/template/inline.rb +22 -0
  44. data/lib/action_view/template/raw_file.rb +28 -0
  45. data/lib/action_view/template/resolver.rb +80 -117
  46. data/lib/action_view/template/sources.rb +13 -0
  47. data/lib/action_view/template/sources/file.rb +17 -0
  48. data/lib/action_view/template/text.rb +5 -3
  49. data/lib/action_view/testing/resolvers.rb +33 -20
  50. data/lib/action_view/unbound_template.rb +32 -0
  51. data/lib/action_view/view_paths.rb +25 -1
  52. data/lib/assets/compiled/rails-ujs.js +21 -12
  53. metadata +25 -16
@@ -9,8 +9,8 @@ module ActionView
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "beta1"
12
+ TINY = 1
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -329,14 +329,14 @@ module ActionView
329
329
  # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
330
330
  # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
331
331
  #
332
- # Active Storage (images that are uploaded by the users of your app):
332
+ # Active Storage blobs (images that are uploaded by the users of your app):
333
333
  #
334
334
  # image_tag(user.avatar)
335
335
  # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
336
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]))
337
- # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
338
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100')
339
- # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
336
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
337
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
338
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
339
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
340
340
  def image_tag(source, options = {})
341
341
  options = options.symbolize_keys
342
342
  check_for_image_tag_errors(options)
@@ -216,13 +216,13 @@ module ActionView
216
216
  end
217
217
  end
218
218
 
219
- def digest_path_from_virtual(virtual_path) # :nodoc:
220
- digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies)
219
+ def digest_path_from_template(template) # :nodoc:
220
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
221
221
 
222
222
  if digest.present?
223
- "#{virtual_path}:#{digest}"
223
+ "#{template.virtual_path}:#{digest}"
224
224
  else
225
- virtual_path
225
+ template.virtual_path
226
226
  end
227
227
  end
228
228
 
@@ -234,7 +234,7 @@ module ActionView
234
234
  if virtual_path || digest_path
235
235
  name = controller.url_for(name).split("://").last if name.is_a?(Hash)
236
236
 
237
- digest_path ||= digest_path_from_virtual(virtual_path)
237
+ digest_path ||= digest_path_from_template(@current_template)
238
238
 
239
239
  [ digest_path, name ]
240
240
  else
@@ -14,9 +14,11 @@ module ActionView
14
14
  # This is used by the Rails UJS helper to create dynamically
15
15
  # loaded inline <script> elements.
16
16
  #
17
- def csp_meta_tag
17
+ def csp_meta_tag(**options)
18
18
  if content_security_policy?
19
- tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
19
+ options[:name] = "csp-nonce"
20
+ options[:content] = content_security_policy_nonce
21
+ tag("meta", options)
20
22
  end
21
23
  end
22
24
  end
@@ -739,7 +739,7 @@ module ActionView
739
739
  # def labelled_form_with(**options, &block)
740
740
  # form_with(**options.merge(builder: LabellingFormBuilder), &block)
741
741
  # end
742
- def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
742
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
743
743
  options[:allow_method_names_outside_object] = true
744
744
  options[:skip_default_ids] = !form_with_generates_ids
745
745
 
@@ -752,7 +752,7 @@ module ActionView
752
752
 
753
753
  if block_given?
754
754
  builder = instantiate_builder(scope, model, options)
755
- output = capture(builder, &Proc.new)
755
+ output = capture(builder, &block)
756
756
  options[:multipart] ||= builder.multipart?
757
757
 
758
758
  html_options = html_options_for_form_with(url, model, options)
@@ -566,9 +566,10 @@ module ActionView
566
566
  # an ActiveSupport::TimeZone.
567
567
  #
568
568
  # By default, +model+ is the ActiveSupport::TimeZone constant (which can
569
- # be obtained in Active Record as a value object). The only requirement
570
- # is that the +model+ parameter be an object that responds to +all+, and
571
- # returns an array of objects that represent time zones.
569
+ # be obtained in Active Record as a value object). The +model+ parameter
570
+ # must respond to +all+ and return an array of objects that represent time
571
+ # zones; each object must respond to +name+. If a Regexp is given it will
572
+ # attempt to match the zones using the <code>=~<code> operator.
572
573
  #
573
574
  # NOTE: Only the option tags are returned, you have to wrap this call in
574
575
  # a regular HTML select tag.
@@ -24,7 +24,7 @@ module ActionView
24
24
 
25
25
  mattr_accessor :default_enforce_utf8, default: true
26
26
 
27
- # Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
27
+ # Starts a form tag that points the action to a URL configured with <tt>url_for_options</tt> just like
28
28
  # ActionController::Base#url_for. The method for the form defaults to POST.
29
29
  #
30
30
  # ==== Options
@@ -137,7 +137,8 @@ module ActionView
137
137
  html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
138
138
 
139
139
  if options.include?(:include_blank)
140
- include_blank = options.delete(:include_blank)
140
+ include_blank = options[:include_blank]
141
+ options = options.except(:include_blank)
141
142
  options_for_blank_options_tag = { value: "" }
142
143
 
143
144
  if include_blank == true
@@ -165,6 +166,8 @@ module ActionView
165
166
  # * <tt>:size</tt> - The number of visible characters that will fit in the input.
166
167
  # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
167
168
  # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
169
+ # If set to true, use a translation is found in the current I18n locale
170
+ # (through helpers.placeholders.<modelname>.<attribute>).
168
171
  # * Any other key creates standard HTML attributes for the tag.
169
172
  #
170
173
  # ==== Examples
@@ -38,7 +38,7 @@ module ActionView #:nodoc:
38
38
 
39
39
  # Converts the array to a comma-separated sentence where the last element is
40
40
  # joined by the connector word. This is the html_safe-aware version of
41
- # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
41
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
42
42
  #
43
43
  def to_sentence(array, options = {})
44
44
  options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
@@ -27,10 +27,12 @@ module ActionView
27
27
  def render(options = {}, locals = {}, &block)
28
28
  case options
29
29
  when Hash
30
- if block_given?
31
- view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
32
- else
33
- view_renderer.render(self, options)
30
+ in_rendering_context(options) do |renderer|
31
+ if block_given?
32
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
33
+ else
34
+ view_renderer.render(self, options)
35
+ end
34
36
  end
35
37
  else
36
38
  view_renderer.render_partial(self, partial: options, locals: locals, &block)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/object/try"
4
3
  require "rails-html-sanitizer"
5
4
 
6
5
  module ActionView
@@ -17,7 +16,7 @@ module ActionView
17
16
  # ASCII, and hex character references to work around these protocol filters.
18
17
  # All special characters will be escaped.
19
18
  #
20
- # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
21
20
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
22
21
  #
23
22
  # Custom sanitization rules can also be provided.
@@ -80,12 +79,12 @@ module ActionView
80
79
  # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
81
80
  # config.action_view.sanitized_allowed_attributes = ['href', 'title']
82
81
  def sanitize(html, options = {})
83
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
84
83
  end
85
84
 
86
85
  # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
87
86
  def sanitize_css(style)
88
- self.class.white_list_sanitizer.sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
89
88
  end
90
89
 
91
90
  # Strips all HTML tags from +html+, including comments and special characters.
@@ -123,20 +122,18 @@ module ActionView
123
122
  end
124
123
 
125
124
  module ClassMethods #:nodoc:
126
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
127
126
 
128
- # Vendors the full, link and white list sanitizers.
129
- # Provided strictly for compatibility and can be removed in Rails 6.
130
127
  def sanitizer_vendor
131
128
  Rails::Html::Sanitizer
132
129
  end
133
130
 
134
131
  def sanitized_allowed_tags
135
- sanitizer_vendor.white_list_sanitizer.allowed_tags
132
+ safe_list_sanitizer.allowed_tags
136
133
  end
137
134
 
138
135
  def sanitized_allowed_attributes
139
- sanitizer_vendor.white_list_sanitizer.allowed_attributes
136
+ safe_list_sanitizer.allowed_attributes
140
137
  end
141
138
 
142
139
  # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
@@ -145,7 +142,6 @@ module ActionView
145
142
  # class Application < Rails::Application
146
143
  # config.action_view.full_sanitizer = MySpecialSanitizer.new
147
144
  # end
148
- #
149
145
  def full_sanitizer
150
146
  @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
151
147
  end
@@ -156,20 +152,18 @@ module ActionView
156
152
  # class Application < Rails::Application
157
153
  # config.action_view.link_sanitizer = MySpecialSanitizer.new
158
154
  # end
159
- #
160
155
  def link_sanitizer
161
156
  @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
162
157
  end
163
158
 
164
- # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
159
+ # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
165
160
  # Replace with any object that responds to +sanitize+.
166
161
  #
167
162
  # class Application < Rails::Application
168
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
169
164
  # end
170
- #
171
- def white_list_sanitizer
172
- @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
173
167
  end
174
168
  end
175
169
  end
@@ -88,7 +88,7 @@ module ActionView
88
88
  if value.is_a?(Array)
89
89
  value = escape ? safe_join(value, " ") : value.join(" ")
90
90
  else
91
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
91
+ value = escape ? ERB::Util.unwrapped_html_escape(value).dup : value.to_s.dup
92
92
  end
93
93
  value.gsub!('"', "&quot;")
94
94
  %(#{key}="#{value}")
@@ -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:]]]/, "").downcase
142
142
  end
143
143
 
144
144
  def select_content_tag(option_tags, options, html_options)
@@ -60,7 +60,7 @@ module ActionView
60
60
  def translate(key, options = {})
61
61
  options = options.dup
62
62
  if options.has_key?(:default)
63
- remaining_defaults = Array(options.delete(:default)).compact
63
+ remaining_defaults = Array.wrap(options.delete(:default)).compact
64
64
  options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
65
65
  end
66
66
 
@@ -114,7 +114,7 @@ module ActionView
114
114
 
115
115
  # Delegates to <tt>I18n.localize</tt> with no additional functionality.
116
116
  #
117
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
117
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
118
118
  # for more information.
119
119
  def localize(*args)
120
120
  I18n.localize(*args)
@@ -138,7 +138,7 @@ module ActionView
138
138
  end
139
139
 
140
140
  def html_safe_translation_key?(key)
141
- /(\b|_|\.)html$/.match?(key.to_s)
141
+ /(?:_|\b)html\z/.match?(key.to_s)
142
142
  end
143
143
  end
144
144
  end
@@ -253,7 +253,7 @@ module ActionView
253
253
  # # <input value="New" type="submit" />
254
254
  # # </form>"
255
255
  #
256
- # <%= button_to "New", new_articles_path %>
256
+ # <%= button_to "New", new_article_path %>
257
257
  # # => "<form method="post" action="/articles/new" class="button_to">
258
258
  # # <input value="New" type="submit" />
259
259
  # # </form>"
@@ -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
@@ -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
@@ -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
 
@@ -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,20 +125,13 @@ 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
118
131
  alias :find_template :find
119
132
 
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
133
+ alias :find_file :find
134
+ deprecate :find_file
123
135
 
124
136
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
137
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
@@ -138,19 +150,34 @@ module ActionView
138
150
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
151
  # a :file.
140
152
  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
153
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
154
+
155
+ if block_given?
156
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
157
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
158
+ the lookup context returned by `with_fallbacks` instead.
159
+ eowarn
160
+
161
+ begin
162
+ _view_paths = @view_paths
163
+ @view_paths = view_paths
164
+ yield
165
+ ensure
166
+ @view_paths = _view_paths
167
+ end
168
+ else
169
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
170
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
171
  end
151
172
 
152
173
  private
153
174
 
175
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
176
+ # instance objects as we wish.
177
+ def build_view_paths(paths)
178
+ ActionView::PathSet.new(Array(paths))
179
+ end
180
+
154
181
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
182
  name, prefixes = normalize_name(name, prefixes)
156
183
  details, details_key = detail_args_for(details_options)
@@ -163,7 +190,7 @@ module ActionView
163
190
  user_details = @details.merge(options)
164
191
 
165
192
  if @cache
166
- details_key = DetailsKey.get(user_details)
193
+ details_key = DetailsKey.details_cache_key(user_details)
167
194
  else
168
195
  details_key = nil
169
196
  end
@@ -190,7 +217,7 @@ module ActionView
190
217
  end
191
218
 
192
219
  if @cache
193
- [details, DetailsKey.get(details)]
220
+ [details, DetailsKey.details_cache_key(details)]
194
221
  else
195
222
  [details, nil]
196
223
  end
@@ -221,16 +248,23 @@ module ActionView
221
248
 
222
249
  def initialize(view_paths, details = {}, prefixes = [])
223
250
  @details_key = nil
251
+ @digest_cache = nil
224
252
  @cache = true
225
253
  @prefixes = prefixes
226
- @rendered_format = nil
227
254
 
228
255
  @details = initialize_details({}, details)
229
- self.view_paths = view_paths
256
+ @view_paths = build_view_paths(view_paths)
230
257
  end
231
258
 
232
259
  def digest_cache
233
- details_key
260
+ @digest_cache ||= DetailsKey.digest_cache(@details)
261
+ end
262
+
263
+ def with_prepended_formats(formats)
264
+ details = @details.dup
265
+ details[:formats] = formats
266
+
267
+ self.class.new(@view_paths, details, @prefixes)
234
268
  end
235
269
 
236
270
  def initialize_details(target, details)
@@ -245,7 +279,15 @@ module ActionView
245
279
  # add :html as fallback to :js.
246
280
  def formats=(values)
247
281
  if values
282
+ values = values.dup
248
283
  values.concat(default_formats) if values.delete "*/*"
284
+ values.uniq!
285
+
286
+ invalid_values = (values - Template::Types.symbols)
287
+ unless invalid_values.empty?
288
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
289
+ end
290
+
249
291
  if values == [:js]
250
292
  values << :html
251
293
  @html_fallback_for_js = true