actionview 7.0.8.7 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -454
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +52 -14
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +44 -41
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
  12. data/lib/action_view/dependency_tracker.rb +1 -1
  13. data/lib/action_view/deprecator.rb +7 -0
  14. data/lib/action_view/digestor.rb +1 -1
  15. data/lib/action_view/gem_version.rb +4 -4
  16. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  17. data/lib/action_view/helpers/asset_tag_helper.rb +151 -55
  18. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  19. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  20. data/lib/action_view/helpers/cache_helper.rb +7 -13
  21. data/lib/action_view/helpers/capture_helper.rb +30 -10
  22. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  23. data/lib/action_view/helpers/controller_helper.rb +6 -0
  24. data/lib/action_view/helpers/csp_helper.rb +2 -2
  25. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  26. data/lib/action_view/helpers/date_helper.rb +17 -19
  27. data/lib/action_view/helpers/debug_helper.rb +3 -3
  28. data/lib/action_view/helpers/form_helper.rb +248 -214
  29. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  30. data/lib/action_view/helpers/form_tag_helper.rb +125 -58
  31. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  32. data/lib/action_view/helpers/number_helper.rb +37 -330
  33. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  34. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  35. data/lib/action_view/helpers/sanitize_helper.rb +51 -21
  36. data/lib/action_view/helpers/tag_helper.rb +210 -42
  37. data/lib/action_view/helpers/tags/base.rb +11 -52
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  41. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  45. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  46. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  47. data/lib/action_view/helpers/tags/select.rb +3 -0
  48. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  49. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  51. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  52. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  53. data/lib/action_view/helpers/tags.rb +2 -0
  54. data/lib/action_view/helpers/text_helper.rb +157 -85
  55. data/lib/action_view/helpers/translation_helper.rb +3 -3
  56. data/lib/action_view/helpers/url_helper.rb +35 -80
  57. data/lib/action_view/helpers.rb +2 -0
  58. data/lib/action_view/layouts.rb +8 -8
  59. data/lib/action_view/log_subscriber.rb +57 -36
  60. data/lib/action_view/lookup_context.rb +29 -13
  61. data/lib/action_view/path_registry.rb +57 -0
  62. data/lib/action_view/path_set.rb +13 -14
  63. data/lib/action_view/railtie.rb +25 -3
  64. data/lib/action_view/record_identifier.rb +15 -8
  65. data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
  66. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +156 -13
  67. data/lib/action_view/render_parser.rb +21 -169
  68. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  69. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  70. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  71. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  72. data/lib/action_view/renderer/renderer.rb +34 -38
  73. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  74. data/lib/action_view/renderer/template_renderer.rb +3 -2
  75. data/lib/action_view/rendering.rb +26 -8
  76. data/lib/action_view/template/error.rb +14 -1
  77. data/lib/action_view/template/handlers/builder.rb +4 -4
  78. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  79. data/lib/action_view/template/handlers/erb.rb +73 -1
  80. data/lib/action_view/template/handlers.rb +1 -1
  81. data/lib/action_view/template/html.rb +1 -1
  82. data/lib/action_view/template/raw_file.rb +1 -1
  83. data/lib/action_view/template/renderable.rb +8 -2
  84. data/lib/action_view/template/resolver.rb +9 -3
  85. data/lib/action_view/template/text.rb +1 -1
  86. data/lib/action_view/template/types.rb +25 -34
  87. data/lib/action_view/template.rb +278 -55
  88. data/lib/action_view/template_path.rb +2 -0
  89. data/lib/action_view/test_case.rb +181 -28
  90. data/lib/action_view/unbound_template.rb +17 -7
  91. data/lib/action_view/version.rb +1 -1
  92. data/lib/action_view/view_paths.rb +15 -24
  93. data/lib/action_view.rb +4 -1
  94. metadata +26 -26
  95. data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -7,8 +7,9 @@ require "action_view/helpers/asset_url_helper"
7
7
  require "action_view/helpers/tag_helper"
8
8
 
9
9
  module ActionView
10
- # = Action View Asset Tag Helpers
11
10
  module Helpers # :nodoc:
11
+ # = Action View Asset Tag \Helpers
12
+ #
12
13
  # This module provides methods for generating HTML that links views to assets such
13
14
  # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
14
15
  # the assets exist before linking to them:
