actionview 7.0.8 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -341
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +693 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +33 -12
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +40 -43
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/deprecator.rb +7 -0
  12. data/lib/action_view/digestor.rb +1 -1
  13. data/lib/action_view/gem_version.rb +3 -3
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
  16. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  17. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  18. data/lib/action_view/helpers/cache_helper.rb +3 -9
  19. data/lib/action_view/helpers/capture_helper.rb +24 -10
  20. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  21. data/lib/action_view/helpers/controller_helper.rb +6 -0
  22. data/lib/action_view/helpers/csp_helper.rb +2 -2
  23. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  24. data/lib/action_view/helpers/date_helper.rb +17 -19
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +44 -19
  27. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  28. data/lib/action_view/helpers/form_tag_helper.rb +43 -9
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +2 -1
  31. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  32. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  33. data/lib/action_view/helpers/sanitize_helper.rb +33 -14
  34. data/lib/action_view/helpers/tag_helper.rb +5 -27
  35. data/lib/action_view/helpers/tags/base.rb +11 -52
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  38. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  41. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  43. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/select.rb +3 -0
  46. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  47. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  48. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  49. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  51. data/lib/action_view/helpers/tags.rb +2 -0
  52. data/lib/action_view/helpers/text_helper.rb +32 -16
  53. data/lib/action_view/helpers/translation_helper.rb +3 -3
  54. data/lib/action_view/helpers/url_helper.rb +41 -14
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +6 -4
  57. data/lib/action_view/log_subscriber.rb +49 -32
  58. data/lib/action_view/lookup_context.rb +29 -13
  59. data/lib/action_view/path_registry.rb +57 -0
  60. data/lib/action_view/path_set.rb +13 -14
  61. data/lib/action_view/railtie.rb +26 -3
  62. data/lib/action_view/record_identifier.rb +15 -8
  63. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  64. data/lib/action_view/renderer/collection_renderer.rb +9 -1
  65. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  66. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  67. data/lib/action_view/renderer/renderer.rb +2 -0
  68. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  69. data/lib/action_view/renderer/template_renderer.rb +3 -2
  70. data/lib/action_view/rendering.rb +22 -4
  71. data/lib/action_view/ripper_ast_parser.rb +6 -6
  72. data/lib/action_view/template/error.rb +14 -1
  73. data/lib/action_view/template/handlers/builder.rb +4 -4
  74. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  75. data/lib/action_view/template/handlers/erb.rb +73 -1
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/html.rb +1 -1
  78. data/lib/action_view/template/raw_file.rb +1 -1
  79. data/lib/action_view/template/renderable.rb +1 -1
  80. data/lib/action_view/template/resolver.rb +10 -2
  81. data/lib/action_view/template/text.rb +1 -1
  82. data/lib/action_view/template/types.rb +25 -34
  83. data/lib/action_view/template.rb +227 -53
  84. data/lib/action_view/template_path.rb +2 -0
  85. data/lib/action_view/test_case.rb +174 -21
  86. data/lib/action_view/unbound_template.rb +15 -5
  87. data/lib/action_view/version.rb +1 -1
  88. data/lib/action_view/view_paths.rb +15 -24
  89. data/lib/action_view.rb +4 -1
  90. metadata +23 -23
@@ -3,11 +3,13 @@
3
3
  require "active_support/core_ext/array/access"
4
4
  require "active_support/core_ext/hash/keys"
5
5
  require "active_support/core_ext/string/output_safety"
6
+ require "action_view/helpers/content_exfiltration_prevention_helper"
6
7
  require "action_view/helpers/tag_helper"
7
8
 
8
9
  module ActionView
9
- # = Action View URL Helpers
10
10
  module Helpers # :nodoc:
11
+ # = Action View URL \Helpers
12
+ #
11
13
  # Provides a set of methods for making links and getting URLs that
12
14
  # depend on the routing subsystem (see ActionDispatch::Routing).
13
15
  # This allows you to use the same format for links in views
@@ -22,6 +24,7 @@ module ActionView
22
24
  extend ActiveSupport::Concern
23
25
 
