actionview 6.1.7.2 → 7.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +265 -261
  3. data/MIT-LICENSE +1 -0
  4. data/lib/action_view/base.rb +4 -7
  5. data/lib/action_view/buffers.rb +2 -2
  6. data/lib/action_view/cache_expiry.rb +46 -32
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  8. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  9. data/lib/action_view/dependency_tracker.rb +6 -147
  10. data/lib/action_view/digestor.rb +7 -4
  11. data/lib/action_view/flows.rb +4 -4
  12. data/lib/action_view/gem_version.rb +5 -5
  13. data/lib/action_view/helpers/active_model_helper.rb +2 -2
  14. data/lib/action_view/helpers/asset_tag_helper.rb +95 -39
  15. data/lib/action_view/helpers/asset_url_helper.rb +16 -16
  16. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  17. data/lib/action_view/helpers/cache_helper.rb +52 -3
  18. data/lib/action_view/helpers/capture_helper.rb +4 -4
  19. data/lib/action_view/helpers/controller_helper.rb +2 -2
  20. data/lib/action_view/helpers/csp_helper.rb +1 -1
  21. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  22. data/lib/action_view/helpers/date_helper.rb +111 -43
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +211 -85
  25. data/lib/action_view/helpers/form_options_helper.rb +70 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +150 -53
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +17 -16
  29. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  31. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  32. data/lib/action_view/helpers/tag_helper.rb +37 -8
  33. data/lib/action_view/helpers/tags/base.rb +5 -25
  34. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  37. data/lib/action_view/helpers/tags/select.rb +1 -1
  38. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  39. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  40. data/lib/action_view/helpers/tags.rb +3 -2
  41. data/lib/action_view/helpers/text_helper.rb +25 -14
  42. data/lib/action_view/helpers/translation_helper.rb +12 -43
  43. data/lib/action_view/helpers/url_helper.rb +194 -123
  44. data/lib/action_view/helpers.rb +25 -25
  45. data/lib/action_view/layouts.rb +7 -4
  46. data/lib/action_view/lookup_context.rb +33 -52
  47. data/lib/action_view/model_naming.rb +2 -2
  48. data/lib/action_view/path_set.rb +16 -22
  49. data/lib/action_view/railtie.rb +19 -7
  50. data/lib/action_view/record_identifier.rb +1 -1
  51. data/lib/action_view/render_parser.rb +188 -0
  52. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  53. data/lib/action_view/renderer/partial_renderer.rb +1 -35
  54. data/lib/action_view/renderer/renderer.rb +4 -4
  55. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  56. data/lib/action_view/renderer/template_renderer.rb +6 -2
  57. data/lib/action_view/rendering.rb +3 -3
  58. data/lib/action_view/ripper_ast_parser.rb +198 -0
  59. data/lib/action_view/routing_url_for.rb +8 -5
  60. data/lib/action_view/template/error.rb +108 -13
  61. data/lib/action_view/template/handlers/erb.rb +6 -0
  62. data/lib/action_view/template/handlers.rb +3 -3
  63. data/lib/action_view/template/html.rb +3 -3
  64. data/lib/action_view/template/inline.rb +3 -3
  65. data/lib/action_view/template/raw_file.rb +3 -3
  66. data/lib/action_view/template/resolver.rb +89 -314
  67. data/lib/action_view/template/text.rb +3 -3
  68. data/lib/action_view/template/types.rb +14 -12
  69. data/lib/action_view/template.rb +18 -2
  70. data/lib/action_view/template_details.rb +66 -0
  71. data/lib/action_view/template_path.rb +64 -0
  72. data/lib/action_view/test_case.rb +7 -3
  73. data/lib/action_view/testing/resolvers.rb +11 -12
  74. data/lib/action_view/unbound_template.rb +33 -7
  75. data/lib/action_view/version.rb +1 -1
  76. data/lib/action_view/view_paths.rb +4 -4
  77. data/lib/action_view.rb +2 -3
  78. data/lib/assets/compiled/rails-ujs.js +36 -5
  79. metadata +23 -16