@@ -41,13 +42,14 @@ module ActionView
41
42
  # When the Asset Pipeline is enabled, you can pass the name of your manifest as
42
43
  # source, and include other JavaScript or CoffeeScript files inside the manifest.
43
44
  #
44
- # If the server supports Early Hints, header links for these assets will be
45
- # automatically pushed.
45
+ # If the server supports HTTP Early Hints, and the +defer+ option is not
46
+ # enabled, \Rails will push a <tt>103 Early Hints</tt> response that links
47
+ # to the assets.
46
48
  #
47
49
  # ==== Options
48
50
  #
49
51
  # When the last parameter is a hash you can add HTML attributes using that
50
- # parameter. The following options are supported:
52
+ # parameter. This includes but is not limited to the following options:
51
53
  #
52
54
  # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
53
55
  # already exists. This only applies for relative URLs.
@@ -59,6 +61,22 @@ module ActionView
59
61
  # when it is set to true.
60
62
  # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
61
63
  # you have Content Security Policy enabled.
64
+ # * <tt>:async</tt> - When set to +true+, adds the +async+ HTML
65
+ # attribute, allowing the script to be fetched in parallel to be parsed
66
+ # and evaluated as soon as possible.
67
+ # * <tt>:defer</tt> - When set to +true+, adds the +defer+ HTML
68
+ # attribute, which indicates to the browser that the script is meant to
69
+ # be executed after the document has been parsed. Additionally, prevents
70
+ # sending the Preload Links header.
71
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
72
+ # for the script. Defaults to +true+.
73
+ #
74
+ # Any other specified options will be treated as HTML attributes for the
75
+ # +script+ tag.
76
+ #
77
+ # For more information regarding how the <tt>:async</tt> and <tt>:defer</tt>
78
+ # options affect the <tt><script></tt> tag, please refer to the
79
+ # {MDN docs}[https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script].
62
80
  #
63
81
  # ==== Examples
64
82
  #
@@ -86,10 +104,17 @@ module ActionView
86
104
  #
87
105
  # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
88
106
  # # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
107
+ #
108
+ # javascript_include_tag "http://www.example.com/xmlhr.js", async: true
109
+ # # => <script src="http://www.example.com/xmlhr.js" async="async"></script>
110
+ #
111
+ # javascript_include_tag "http://www.example.com/xmlhr.js", defer: true
112
+ # # => <script src="http://www.example.com/xmlhr.js" defer="defer"></script>
89
113
  def javascript_include_tag(*sources)
90
114
  options = sources.extract_options!.stringify_keys
91
115
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
92
116
  preload_links = []
117
+ use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
93
118
  nopush = options["nopush"].nil? ? true : options.delete("nopush")
94
119
  crossorigin = options.delete("crossorigin")
95
120
  crossorigin = "anonymous" if crossorigin == true
@@ -98,7 +123,7 @@ module ActionView
98
123
 
99
124
  sources_tags = sources.uniq.map { |source|
100
125
  href = path_to_javascript(source, path_options)
101
- if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
126
+ if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
102
127
  preload_link = "<#{href}>; rel=#{rel}; as=script"
103
128
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
104
129
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -115,7 +140,7 @@ module ActionView
115
140
  content_tag("script", "", tag_options)
116
141
  }.join("\n").html_safe
117
142
 
118
- if preload_links_header
143
+ if use_preload_links_header
119
144
  send_preload_links_header(preload_links)
120
145
  end
121
146
 
@@ -130,8 +155,8 @@ module ActionView
130
155
  # set <tt>extname: false</tt> in the options.
131
156
  # You can modify the link attributes by passing a hash as the last argument.
132
157
  #
133
- # If the server supports Early Hints, header links for these assets will be
134
- # automatically pushed.
158
+ # If the server supports HTTP Early Hints, \Rails will push a <tt>103 Early
159
+ # Hints</tt> response that links to the assets.
135
160
  #
136
161
  # ==== Options
137
162
  #
@@ -143,6 +168,10 @@ module ActionView
143
168
  # that path.
144
169
  # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
145
170
  # when it is set to true.
171
+ # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
172
+ # you have Content Security Policy enabled.
173
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
174
+ # for the stylesheet. Defaults to +true+.
146
175
  #
147
176
  # ==== Examples