24
26
  include TagHelper
27
+ include ContentExfiltrationPreventionHelper
25
28
 
26
29
  module ClassMethods
27
30
  def _url_for_modules
@@ -93,7 +96,7 @@ module ActionView
93
96
  # ==== Examples
94
97
  #
95
98
  # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
96
- # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
99
+ # and newer RESTful routes. Current \Rails style favors RESTful routes whenever possible, so base
97
100
  # your application on resources and use
98
101
  #
99
102
  # link_to "Profile", profile_path(@profile)
@@ -170,9 +173,31 @@ module ActionView
170
173
  # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
171
174
  # # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
172
175
  #
173
- # ==== Deprecated: Rails UJS Attributes
176
+ # ==== Turbo
174
177
  #
175
- # Prior to Rails 7, Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following Rails 7,
178
+ # Rails 7 ships with Turbo enabled by default. Turbo provides the following +:data+ options:
179
+ #
180
+ # * <tt>turbo_method: symbol of HTTP verb</tt> - Performs a Turbo link visit
181
+ # with the given HTTP verb. Forms are recommended when performing non-+GET+ requests.
182
+ # Only use <tt>data-turbo-method</tt> where a form is not possible.
183
+ #
184
+ # * <tt>turbo_confirm: "question?"</tt> - Adds a confirmation dialog to the link with the
185
+ # given value.
186
+ #
187
+ # {Consult the Turbo Handbook for more information on the options
188
+ # above.}[https://turbo.hotwired.dev/handbook/drive#performing-visits-with-a-different-method]
189
+ #
190
+ # ===== \Examples
191
+ #
192
+ # link_to "Delete profile", @profile, data: { turbo_method: :delete }
193
+ # # => <a href="/profiles/1" data-turbo-method="delete">Delete profile</a>
194
+ #
195
+ # link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" }
196
+ # # => <a href="https://rubyonrails.org/" data-turbo-confirm="Are you sure?">Visit Other Site</a>
197
+ #
198
+ # ==== Deprecated: \Rails UJS Attributes
199
+ #
200
+ # Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
176
201
  # this library is no longer on by default. This library integrated with the following options:
177
202
  #
178
203
  # * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
@@ -198,7 +223,7 @@ module ActionView
198
223
  # * <tt>:disable_with</tt> - Value of this parameter will be used as the
199
224
  # name for a disabled version of the link.
200
225
  #
201
- # ===== Rails UJS Examples
226
+ # ===== \Rails UJS Examples
202
227
  #
203
228
  # link_to "Remove Profile", profile_path(@profile), method: :delete
204
229
  # # => <a href="/profiles/1" rel="nofollow" data-method="delete">Remove Profile</a>
@@ -221,9 +246,6 @@ module ActionView
221
246
  # Generates a form containing a single button that submits to the URL created
222
247
  # by the set of +options+. This is the safest method to ensure links that
223
248
  # cause changes to your data are not triggered by search bots or accelerators.
224
- # If the HTML button does not work with your layout, you can also consider
225
- # using the +link_to+ method with the <tt>:method</tt> modifier as described in
226
- # the +link_to+ documentation.
227
249
  #
228
250
  # You can control the form and button behavior with +html_options+. Most
229
251
  # values in +html_options+ are passed through to the button element. For
@@ -237,6 +259,10 @@ module ActionView
237
259
  # The form submits a POST request by default. You can specify a different
238
260
  # HTTP verb via the +:method+ option within +html_options+.
239
261
  #
262
+ # If the HTML button generated from +button_to+ does not work with your layout, you can
263
+ # consider using the +link_to+ method with the +data-turbo-method+
264
+ # attribute as described in the +link_to+ documentation.
265
+ #
240
266
  # ==== Options
241
267
  # The +options+ hash accepts the same options as +url_for+. To generate a
242
268
  # <tt><form></tt> element without an <tt>[action]</tt> attribute, pass
@@ -302,9 +328,9 @@ module ActionView
302
328
  # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
303
329
  # # </form>"
304
330
  #