@@ -8,7 +8,7 @@ require "action_view/helpers/tag_helper"
8
8
 
9
9
  module ActionView
10
10
  # = Action View Asset Tag Helpers
11
- module Helpers #:nodoc:
11
+ module Helpers # :nodoc:
12
12
  # This module provides methods for generating HTML that links views to assets such
13
13
  # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
14
14
  # the assets exist before linking to them:
@@ -16,14 +16,15 @@ module ActionView
16
16
  # image_tag("rails.png")
17
17
  # # => <img src="/assets/rails.png" />
18
18
  # stylesheet_link_tag("application")
19
- # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
19
+ # # => <link href="/assets/application.css?body=1" rel="stylesheet" />
20
20
  module AssetTagHelper
21
- extend ActiveSupport::Concern
22
-
23
21
  include AssetUrlHelper
24
22
  include TagHelper
25
23
 
24
+ mattr_accessor :image_loading
25
+ mattr_accessor :image_decoding
26
26
  mattr_accessor :preload_links_header
27
+ mattr_accessor :apply_stylesheet_media_default
27
28
 
28
29
  # Returns an HTML script tag for each of the +sources+ provided.
29
30
  #
@@ -40,7 +41,7 @@ module ActionView
40
41
  # When the Asset Pipeline is enabled, you can pass the name of your manifest as
41
42
  # source, and include other JavaScript or CoffeeScript files inside the manifest.
42
43
  #
43
- # If the server supports Early Hints header links for these assets will be
44
+ # If the server supports Early Hints, header links for these assets will be
44
45
  # automatically pushed.
45
46
  #
46
47
  # ==== Options
@@ -93,11 +94,12 @@ module ActionView
93
94
  crossorigin = options.delete("crossorigin")
94
95
  crossorigin = "anonymous" if crossorigin == true
95
96
  integrity = options["integrity"]
97
+ rel = options["type"] == "module" ? "modulepreload" : "preload"
96
98
 