148
177
  #
@@ -167,9 +196,13 @@ module ActionView
167
196
  # stylesheet_link_tag "random.styles", "/css/stylish"
168
197
  # # => <link href="/assets/random.styles" rel="stylesheet" />
169
198
  # # <link href="/css/stylish.css" rel="stylesheet" />
199
+ #
200
+ # stylesheet_link_tag "style", nonce: true
201
+ # # => <link href="/assets/style.css" rel="stylesheet" nonce="..." />
170
202
  def stylesheet_link_tag(*sources)
171
203
  options = sources.extract_options!.stringify_keys
172
204
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
205
+ use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
173
206
  preload_links = []
174
207
  crossorigin = options.delete("crossorigin")
175
208
  crossorigin = "anonymous" if crossorigin == true
@@ -178,7 +211,7 @@ module ActionView
178
211
 
179
212
  sources_tags = sources.uniq.map { |source|
180
213
  href = path_to_stylesheet(source, path_options)
181
- if preload_links_header && href.present? && !href.start_with?("data:")
214
+ if use_preload_links_header && href.present? && !href.start_with?("data:")
182
215
  preload_link = "<#{href}>; rel=preload; as=style"
183
216
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
184
217
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -190,6 +223,9 @@ module ActionView
190
223
  "crossorigin" => crossorigin,
191
224
  "href" => href
192
225
  }.merge!(options)
226
+ if tag_options["nonce"] == true
227
+ tag_options["nonce"] = content_security_policy_nonce
228
+ end
193
229
 
194
230
  if apply_stylesheet_media_default && tag_options["media"].blank?
195
231
  tag_options["media"] = "screen"
@@ -198,7 +234,7 @@ module ActionView
198
234
  tag(:link, tag_options)
199
235
  }.join("\n").html_safe
200
236
 
201
- if preload_links_header
237
+ if use_preload_links_header
202
238
  send_preload_links_header(preload_links)
203
239
  end
204
240
 
@@ -327,13 +363,13 @@ module ActionView
327
363
  nopush = options.delete(:nopush) || false
328
364
  rel = mime_type == "module" ? "modulepreload" : "preload"
329
365
 
330
- link_tag = tag.link(**{
366
+ link_tag = tag.link(
331
367
  rel: rel,
332
368
  href: href,
333
369
  as: as_type,
334
370
  type: mime_type,
335
- crossorigin: crossorigin
336
- }.merge!(options.symbolize_keys))
371
+ crossorigin: crossorigin,
372
+ **options.symbolize_keys)
337
373
 
338
374
  preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
339
375
  preload_link += "; type=#{mime_type}" if mime_type
@@ -354,8 +390,8 @@ module ActionView
354
390
  # You can add HTML attributes using the +options+. The +options+ supports
355
391
  # additional keys for convenience and conformance:
356
392
  #
357
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
358
- # width="30" and height="45", and "50" becomes width="50" and height="50".
393
+ # * <tt>:size</tt> - Supplied as <tt>"#{width}x#{height}"</tt> or <tt>"#{number}"</tt>, so <tt>"30x45"</tt> becomes
394
+ # <tt>width="30" height="45"</tt>, and <tt>"50"</tt> becomes <tt>width="50" height="50"</tt>.
359
395
  # <tt>:size</tt> will be ignored if the value is not in the correct format.
360
396
  # * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
361
397
  # pairs, each image path will be expanded before the list is formatted as a string.
@@ -396,7 +432,7 @@ module ActionView
396
432
  check_for_image_tag_errors(options)
397
433
  skip_pipeline = options.delete(:skip_pipeline)
398
434
 
399
- options[:src] = resolve_image_source(source, skip_pipeline)
435
+ options[:src] = resolve_asset_source("image", source, skip_pipeline)
400
436
 
401
437
  if options[:srcset] && !options[:srcset].is_a?(String)
402
438
  options[:srcset] = options[:srcset].map do |src_path, size|
@@ -413,11 +449,72 @@ module ActionView
413
449
  tag("img", options)
414
450
  end
415
451
 