305
- # ==== Deprecated: Rails UJS Attributes
331
+ # ==== Deprecated: \Rails UJS Attributes
306
332
  #
307
- # Prior to Rails 7, Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following Rails 7,
333
+ # Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
308
334
  # this library is no longer on by default. This library integrated with the following options:
309
335
  #
310
336
  # * <tt>:remote</tt> - If set to true, will allow <tt>@rails/ujs</tt> to control the
@@ -320,7 +346,7 @@ module ActionView
320
346
  # used as the value for a disabled version of the submit
321
347
  # button when the form is submitted.
322
348
  #
323
- # ===== Rails UJS Examples
349
+ # ===== \Rails UJS Examples
324
350
  #
325
351
  # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
326
352
  # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
@@ -380,7 +406,8 @@ module ActionView
380
406
  autocomplete: "off")
381
407
  end
382
408
  end
383
- content_tag("form", inner_tags, form_options)
409
+ html = content_tag("form", inner_tags, form_options)
410
+ prevent_content_exfiltration(html)
384
411
  end
385
412
 
386
413
  # Creates a link tag of the given +name+ using a URL created by the set of
@@ -495,7 +522,7 @@ module ActionView
495
522
  # * <tt>:reply_to</tt> - Preset the +Reply-To+ field of the email.
496
523
  #
497
524
  # ==== Obfuscation
498
- # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
525
+ # Prior to \Rails 4.0, +mail_to+ provided options for encoding the address
499
526
  # in order to hinder email harvesters. To take advantage of these options,
500
527
  # install the +actionview-encoded_mail_to+ gem.
501
528
  #
@@ -595,7 +622,7 @@ module ActionView
595
622
  # We ignore any extra parameters in the request_uri if the
596
623
  # submitted URL doesn't have any either. This lets the function
597
624
  # work with things like ?order=asc
598
- # the behaviour can be disabled with check_parameters: true
625
+ # the behavior can be disabled with check_parameters: true
599
626
  request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
600
627
  request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
601
628
 
@@ -12,6 +12,7 @@ require "action_view/helpers/asset_tag_helper"
12
12
  require "action_view/helpers/asset_url_helper"
13
13
  require "action_view/helpers/atom_feed_helper"
14
14
  require "action_view/helpers/cache_helper"
15
+ require "action_view/helpers/content_exfiltration_prevention_helper"
15
16
  require "action_view/helpers/controller_helper"
16
17
  require "action_view/helpers/csp_helper"
17
18
  require "action_view/helpers/csrf_helper"
@@ -45,6 +46,7 @@ module ActionView # :nodoc:
45
46
  include AtomFeedHelper
46
47
  include CacheHelper
47
48
  include CaptureHelper
49
+ include ContentExfiltrationPreventionHelper
48
50
  include ControllerHelper
49
51
  include CspHelper
50
52
  include CsrfHelper
@@ -4,12 +4,14 @@ require "action_view/rendering"
4
4
  require "active_support/core_ext/module/redefine_method"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Layouts
8
+ #
7
9
  # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
8
10
  # repeated setups. The inclusion pattern has pages that look like this:
9
11
  #
10
- # <%= render "shared/header" %>
12
+ # <%= render "application/header" %>
11
13
  # Hello World
12
- # <%= render "shared/footer" %>
14
+ # <%= render "application/footer" %>
13
15
  #
14
16
  # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
15
17
  # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
@@ -210,9 +212,9 @@ module ActionView
210
212
  class_attribute :_layout_conditions, instance_accessor: false, default: {}
211
213
 
212
214
  _write_layout_method
213
- end
214
215
 
215
- delegate :_layout_conditions, to: :class
216
+ delegate :_layout_conditions, to: :class
217
+ end
216
218
 
217
219
  module ClassMethods
218
220
  def inherited(klass) # :nodoc:
@@ -5,7 +5,7 @@ require "active_support/log_subscriber"
5
5
  module ActionView
6
6
  # = Action View Log Subscriber
7
7
  #
8
- # Provides functionality so that Rails can output logs from Action View.
8
+ # Provides functionality so that \Rails can output logs from Action View.
9
9
  class LogSubscriber < ActiveSupport::LogSubscriber