97
99
  sources_tags = sources.uniq.map { |source|
98
100
  href = path_to_javascript(source, path_options)
99
- if preload_links_header && !options["defer"]
100
- preload_link = "<#{href}>; rel=preload; as=script"
101
+ if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
102
+ preload_link = "<#{href}>; rel=#{rel}; as=script"
101
103
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
102
104
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
103
105
  preload_link += "; nopush" if nopush
@@ -120,24 +122,41 @@ module ActionView
120
122
  sources_tags
121
123
  end
122
124
 
123
- # Returns a stylesheet link tag for the sources specified as arguments. If
124
- # you don't specify an extension, <tt>.css</tt> will be appended automatically.
125
+ # Returns a stylesheet link tag for the sources specified as arguments.
126
+ #
127
+ # When passing paths, the <tt>.css</tt> extension is optional.
128
+ # If you don't specify an extension, <tt>.css</tt> will be appended automatically.
129
+ # If you do not want <tt>.css</tt> appended to the path,
130
+ # set <tt>extname: false</tt> in the options.
125
131
  # You can modify the link attributes by passing a hash as the last argument.
126
- # For historical reasons, the 'media' attribute will always be present and defaults
127
- # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
128
- # apply to all media types.
129
132
  #
130
- # If the server supports Early Hints header links for these assets will be
133
+ # If the server supports Early Hints, header links for these assets will be
131
134
  # automatically pushed.
132
135
  #
136
+ # ==== Options
137
+ #
138
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
139
+ # already exists. This only applies for relative URLs.
140
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
141
+ # applies when a relative URL and +host+ options are provided.
142
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
143
+ # that path.
144
+ # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
145
+ # when it is set to true.
146
+ #
147
+ # ==== Examples
148
+ #
133
149
  # stylesheet_link_tag "style"
134
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
150
+ # # => <link href="/assets/style.css" rel="stylesheet" />
135
151
  #
136
152
  # stylesheet_link_tag "style.css"
137
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
153
+ # # => <link href="/assets/style.css" rel="stylesheet" />
138
154
  #
139
155
  # stylesheet_link_tag "http://www.example.com/style.css"
140
- # # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
156
+ # # => <link href="http://www.example.com/style.css" rel="stylesheet" />
157
+ #
158
+ # stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less"
159
+ # # => <link href="/stylesheets/style.less" rel="stylesheet/less">
141
160
  #
142
161
  # stylesheet_link_tag "style", media: "all"
143
162
  # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
@@ -146,11 +165,11 @@ module ActionView
146
165
  # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
147
166
  #
148
167
  # stylesheet_link_tag "random.styles", "/css/stylish"
149
- # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
150
- # # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
168
+ # # => <link href="/assets/random.styles" rel="stylesheet" />
169
+ # # <link href="/css/stylish.css" rel="stylesheet" />
151
170
  def stylesheet_link_tag(*sources)
152
171
  options = sources.extract_options!.stringify_keys
153
- path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
172
+ path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
154
173
  preload_links = []
155
174
  crossorigin = options.delete("crossorigin")
156
175
  crossorigin = "anonymous" if crossorigin == true
@@ -159,7 +178,7 @@ module ActionView
159
178
 
160
179
  sources_tags = sources.uniq.map { |source|
161
180
  href = path_to_stylesheet(source, path_options)
162
- if preload_links_header
181
+ if preload_links_header && href.present? && !href.start_with?("data:")
163
182
  preload_link = "<#{href}>; rel=preload; as=style"
164
183
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
165
184
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -168,10 +187,14 @@ module ActionView
168
187
  end
169
188
  tag_options = {
170
189
  "rel" => "stylesheet",
171
- "media" => "screen",
172
190
  "crossorigin" => crossorigin,
173
191
  "href" => href
174
192
  }.merge!(options)
193
+
194
+ if apply_stylesheet_media_default && tag_options["media"].blank?
195
+ tag_options["media"] = "screen"
196
+ end
197
+
175
198
  tag(:link, tag_options)
176
199
  }.join("\n").html_safe
177
200
 
@@ -235,14 +258,14 @@ module ActionView
235
258
  #
236
259
  # The helper gets the name of the favicon file as first argument, which
237
260
  # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
238
- # to override their defaults, "shortcut icon" and "image/x-icon"
261
+ # to override their defaults, "icon" and "image/x-icon"
239
262
  # respectively:
240
263
  #
241
264
  # favicon_link_tag
242
- # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
265
+ # # => <link href="/assets/favicon.ico" rel="icon" type="image/x-icon" />
243
266
  #
244
267
  # favicon_link_tag 'myicon.ico'
245
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
268
+ # # => <link href="/assets/myicon.ico" rel="icon" type="image/x-icon" />
246
269
  #
247
270
  # Mobile Safari looks for a different link tag, pointing to an image that
248
271
  # will be used if you add the page to the home screen of an iOS device.
@@ -252,7 +275,7 @@ module ActionView
252
275
  # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
253
276
  def favicon_link_tag(source = "favicon.ico", options = {})
254
277
  tag("link", {
255
- rel: "shortcut icon",
278
+ rel: "icon",
256
279
  type: "image/x-icon",
257
280
  href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
258
281
  }.merge!(options.symbolize_keys))
@@ -302,16 +325,17 @@ module ActionView
302
325
  crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
303
326
  integrity = options[:integrity]
304
327
  nopush = options.delete(:nopush) || false
328
+ rel = mime_type == "module" ? "modulepreload" : "preload"
305
329
 
306
330
  link_tag = tag.link(**{
307
- rel: "preload",
331
+ rel: rel,
308
332
  href: href,
309
333
  as: as_type,
310
334
  type: mime_type,
311
335
  crossorigin: crossorigin
312
336
  }.merge!(options.symbolize_keys))
313
337
 
314
- preload_link = "<#{href}>; rel=preload; as=#{as_type}"
338
+ preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
315
339
  preload_link += "; type=#{mime_type}" if mime_type
316
340
  preload_link += "; crossorigin=#{crossorigin}" if crossorigin
317
341
  preload_link += "; integrity=#{integrity}" if integrity
@@ -382,6 +406,10 @@ module ActionView
382
406
  end
383
407
 
384
408
  options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
409
+
410
+ options[:loading] ||= image_loading if image_loading
411
+ options[:decoding] ||= image_decoding if image_decoding
412
+
385
413
  tag("img", options)
386
414
  end
387
415
 
@@ -489,9 +517,9 @@ module ActionView
489
517
 
490
518
  def extract_dimensions(size)
491
519
  size = size.to_s
492
- if /\A\d+x\d+\z/.match?(size)
520
+ if /\A(\d+|\d+.\d+)x(\d+|\d+.\d+)\z/.match?(size)
493
521
  size.split("x")
494
- elsif /\A\d+\z/.match?(size)
522
+ elsif /\A(\d+|\d+.\d+)\z/.match?(size)
495
523
  [size, size]
496
524
  end
497
525
  end
@@ -503,24 +531,52 @@ module ActionView
503
531
  end
504
532
 
505
533
  def resolve_link_as(extname, mime_type)
506
- if extname == "js"
507
- "script"
508
- elsif extname == "css"
509
- "style"
510
- elsif extname == "vtt"
511
- "track"
512
- elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font image))
513
- type
534
+ case extname
535
+ when "js" then "script"
536
+ when "css" then "style"
537
+ when "vtt" then "track"
538
+ else
539
+ mime_type.to_s.split("/").first.presence_in(%w(audio video font image))
514
540
  end