452
+ # Returns an HTML picture tag for the +sources+. If +sources+ is a string,
453
+ # a single picture tag will be returned. If +sources+ is an array, a picture
454
+ # tag with nested source tags for each source will be returned. The
455
+ # +sources+ can be full paths, files that exist in your public images
456
+ # directory, or Active Storage attachments. Since the picture tag requires
457
+ # an img tag, the last element you provide will be used for the img tag.
458
+ # For complete control over the picture tag, a block can be passed, which
459
+ # will populate the contents of the tag accordingly.
460
+ #
461
+ # ==== Options
462
+ #
463
+ # When the last parameter is a hash you can add HTML attributes using that
464
+ # parameter. Apart from all the HTML supported options, the following are supported:
465
+ #
466
+ # * <tt>:image</tt> - Hash of options that are passed directly to the +image_tag+ helper.
467
+ #
468
+ # ==== Examples
469
+ #
470
+ # picture_tag("picture.webp")
471
+ # # => <picture><img src="/images/picture.webp" /></picture>
472
+ # picture_tag("gold.png", :image => { :size => "20" })
473
+ # # => <picture><img height="20" src="/images/gold.png" width="20" /></picture>
474
+ # picture_tag("gold.png", :image => { :size => "45x70" })
475
+ # # => <picture><img height="70" src="/images/gold.png" width="45" /></picture>
476
+ # picture_tag("picture.webp", "picture.png")
477
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img src="/images/picture.png" /></picture>
478
+ # picture_tag("picture.webp", "picture.png", :image => { alt: "Image" })
479
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
480
+ # picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" })
481
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
482
+ # picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") }
483
+ # # => <picture class="my-class"><source srcset="/images/picture.webp" /><img alt="Image" src="/images/picture.png" /></picture>
484
+ # picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") }
485
+ # # => <picture><source srcset="/images/picture-small.webp" media="(min-width: 600px)" /><source srcset="/images/picture-big.webp" /><img alt="Image" src="/images/picture.png" /></picture>
486
+ #
487
+ # Active Storage blobs (images that are uploaded by the users of your app):
488
+ #
489
+ # picture_tag(user.profile_picture)
490
+ # # => <picture><img src="/rails/active_storage/blobs/.../profile_picture.webp" /></picture>
491
+ def picture_tag(*sources, &block)
492
+ sources.flatten!
493
+ options = sources.extract_options!.symbolize_keys
494
+ image_options = options.delete(:image) || {}
495
+ skip_pipeline = options.delete(:skip_pipeline)
496
+
497
+ content_tag("picture", options) do
498
+ if block.present?
499
+ capture(&block).html_safe
500
+ elsif sources.size <= 1
501
+ image_tag(sources.last, image_options)
502
+ else
503
+ source_tags = sources.map do |source|
504
+ tag("source",
505
+ srcset: resolve_asset_source("image", source, skip_pipeline),
506
+ type: Template::Types[File.extname(source)[1..]]&.to_s)
507
+ end
508
+ safe_join(source_tags << image_tag(sources.last, image_options))
509
+ end
510
+ end
511
+ end
512
+
416
513
  # Returns an HTML video tag for the +sources+. If +sources+ is a string,
417
514
  # a single video tag will be returned. If +sources+ is an array, a video
418
515
  # tag with nested source tags for each source will be returned. The
419
- # +sources+ can be full paths or files that exist in your public videos
420
- # directory.
516
+ # +sources+ can be full paths, files that exist in your public videos
517
+ # directory, or Active Storage attachments.
421
518
  #
422
519
  # ==== Options
423
520
  #
@@ -426,8 +523,8 @@ module ActionView
426
523
  #
427
524
  # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
428
525
  # before the video loads. The path is calculated like the +src+ of +image_tag+.
429
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
430
- # width="30" and height="45", and "50" becomes width="50" and height="50".
526
+ # * <tt>:size</tt> - Supplied as <tt>"#{width}x#{height}"</tt> or <tt>"#{number}"</tt>, so <tt>"30x45"</tt> becomes
527
+ # <tt>width="30" height="45"</tt>, and <tt>"50"</tt> becomes <tt>width="50" height="50"</tt>.
431
528
  # <tt>:size</tt> will be ignored if the value is not in the correct format.
432
529
  # * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
433
530
  # the <tt>:poster</tt> option instead using an asset in the public folder.
@@ -456,6 +553,11 @@ module ActionView
456
553
  # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
457
554
  # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
458
555
  # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