10
10
  VIEWS_PATTERN = /^app\/views\//
11
11
 
@@ -21,6 +21,7 @@ module ActionView
21
21
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
22
  end
23
23
  end
24
+ subscribe_log_level :render_template, :debug
24
25
 
25
26
  def render_partial(event)
26
27
  debug do
@@ -31,6 +32,7 @@ module ActionView
31
32
  message
32
33
  end
33
34
  end
35
+ subscribe_log_level :render_partial, :debug
34
36
 
35
37
  def render_layout(event)
36
38
  info do
@@ -38,6 +40,7 @@ module ActionView
38
40
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
39
41
  end
40
42
  end
43
+ subscribe_log_level :render_layout, :info
41
44
 
42
45
  def render_collection(event)
43
46
  identifier = event.payload[:identifier] || "templates"
@@ -49,29 +52,60 @@ module ActionView
49
52
  message
50
53
  end
51
54
  end
55
+ subscribe_log_level :render_collection, :debug
52
56
 
53
- def start(name, id, payload)
54
- log_rendering_start(payload, name)
57
+ module Utils # :nodoc:
58
+ def logger
59
+ ActionView::Base.logger
60
+ end
55
61
 
56
- super
57
- end
62
+ private
63
+ def from_rails_root(string)
64
+ string = string.sub(rails_root, "")
65
+ string.sub!(VIEWS_PATTERN, "")
66
+ string
67
+ end
58
68
 
59
- def logger
60
- ActionView::Base.logger
69
+ def rails_root # :doc:
70
+ @root ||= "#{Rails.root}/"
71
+ end
61
72
  end
62
73
 
63
- private
64
- EMPTY = ""
65
- def from_rails_root(string) # :doc:
66
- string = string.sub(rails_root, EMPTY)
67
- string.sub!(VIEWS_PATTERN, EMPTY)
68
- string
74
+ include Utils
75
+
76
+ class Start # :nodoc:
77
+ include Utils
78
+
79
+ def start(name, id, payload)
80
+ return unless logger
81
+ logger.debug do
82
+ qualifier =
83
+ if name == "render_template.action_view"
84
+ ""
85
+ elsif name == "render_layout.action_view"
86
+ "layout "
87
+ end
88
+
89
+ return unless qualifier
90
+
91
+ message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
92
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
93
+ message
94
+ end
95
+ end
96
+
97
+ def finish(name, id, payload)
98
+ end
69
99
  end
70
100
 
71
- def rails_root # :doc:
72
- @root ||= "#{Rails.root}/"
101
+ def self.attach_to(*)
102
+ ActiveSupport::Notifications.subscribe("render_template.action_view", ActionView::LogSubscriber::Start.new)
103
+ ActiveSupport::Notifications.subscribe("render_layout.action_view", ActionView::LogSubscriber::Start.new)
104
+
105
+ super
73
106
  end
74
107
 
108
+ private
75
109
  def render_count(payload) # :doc:
76
110
  if payload[:cache_hits]
77
111
  "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
@@ -88,23 +122,6 @@ module ActionView
88
122
  "[cache miss]"
89
123
  end
90
124
  end
91
-
92
- def log_rendering_start(payload, name)
93
- debug do
94
- qualifier =
95
- if name == "render_template.action_view"
96
- ""
97
- elsif name == "render_layout.action_view"
98
- "layout "
99
- end
100
-
101
- return unless qualifier
102
-
103
- message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
104
- message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
105
- message
106
- end
107
- end
108
125
  end
109
126
  end
110
127
 
@@ -13,7 +13,7 @@ module ActionView
13
13
  # view paths, used in the resolver cache lookup. Since this key is generated
14
14
  # only once during the request, it speeds up all cache accesses.
15
15
  class LookupContext # :nodoc:
16
- attr_accessor :prefixes, :rendered_format
16
+ attr_accessor :prefixes
17
17
 
18
18
  singleton_class.attr_accessor :registered_details
19
19
  self.registered_details = []
@@ -63,16 +63,20 @@ module ActionView
63
63
  end