515
541
  end
516
542
 
517
- def send_preload_links_header(preload_links)
543
+ MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
544
+ def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
545
+ return if preload_links.empty?
546
+ return if respond_to?(:response) && response&.sending?
547
+
518
548
  if respond_to?(:request) && request
519
549
  request.send_early_hints("Link" => preload_links.join("\n"))
520
550
  end
521
551
 
522
552
  if respond_to?(:response) && response
523
- response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",")
553
+ header = response.headers["Link"]
554
+ header = header ? header.dup : +""
555
+
556
+ # rindex count characters not bytes, but we assume non-ascii characters
557
+ # are rare in urls, and we have a 192 bytes margin.
558
+ last_line_offset = header.rindex("\n")
559
+ last_line_size = if last_line_offset
560
+ header.bytesize - last_line_offset
561
+ else
562
+ header.bytesize
563
+ end
564
+
565
+ preload_links.each do |link|
566
+ if link.bytesize + last_line_size + 1 < max_header_size
567
+ unless header.empty?
568
+ header << ","
569
+ last_line_size += 1
570
+ end
571
+ else
572
+ header << "\n"
573
+ last_line_size = 0
574
+ end
575
+ header << link
576
+ last_line_size += link.bytesize
577
+ end
578
+
579
+ response.headers["Link"] = header
524
580
  end
525
581
  end
526
582
  end
@@ -4,7 +4,7 @@ require "zlib"
4
4
 
5
5
  module ActionView
6
6
  # = Action View Asset URL Helpers
7
- module Helpers #:nodoc:
7
+ module Helpers # :nodoc:
8
8
  # This module provides methods for generating asset paths and
9
9
  # URLs.
10
10
  #
@@ -31,7 +31,7 @@ module ActionView
31
31
  # image_tag("rails.png")
32
32
  # # => <img src="http://assets.example.com/assets/rails.png" />
33
33
  # stylesheet_link_tag("application")
34
- # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
34
+ # # => <link href="http://assets.example.com/assets/application.css" rel="stylesheet" />
35
35
  #