556
+ #
557
+ # Active Storage blobs (videos that are uploaded by the users of your app):
558
+ #
559
+ # video_tag(user.intro_video)
560
+ # # => <video src="/rails/active_storage/blobs/.../intro_video.mp4"></video>
459
561
  def video_tag(*sources)
460
562
  options = sources.extract_options!.symbolize_keys
461
563
  public_poster_folder = options.delete(:poster_skip_pipeline)
@@ -469,8 +571,8 @@ module ActionView
469
571
  # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
470
572
  # a single audio tag will be returned. If +sources+ is an array, an audio
471
573
  # tag with nested source tags for each source will be returned. The
472
- # +sources+ can be full paths or files that exist in your public audios
473
- # directory.
574
+ # +sources+ can be full paths, files that exist in your public audios
575
+ # directory, or Active Storage attachments.
474
576
  #
475
577
  # When the last parameter is a hash you can add HTML attributes using that
476
578
  # parameter.
@@ -483,6 +585,11 @@ module ActionView
483
585
  # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
484
586
  # audio_tag("sound.wav", "sound.mid")
485
587
  # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
588
+ #
589
+ # Active Storage blobs (audios that are uploaded by the users of your app):
590
+ #
591
+ # audio_tag(user.name_pronunciation_audio)
592
+ # # => <audio src="/rails/active_storage/blobs/.../name_pronunciation_audio.mp3"></audio>
486
593
  def audio_tag(*sources)
487
594
  multiple_sources_tag_builder("audio", sources)
488
595
  end
@@ -497,29 +604,29 @@ module ActionView
497
604
 
498
605
  if sources.size > 1
499
606
  content_tag(type, options) do
500
- safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
607
+ safe_join sources.map { |source| tag("source", src: resolve_asset_source(type, source, skip_pipeline)) }
501
608
  end
502
609
  else
503
- options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
610
+ options[:src] = resolve_asset_source(type, sources.first, skip_pipeline)
504
611
  content_tag(type, nil, options)
505
612
  end
506
613
  end
507
614
 
508
- def resolve_image_source(source, skip_pipeline)
615
+ def resolve_asset_source(asset_type, source, skip_pipeline)
509
616
  if source.is_a?(Symbol) || source.is_a?(String)
510
- path_to_image(source, skip_pipeline: skip_pipeline)
617
+ path_to_asset(source, type: asset_type.to_sym, skip_pipeline: skip_pipeline)
511
618
  else
512
619
  polymorphic_url(source)
513
620
  end
514
621
  rescue NoMethodError => e
515
- raise ArgumentError, "Can't resolve image into URL: #{e}"
622
+ raise ArgumentError, "Can't resolve #{asset_type} into URL: #{e}"
516
623
  end
517
624
 
518
625
  def extract_dimensions(size)
519
626
  size = size.to_s
520
- if /\A(\d+|\d+.\d+)x(\d+|\d+.\d+)\z/.match?(size)
627
+ if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size)
521
628
  size.split("x")
522
- elsif /\A(\d+|\d+.\d+)\z/.match?(size)
629
+ elsif /\A\d+(?:\.\d+)?\z/.match?(size)
523
630
  [size, size]
524
631
  end
525
632
  end
@@ -540,43 +647,32 @@ module ActionView
540
647
  end
541
648
  end
542
649
 
543
- MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
650
+ # Some HTTP client and proxies have a 4kiB header limit, but more importantly
651
+ # including preload links has diminishing returns so it's best to not go overboard
652
+ MAX_HEADER_SIZE = 1_000 # :nodoc:
653
+
544
654
  def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
545
655
  return if preload_links.empty?
546
- return if respond_to?(:response) && response&.sending?
656
+ response_present = respond_to?(:response) && response
657
+ return if response_present && response.sending?
547
658
 
548
659
  if respond_to?(:request) && request
549
- request.send_early_hints("Link" => preload_links.join("\n"))
660
+ request.send_early_hints("link" => preload_links.join(","))
550
661
  end
551
662
 
552
- if respond_to?(:response) && response
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
-
663
+ if response_present
664
+ header = +response.headers["link"].to_s
565
665
  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
666
+ break if header.bytesize + link.bytesize > max_header_size
667
+
668
+ if header.empty?
669
+ header << link
571
670
  else