64
64
 
65
65
  def self.details_cache_key(details)
66
- if details[:formats]
67
- details = details.dup
68
- details[:formats] &= Template::Types.symbols
66
+ @details_keys.fetch(details) do
67
+ if formats = details[:formats]
68
+ unless Template::Types.valid_symbols?(formats)
69
+ details = details.dup
70
+ details[:formats] &= Template::Types.symbols
71
+ end
72
+ end
73
+ @details_keys[details] ||= TemplateDetails::Requested.new(**details)
69
74
  end
70
- @details_keys[details] ||= TemplateDetails::Requested.new(**details)
71
75
  end
72
76
 
73
77
  def self.clear
74
- ActionView::ViewPaths.all_view_paths.each do |path_set|
75
- path_set.each(&:clear_cache)
78
+ ActionView::PathRegistry.all_resolvers.each do |resolver|
79
+ resolver.clear_cache
76
80
  end
77
81
  @view_context_class = nil
78
82
  @details_keys.clear
@@ -83,9 +87,9 @@ module ActionView
83
87
  @digest_cache.values
84
88
  end
85
89
 
86
- def self.view_context_class(klass)
90
+ def self.view_context_class
87
91
  @view_context_mutex.synchronize do
88
- @view_context_class ||= klass.with_empty_template_cache
92
+ @view_context_class ||= ActionView::Base.with_empty_template_cache
89
93
  end
90
94
  end
91
95
  end
@@ -148,11 +152,23 @@ module ActionView
148
152
  end
149
153
  alias :any_templates? :any?
150
154
 
155
+ def append_view_paths(paths)
156
+ @view_paths = build_view_paths(@view_paths.to_a + paths)
157
+ end
158
+
159
+ def prepend_view_paths(paths)
160
+ @view_paths = build_view_paths(paths + @view_paths.to_a)
161
+ end
162
+
151
163
  private
152
164
  # Whenever setting view paths, makes a copy so that we can manipulate them in
153
165
  # instance objects as we wish.
154
166
  def build_view_paths(paths)
155
- ActionView::PathSet.new(Array(paths))
167
+ if ActionView::PathSet === paths
168
+ paths
169
+ else
170
+ ActionView::PathSet.new(Array(paths))
171
+ end
156
172
  end
157
173
 
158
174
  # Compute details hash and key according to user options (e.g. passed from #render).
@@ -250,12 +266,12 @@ module ActionView
250
266
  values.concat(default_formats) if values.delete "*/*"
251
267
  values.uniq!
252
268
 
253
- invalid_values = (values - Template::Types.symbols)
254
- unless invalid_values.empty?
269
+ unless Template::Types.valid_symbols?(values)
270
+ invalid_values = values - Template::Types.symbols
255
271
  raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
256
272
  end
257
273
 
258
- if values == [:js]
274
+ if (values.length == 1) && (values[0] == :js)
259
275
  values << :html
260
276
  @html_fallback_for_js = true
261
277
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView # :nodoc:
4
+ module PathRegistry # :nodoc:
5
+ @view_paths_by_class = {}
6
+ @file_system_resolvers = {}
7
+ @file_system_resolver_mutex = Mutex.new
8
+ @file_system_resolver_hooks = []
9
+
10
+ class << self
11
+ attr_reader :file_system_resolver_hooks
12
+ end
13
+
14
+ def self.get_view_paths(klass)
15
+ @view_paths_by_class[klass] || get_view_paths(klass.superclass)
16
+ end
17
+
18
+ def self.set_view_paths(klass, paths)
19
+ @view_paths_by_class[klass] = paths
20
+ end
21
+
22
+ def self.cast_file_system_resolvers(paths)
23
+ paths = Array(paths)
24
+
25
+ @file_system_resolver_mutex.synchronize do
26
+ built_resolver = false
27
+ paths = paths.map do |path|
28
+ case path
29
+ when String, Pathname
30
+ path = File.expand_path(path)
31
+ @file_system_resolvers[path] ||=
32
+ begin
33
+ built_resolver = true
34
+ FileSystemResolver.new(path)
35
+ end
36
+ else
37
+ path
38
+ end
39
+ end
40
+
41
+ file_system_resolver_hooks.each(&:call) if built_resolver
42
+ end
43
+
44
+ paths
45
+ end
46
+
47
+ def self.all_resolvers
48
+ resolvers = [all_file_system_resolvers]
49
+ resolvers.concat @view_paths_by_class.values.map(&:to_a)
50
+ resolvers.flatten.uniq
51
+ end
52
+
53
+ def self.all_file_system_resolvers
54
+ @file_system_resolvers.values
55
+ end
56
+ end
57
+ end
@@ -13,14 +13,14 @@ module ActionView # :nodoc:
13
13
 