36
36
  # Browsers open a limited number of simultaneous connections to a single
37
37
  # host. The exact number varies by browser and version. This limit may cause
@@ -44,7 +44,7 @@ module ActionView
44
44
  # image_tag("rails.png")
45
45
  # # => <img src="http://assets0.example.com/assets/rails.png" />
46
46
  # stylesheet_link_tag("application")
47
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
47
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
48
48
  #
49
49
  # This may improve the asset loading performance of your application.
50
50
  # It is also possible the combination of additional connection overhead
@@ -65,12 +65,12 @@ module ActionView
65
65
  # +asset_host+ to a proc like this:
66
66
  #
67
67
  # ActionController::Base.asset_host = Proc.new { |source|
68
- # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
68
+ # "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com"
69
69
  # }
70
70
  # image_tag("rails.png")
71
71
  # # => <img src="http://assets1.example.com/assets/rails.png" />
72
72
  # stylesheet_link_tag("application")
73
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
73
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
74
74
  #
75
75
  # The example above generates "http://assets1.example.com" and
76
76
  # "http://assets2.example.com". This option is useful for example if
@@ -89,7 +89,7 @@ module ActionView
89
89
  # image_tag("rails.png")
90
90
  # # => <img src="http://assets.example.com/assets/rails.png" />
91
91
  # stylesheet_link_tag("application")
92
- # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
92
+ # # => <link href="http://stylesheets.example.com/assets/application.css" rel="stylesheet" />
93
93
  #
94
94
  # Alternatively you may ask for a second parameter +request+. That one is
95
95
  # particularly useful for serving assets from an SSL-protected page. The
@@ -121,7 +121,7 @@ module ActionView
121
121
  URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
122
122
 
123
123
  # This is the entry point for all assets.
124
- # When using the asset pipeline (i.e. sprockets and sprockets-rails), the
124
+ # When using an asset pipeline gem (e.g. propshaft or sprockets-rails), the
125
125
  # behavior is "enhanced". You can bypass the asset pipeline by passing in
126
126
  # <tt>skip_pipeline: true</tt> to the options.
127
127
  #
@@ -130,7 +130,7 @@ module ActionView
130
130
  # === With the asset pipeline
131
131
  #
132
132
  # All options passed to +asset_path+ will be passed to +compute_asset_path+
133
- # which is implemented by sprockets-rails.
133
+ # which is implemented by asset pipeline gems.
134
134
  #
135
135
  # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
136
136
  # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
@@ -190,7 +190,7 @@ module ActionView
190
190
  return "" if source.blank?
191
191
  return source if URI_REGEXP.match?(source)
192
192
 
193
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
193
+ tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "")
194
194
 
195
195
  if extname = compute_asset_extname(source, options)
196
196
  source = "#{source}#{extname}"
@@ -219,7 +219,7 @@ module ActionView
219
219
 
220
220
  # Computes the full URL to an asset in the public directory. This
221
221
  # will use +asset_path+ internally, so most of their behaviors
222
- # will be the same. If :host options is set, it overwrites global
222
+ # will be the same. If +:host+ options is set, it overwrites global
223
223
  # +config.action_controller.asset_host+ setting.
224
224
  #
225
225
  # All other options provided are forwarded to +asset_path+ call.
@@ -324,7 +324,7 @@ module ActionView
324
324
 
325
325
  # Computes the full URL to a JavaScript asset in the public javascripts directory.
326
326
  # This will use +javascript_path+ internally, so most of their behaviors will be the same.
327
- # Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
327
+ # Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
328
328
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
329
329
  #
330
330
  # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
@@ -351,7 +351,7 @@ module ActionView
351
351
 
352
352
  # Computes the full URL to a stylesheet asset in the public stylesheets directory.
353
353
  # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
354
- # Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
354
+ # Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
355
355
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
356
356
  #
357
357
  # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
@@ -381,7 +381,7 @@ module ActionView
381
381
 
382
382
  # Computes the full URL to an image asset.