572
- header << "\n"
573
- last_line_size = 0
671
+ header << "," << link
574
672
  end
575
- header << link
576
- last_line_size += link.bytesize
577
673
  end
578
674
 
579
- response.headers["Link"] = header
675
+ response.headers["link"] = header
580
676
  end
581
677
  end
582
678
  end
@@ -3,8 +3,9 @@
3
3
  require "zlib"
4
4
 
5
5
  module ActionView
6
- # = Action View Asset URL Helpers
7
6
  module Helpers # :nodoc:
7
+ # = Action View Asset URL \Helpers
8
+ #
8
9
  # This module provides methods for generating asset paths and
9
10
  # URLs.
10
11
  #
@@ -16,8 +17,8 @@ module ActionView
16
17
  #
17
18
  # === Using asset hosts
18
19
  #
19
- # By default, Rails links to these assets on the current host in the public
20
- # folder, but you can direct Rails to link to assets from a dedicated asset
20
+ # By default, \Rails links to these assets on the current host in the public
21
+ # folder, but you can direct \Rails to link to assets from a dedicated asset
21
22
  # server by setting <tt>ActionController::Base.asset_host</tt> in the application
22
23
  # configuration, typically in <tt>config/environments/production.rb</tt>.
23
24
  # For example, you'd define <tt>assets.example.com</tt> to be your asset
@@ -196,7 +197,7 @@ module ActionView
196
197
  source = "#{source}#{extname}"
197
198
  end
198
199
 
199
- if source[0] != ?/
200
+ unless source.start_with?(?/)
200
201
  if options[:skip_pipeline]
201
202
  source = public_compute_asset_path(source, options)
202
203
  else
@@ -372,7 +373,7 @@ module ActionView
372
373
  # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
373
374
  #
374
375
  # If you have images as application resources this method may conflict with their named routes.
375
- # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
376
+ # The alias +path_to_image+ is provided to avoid that. \Rails uses the alias internally, and
376
377
  # plugin authors are encouraged to do so.
377
378
  def image_path(source, options = {})
378
379
  path_to_asset(source, { type: :image }.merge!(options))
@@ -3,8 +3,8 @@
3
3
  require "set"
4
4
 
5
5
  module ActionView
6
- # = Action View Atom Feed Helpers
7
6
  module Helpers # :nodoc:
7
+ # = Action View Atom Feed \Helpers
8
8
  module AtomFeedHelper
9
9
  # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
10
10
  # template languages).
@@ -82,9 +82,9 @@ module ActionView
82
82
  # end
83
83
  #
84
84
  # The Atom spec defines five elements (content rights title subtitle
85
- # summary) which may directly contain xhtml content if type: 'xhtml'
85
+ # summary) which may directly contain XHTML content if type: 'xhtml'
86
86
  # is specified as an attribute. If so, this helper will take care of
87
- # the enclosing div and xhtml namespace declaration. Example usage:
87
+ # the enclosing div and XHTML namespace declaration. Example usage:
88
88
  #
89
89
  # entry.summary type: 'xhtml' do |xhtml|
90
90
  # xhtml.p pluralize(order.line_items.count, "line item")
@@ -102,7 +102,7 @@ module ActionView
102
102
  options[:schema_date] = "2005" # The Atom spec copyright date
103
103
  end
104
104
 
105
- xml = options.delete(:xml) || eval("xml", block.binding)
105
+ xml = options.delete(:xml) || block.binding.local_variable_get(:xml)
106
106
  xml.instruct!
107
107
  if options[:instruct]
108
108
  options[:instruct].each do |target, attrs|
@@ -134,7 +134,7 @@ module ActionView
134
134
  end
135
135
 
136
136
  private
137
- # Delegate to xml builder, first wrapping the element in an xhtml
137
+ # Delegate to XML Builder, first wrapping the element in an XHTML
138
138
  # namespaced div element if the method and arguments indicate
139
139
  # that an xhtml_block? is desired.
140
140
  def method_missing(method, *arguments, &block)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
- # = Action View Cache Helper
5
4
  module Helpers # :nodoc:
5
+ # = Action View Cache \Helpers
6
6
  module CacheHelper
7
7
  class UncacheableFragmentError < StandardError; end
8
8
 
@@ -76,11 +76,11 @@ module ActionView
76
76
  # render 'comments/comments'
77
77
  # render('comments/comments')
78
78
  #
79
- # render "header" translates to render("comments/header")
79
+ # render "header" # translates to render("comments/header")
80
80
  #
81
- # render(@topic) translates to render("topics/topic")
82
- # render(topics) translates to render("topics/topic")
83
- # render(message.topics) translates to render("topics/topic")
81
+ # render(@topic) # translates to render("topics/topic")
82
+ # render(topics) # translates to render("topics/topic")
83
+ # render(message.topics) # translates to render("topics/topic")
84
84
  #
85
85
  # It's not possible to derive all render calls like that, though.
86
86
  # Here are a few examples of things that can't be derived:
@@ -281,14 +281,8 @@ module ActionView
281
281
  controller.read_fragment(name, options)
282
282
  end
283
283
 
284
- def write_fragment_for(name, options)
285
- pos = output_buffer.length
286
- yield
287
- output_safe = output_buffer.html_safe?
288
- fragment = output_buffer.slice!(pos..-1)
289
- if output_safe
290
- self.output_buffer = output_buffer.class.new(output_buffer)
291
- end
284
+ def write_fragment_for(name, options, &block)
285
+ fragment = output_buffer.capture(&block)
292
286
  controller.write_fragment(name, fragment, options)
293
287
  end
294
288
 
@@ -3,18 +3,22 @@
3
3
  require "active_support/core_ext/string/output_safety"
4
4
 
5
5
  module ActionView
6
- # = Action View Capture Helper
7
6
  module Helpers # :nodoc:
8
- # CaptureHelper exposes methods to let you extract generated markup which
7
+ # = Action View Capture \Helpers
8
+ #
9
+ # \CaptureHelper exposes methods to let you extract generated markup which
9
10
  # can be used in other parts of a template or layout file.
10
11
  #
11
- # It provides a method to capture blocks into variables through capture and
12
- # a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for].
12
+ # It provides a method to capture blocks into variables through #capture and
13
+ # a way to capture a block of markup for use in a layout through #content_for.
14
+ #
15
+ # As well as provides a method when using streaming responses through #provide.
16
+ # See ActionController::Streaming for more information.
13
17
  module CaptureHelper
14
- # The capture method extracts part of a template as a String object.
18
+ # The capture method extracts part of a template as a string object.
15
19
  # You can then use this object anywhere in your templates, layout, or helpers.
16
20
  #
17
- # The capture method can be used in ERB templates...
21
+ # The capture method can be used in \ERB templates...
18
22
  #
19
23
  # <% @greeting = capture do %>
20
24
  # Welcome to my shiny new web page! The date and time is
@@ -40,11 +44,24 @@ module ActionView
40
44
  #
41
45
  # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
42
46
  #
43
- def capture(*args)
47
+ def capture(*args, &block)
44
48
  value = nil
45
- buffer = with_output_buffer { value = yield(*args) }
46
- if (string = buffer.presence || value) && string.is_a?(String)
47
- ERB::Util.html_escape string
49
+ @output_buffer ||= ActionView::OutputBuffer.new
50
+ buffer = @output_buffer.capture { value = yield(*args) }
51
+
52
+ string = if @output_buffer.equal?(value)
53
+ buffer
54
+ else
55
+ buffer.presence || value
56
+ end
57
+
58
+ case string
59
+ when OutputBuffer
60
+ string.to_s
61
+ when ActiveSupport::SafeBuffer
62
+ string
63
+ when String
64
+ ERB::Util.html_escape(string)
48
65
  end
49
66
  end
50
67
 
@@ -172,6 +189,8 @@ module ActionView
172
189
  # concatenate several times to the same buffer when rendering a given
173
190
  # template, you should use +content_for+, if not, use +provide+ to tell
174
191
  # the layout to stop looking for more contents.
192
+ #
193
+ # See ActionController::Streaming for more information.
175
194
  def provide(name, content = nil, &block)
176
195
  content = capture(&block) if block_given?
177
196
  result = @view_flow.append!(name, content) if content
@@ -179,6 +198,7 @@ module ActionView
179
198
  end
180
199
 
181
200
  # <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
201
+ #
182
202
  # Useful to render parts of your layout differently based on what is in your views.
183
203
  #
184
204
  # <%# This is the layout %>