14
14
  attr_reader :paths
15
15
 
16
- delegate :[], :include?, :pop, :size, :each, to: :paths
16
+ delegate :[], :include?, :size, :each, to: :paths
17
17
 
18
18
  def initialize(paths = [])
19
- @paths = typecast paths
19
+ @paths = typecast(paths).freeze
20
20
  end
21
21
 
22
22
  def initialize_copy(other)
23
- @paths = other.paths.dup
23
+ @paths = other.paths.dup.freeze
24
24
  self
25
25
  end
26
26
 
@@ -32,18 +32,11 @@ module ActionView # :nodoc:
32
32
  PathSet.new paths.compact
33
33
  end
34
34
 
35
- def +(array)
35
+ def +(other)
36
+ array = Array === other ? other : other.paths
36
37
  PathSet.new(paths + array)
37
38
  end
38
39
 
39
- %w(<< concat push insert unshift).each do |method|
40
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
41
- def #{method}(*args)
42
- paths.#{method}(*typecast(args))
43
- end
44
- METHOD
45
- end
46
-
47
40
  def find(path, prefixes, partial, details, details_key, locals)
48
41
  find_all(path, prefixes, partial, details, details_key, locals).first ||
49
42
  raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
@@ -75,9 +68,15 @@ module ActionView # :nodoc:
75
68
  paths.map do |path|
76
69
  case path
77
70
  when Pathname, String
78
- FileSystemResolver.new path.to_s
79
- else
71
+ # This path should only be reached by "direct" users of
72
+ # ActionView::Base (not using the ViewPaths or Renderer modules).
73
+ # We can't cache/de-dup the file system resolver in this case as we
74
+ # don't know which compiled_method_container we'll be rendering to.
75
+ FileSystemResolver.new(path)
76
+ when Resolver
80
77
  path
78
+ else
79
+ raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver"
81
80
  end
82
81
  end
83
82
  end
@@ -13,6 +13,7 @@ module ActionView
13
13
  config.action_view.image_loading = nil
14
14
  config.action_view.image_decoding = nil
15
15
  config.action_view.apply_stylesheet_media_default = true
16
+ config.action_view.prepend_content_exfiltration_prevention = false
16
17
 
17
18
  config.eager_load_namespaces << ActionView
18
19
 
@@ -40,6 +41,17 @@ module ActionView
40
41
  end
41
42
  end
42
43
 
44
+ config.after_initialize do |app|
45
+ prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
46
+ ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
47
+ end
48
+
49
+ config.after_initialize do |app|
50
+ if klass = app.config.action_view.delete(:sanitizer_vendor)
51
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
52
+ end
53
+ end
54
+
43
55
  config.after_initialize do |app|
44
56
  button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
45
57
  unless button_to_generates_button_tag.nil?
@@ -67,6 +79,10 @@ module ActionView
67
79
  end
68
80
  end
69
81
 
82
+ initializer "action_view.deprecator", before: :load_environment_config do |app|
83
+ app.deprecators[:action_view] = ActionView.deprecator
84
+ end
85
+
70
86
  initializer "action_view.logger" do
71
87
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
72
88
  end
@@ -74,7 +90,7 @@ module ActionView
74
90
  initializer "action_view.caching" do |app|
75
91
  ActiveSupport.on_load(:action_view) do
76
92
  if app.config.action_view.cache_template_loading.nil?