383
383
  # This will use +image_path+ internally, so most of their behaviors will be the same.
384
- # Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
384
+ # Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
385
385
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
386
386
  #
387
387
  # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
@@ -407,7 +407,7 @@ module ActionView
407
407
 
408
408
  # Computes the full URL to a video asset in the public videos directory.
409
409
  # This will use +video_path+ internally, so most of their behaviors will be the same.
410
- # Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
410
+ # Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
411
411
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
412
412
  #
413
413
  # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
@@ -433,7 +433,7 @@ module ActionView
433
433
 
434
434
  # Computes the full URL to an audio asset in the public audios directory.
435
435
  # This will use +audio_path+ internally, so most of their behaviors will be the same.
436
- # Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
436
+ # Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
437
437
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
438
438
  #
439
439
  # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
@@ -458,7 +458,7 @@ module ActionView
458
458
 
459
459
  # Computes the full URL to a font asset.
460
460
  # This will use +font_path+ internally, so most of their behaviors will be the same.
461
- # Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
461
+ # Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
462
462
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
463
463
  #
464
464
  # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
- require "active_support/core_ext/symbol/starts_ends_with"
5
4
 
6
5
  module ActionView
7
6
  # = Action View Atom Feed Helpers
8
- module Helpers #:nodoc:
7
+ module Helpers # :nodoc:
9
8
  module AtomFeedHelper
10
9
  # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
11
10
  # template languages).
@@ -127,7 +126,7 @@ module ActionView
127
126
  end
128
127
  end
129
128
 
130
- class AtomBuilder #:nodoc:
129
+ class AtomBuilder # :nodoc:
131
130
  XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
132
131
 
133
132
  def initialize(xml)
@@ -161,7 +160,7 @@ module ActionView
161
160
  end
162
161
  end
163
162
 
164
- class AtomFeedBuilder < AtomBuilder #:nodoc:
163
+ class AtomFeedBuilder < AtomBuilder # :nodoc:
165
164
  def initialize(xml, view, feed_options = {})
166
165
  @xml, @view, @feed_options = xml, view, feed_options
167
166
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  module ActionView
4
4
  # = Action View Cache Helper
5
- module Helpers #:nodoc:
5
+ module Helpers # :nodoc:
6
6
  module CacheHelper
7
+ class UncacheableFragmentError < StandardError; end
8
+
7
9
  # This helper exposes a method for caching fragments of a view
8
10
  # rather than an entire action or page. This technique is useful
9
11
  # caching pieces like menus, lists of new topics, static HTML
@@ -165,8 +167,10 @@ module ActionView
165
167
  # expire the cache.
166
168
  def cache(name = {}, options = {}, &block)
167
169
  if controller.respond_to?(:perform_caching) && controller.perform_caching
168
- name_options = options.slice(:skip_digest)
169
- safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
170
+ CachingRegistry.track_caching do
171
+ name_options = options.slice(:skip_digest)
172
+ safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
173
+ end
170
174
  else
171
175
  yield
172
176
  end
@@ -174,6 +178,34 @@ module ActionView
174
178
  nil
175
179
  end
176
180
 
181
+ # Returns whether the current view fragment is within a +cache+ block.
182
+ #
183
+ # Useful when certain fragments aren't cacheable:
184
+ #
185
+ # <% cache project do %>
186
+ # <% raise StandardError, "Caching private data!" if caching? %>
187
+ # <% end %>
188
+ def caching?
189
+ CachingRegistry.caching?
190
+ end
191
+
192
+ # Raises +UncacheableFragmentError+ when called from within a +cache+ block.
193
+ #
194
+ # Useful to denote helper methods that can't participate in fragment caching:
195
+ #
196
+ # def project_name_with_time(project)
197
+ # uncacheable!
198
+ # "#{project.name} - #{Time.now}"
199
+ # end
200
+ #
201
+ # # Which will then raise if used within a +cache+ block:
202
+ # <% cache project do %>
203
+ # <%= project_name_with_time(project) %>
204
+ # <% end %>
205
+ def uncacheable!
206
+ raise UncacheableFragmentError, "can't be fragment cached" if caching?
207
+ end
208
+
177
209
  # Cache fragments of a view if +condition+ is true
178
210
  #
179
211
  # <% cache_if admin?, project do %>
@@ -259,6 +291,23 @@ module ActionView
259
291
  end
260
292
  controller.write_fragment(name, fragment, options)
261
293
  end
294
+
295
+ module CachingRegistry # :nodoc:
296
+ extend self
297
+
298
+ def caching?
299
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false
300
+ end
301
+
302
+ def track_caching
303
+ caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching]
304
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] = true
305
+
306
+ yield
307
+ ensure
308
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was
309
+ end
310
+ end
262
311
  end
263
312
  end
264
313
  end
@@ -4,7 +4,7 @@ require "active_support/core_ext/string/output_safety"
4
4
 
5
5
  module ActionView
6
6
  # = Action View Capture Helper
7
- module Helpers #:nodoc:
7
+ module Helpers # :nodoc:
8
8
  # CaptureHelper exposes methods to let you extract generated markup which
9
9
  # can be used in other parts of a template or layout file.
10
10
  #
@@ -121,7 +121,7 @@ module ActionView
121
121
  # <li><%= link_to 'Home', action: 'index' %></li>
122
122
  # <% end %>
123
123
  #
124
- # And in another place:
124
+ # And in another place:
125
125
  #
126
126
  # <% content_for :navigation do %>
127
127
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -137,7 +137,7 @@ module ActionView
137
137
  # <li><%= link_to 'Home', action: 'index' %></li>
138
138
  # <% end %>
139
139
  #
140
- # <%# Add some other content, or use a different template: %>
140
+ # <%# Add some other content, or use a different template: %>
141
141
  #
142
142
  # <% content_for :navigation, flush: true do %>
143
143
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -198,7 +198,7 @@ module ActionView
198
198
 
199
199
  # Use an alternate output buffer for the duration of the block.
200
200
  # Defaults to a new empty string.
201
- def with_output_buffer(buf = nil) #:nodoc:
201
+ def with_output_buffer(buf = nil) # :nodoc:
202
202
  unless buf
203
203
  buf = ActionView::OutputBuffer.new
204
204
  if output_buffer && output_buffer.respond_to?(:encoding)
@@ -3,10 +3,10 @@
3
3
  require "active_support/core_ext/module/attr_internal"
4
4
 
5
5
  module ActionView
6
- module Helpers #:nodoc:
6
+ module Helpers # :nodoc:
7
7
  # This module keeps all methods and behavior in ActionView
8
8
  # that simply delegates to the controller.
9
- module ControllerHelper #:nodoc:
9
+ module ControllerHelper # :nodoc:
10
10
  attr_internal :controller, :request
11
11
 
12
12
  CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params,
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActionView
4
4
  # = Action View CSP Helper
5
- module Helpers #:nodoc:
5
+ module Helpers # :nodoc:
6
6
  module CspHelper
7
7
  # Returns a meta tag "csp-nonce" with the per-session nonce value
8
8
  # for allowing inline <script> tags.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActionView
4
4
  # = Action View CSRF Helper
5
- module Helpers #:nodoc:
5
+ module Helpers # :nodoc:
6
6
  module CsrfHelper
7
7
  # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
8
8
  # request forgery protection parameter and token, respectively.
@@ -17,7 +17,7 @@ module ActionView
17
17
  # You don't need to use these tags for regular forms as they generate their own hidden fields.
18
18
  #
19
19
  # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
20
- # "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
20
+ # +X-CSRF-Token+ HTTP header. If you are using rails-ujs, this happens automatically.
21
21
  #
22
22
  def csrf_meta_tags
23
23
  if defined?(protect_against_forgery?) && protect_against_forgery?