77
- ActionView::Resolver.caching = app.config.cache_classes
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
78
94
  end
79
95
  end
80
96
  end
@@ -91,13 +107,20 @@ module ActionView
91
107
 
92
108
  config.after_initialize do |app|
93
109
  enable_caching = if app.config.action_view.cache_template_loading.nil?
94
- app.config.cache_classes
110
+ !app.config.reloading_enabled?
95
111
  else
96
112
  app.config.action_view.cache_template_loading
97
113
  end
98
114
 
99
115
  unless enable_caching
100
- app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ view_reloader.execute
120
+ app.reloader.to_run do
121
+ require_unload_lock!
122
+ view_reloader.execute
123
+ end
101
124
  end
102
125
  end
103
126
 
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
4
4
  require "action_view/model_naming"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Record \Identifier
8
+ #
7
9
  # RecordIdentifier encapsulates methods used by various ActionView helpers
8
10
  # to associate records with DOM elements.
9
11
  #
@@ -31,6 +33,8 @@ module ActionView
31
33
  # automatically generated, following naming conventions encapsulated by the
32
34
  # RecordIdentifier methods #dom_id and #dom_class:
33
35
  #
36
+ # dom_id(Post) # => "new_post"
37
+ # dom_class(Post) # => "post"
34
38
  # dom_id(Post.new) # => "new_post"
35
39
  # dom_class(Post.new) # => "post"
36
40
  # dom_id(Post.find 42) # => "post_42"
@@ -79,18 +83,21 @@ module ActionView
79
83
  # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
80
84
  # If no id is found, prefix with "new_" instead.
81
85
  #
82
- # dom_id(Post.find(45)) # => "post_45"
83
- # dom_id(Post.new) # => "new_post"
86
+ # dom_id(Post.find(45)) # => "post_45"
87
+ # dom_id(Post) # => "new_post"
84
88
  #
85
89
  # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
86
90
  #
87
91
  # dom_id(Post.find(45), :edit) # => "edit_post_45"
88
- # dom_id(Post.new, :custom) # => "custom_post"
89
- def dom_id(record, prefix = nil)
90
- if record_id = record_key_for_dom_id(record)
91
- "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
92
+ # dom_id(Post, :custom) # => "custom_post"
93
+ def dom_id(record_or_class, prefix = nil)
94
+ raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
95
+
96
+ record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
97
+ if record_id
98
+ "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
92
99
  else
93
- dom_class(record, prefix || NEW)
100
+ dom_class(record_or_class, prefix || NEW)
94
101
  end
95
102
  end
96
103
 
@@ -105,7 +112,7 @@ module ActionView
105
112
  # make sure yourself that your dom ids are valid, in case you override this method.
106
113
  def record_key_for_dom_id(record) # :doc:
107
114
  key = convert_to_model(record).to_key
108
- key ? key.join(JOIN) : key
115
+ key && key.all? ? key.join(JOIN) : nil
109
116
  end
110
117
  end
111
118
  end
@@ -31,7 +31,7 @@ module ActionView
31
31
 
32
32
  module ObjectRendering # :nodoc:
33
33
  PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
- h[k] = Concurrent::Map.new
34
+ h.compute_if_absent(k) { Concurrent::Map.new }
35
35
  end
36
36
 
37
37
  def initialize(lookup_context, options)
@@ -51,6 +51,10 @@ module ActionView
51
51
  def length
52
52
  @collection.respond_to?(:length) ? @collection.length : size
53
53
  end
54
+
55
+ def preload!
56
+ # no-op
57
+ end
54
58
  end
55
59
 
56
60
  class SameCollectionIterator < CollectionIterator # :nodoc:
@@ -84,9 +88,13 @@ module ActionView
84
88
 
85
89
  def each_with_info
86
90
  return super unless block_given?
87
- @relation.preload_associations(@collection)
91
+ preload!
88
92
  super
89
93
  end
94
+
95
+ def preload!
96
+ @relation.preload_associations(@collection)
97
+ end
90
98
  end
91
99
 
92
100
  class MixedCollectionIterator < CollectionIterator # :nodoc: