actionview 7.0.1 → 7.1.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +281 -202
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  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 +2 -2
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +133 -48
  16. data/lib/action_view/helpers/asset_url_helper.rb +13 -12
  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 +26 -12
  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 +3 -3
  24. data/lib/action_view/helpers/date_helper.rb +76 -64
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +62 -31
  27. data/lib/action_view/helpers/form_options_helper.rb +6 -3
  28. data/lib/action_view/helpers/form_tag_helper.rb +88 -44
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +15 -13
  31. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  32. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  33. data/lib/action_view/helpers/sanitize_helper.rb +34 -15
  34. data/lib/action_view/helpers/tag_helper.rb +27 -16
  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 +4 -1
  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 +33 -17
  53. data/lib/action_view/helpers/translation_helper.rb +6 -6
  54. data/lib/action_view/helpers/url_helper.rb +90 -65
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +13 -8
  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 +16 -9
  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 +21 -3
  66. data/lib/action_view/renderer/partial_renderer.rb +3 -2
  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 +24 -6
  71. data/lib/action_view/ripper_ast_parser.rb +6 -6
  72. data/lib/action_view/routing_url_for.rb +7 -4
  73. data/lib/action_view/template/error.rb +14 -1
  74. data/lib/action_view/template/handlers/builder.rb +4 -4
  75. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  76. data/lib/action_view/template/handlers/erb.rb +73 -1
  77. data/lib/action_view/template/handlers.rb +1 -1
  78. data/lib/action_view/template/html.rb +1 -1
  79. data/lib/action_view/template/raw_file.rb +1 -1
  80. data/lib/action_view/template/renderable.rb +1 -1
  81. data/lib/action_view/template/resolver.rb +15 -5
  82. data/lib/action_view/template/text.rb +1 -1
  83. data/lib/action_view/template/types.rb +25 -34
  84. data/lib/action_view/template.rb +227 -53
  85. data/lib/action_view/template_path.rb +2 -0
  86. data/lib/action_view/test_case.rb +174 -21
  87. data/lib/action_view/unbound_template.rb +15 -5
  88. data/lib/action_view/version.rb +1 -1
  89. data/lib/action_view/view_paths.rb +19 -28
  90. data/lib/action_view.rb +4 -1
  91. data/lib/assets/compiled/rails-ujs.js +36 -5
  92. metadata +27 -27
@@ -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,20 @@ 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
+ #
72
+ # Any other specified options will be treated as HTML attributes for the
73
+ # +script+ tag.
74
+ #
75
+ # For more information regarding how the <tt>:async</tt> and <tt>:defer</tt>
76
+ # options affect the <tt><script></tt> tag, please refer to the
77
+ # {MDN docs}[https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script].
62
78
  #
63
79
  # ==== Examples
64
80
  #
@@ -86,10 +102,17 @@ module ActionView
86
102
  #
87
103
  # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
88
104
  # # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
105
+ #
106
+ # javascript_include_tag "http://www.example.com/xmlhr.js", async: true
107
+ # # => <script src="http://www.example.com/xmlhr.js" async="async"></script>
108
+ #
109
+ # javascript_include_tag "http://www.example.com/xmlhr.js", defer: true
110
+ # # => <script src="http://www.example.com/xmlhr.js" defer="defer"></script>
89
111
  def javascript_include_tag(*sources)
90
112
  options = sources.extract_options!.stringify_keys
91
113
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
92
114
  preload_links = []
115
+ use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
93
116
  nopush = options["nopush"].nil? ? true : options.delete("nopush")
94
117
  crossorigin = options.delete("crossorigin")
95
118
  crossorigin = "anonymous" if crossorigin == true
@@ -98,7 +121,7 @@ module ActionView
98
121
 
99
122
  sources_tags = sources.uniq.map { |source|
100
123
  href = path_to_javascript(source, path_options)
101
- if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
124
+ if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
102
125
  preload_link = "<#{href}>; rel=#{rel}; as=script"
103
126
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
104
127
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -115,7 +138,7 @@ module ActionView
115
138
  content_tag("script", "", tag_options)
116
139
  }.join("\n").html_safe
117
140
 
118
- if preload_links_header
141
+ if use_preload_links_header
119
142
  send_preload_links_header(preload_links)
120
143
  end
121
144
 
@@ -130,8 +153,8 @@ module ActionView
130
153
  # set <tt>extname: false</tt> in the options.
131
154
  # You can modify the link attributes by passing a hash as the last argument.
132
155
  #
133
- # If the server supports Early Hints header links for these assets will be
134
- # automatically pushed.
156
+ # If the server supports HTTP Early Hints, \Rails will push a <tt>103 Early
157
+ # Hints</tt> response that links to the assets.
135
158
  #
136
159
  # ==== Options
137
160
  #
@@ -170,6 +193,7 @@ module ActionView
170
193
  def stylesheet_link_tag(*sources)
171
194
  options = sources.extract_options!.stringify_keys
172
195
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
196
+ use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
173
197
  preload_links = []
174
198
  crossorigin = options.delete("crossorigin")
175
199
  crossorigin = "anonymous" if crossorigin == true
@@ -178,7 +202,7 @@ module ActionView
178
202
 
179
203
  sources_tags = sources.uniq.map { |source|
180
204
  href = path_to_stylesheet(source, path_options)
181
- if preload_links_header && href.present? && !href.start_with?("data:")
205
+ if use_preload_links_header && href.present? && !href.start_with?("data:")
182
206
  preload_link = "<#{href}>; rel=preload; as=style"
183
207
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
184
208
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -198,7 +222,7 @@ module ActionView
198
222
  tag(:link, tag_options)
199
223
  }.join("\n").html_safe
200
224
 
201
- if preload_links_header
225
+ if use_preload_links_header
202
226
  send_preload_links_header(preload_links)
203
227
  end
204
228
 
@@ -325,16 +349,17 @@ module ActionView
325
349
  crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
326
350
  integrity = options[:integrity]
327
351
  nopush = options.delete(:nopush) || false
352
+ rel = mime_type == "module" ? "modulepreload" : "preload"
328
353
 
329
354
  link_tag = tag.link(**{
330
- rel: "preload",
355
+ rel: rel,
331
356
  href: href,
332
357
  as: as_type,
333
358
  type: mime_type,
334
359
  crossorigin: crossorigin
335
360
  }.merge!(options.symbolize_keys))
336
361
 
337
- preload_link = "<#{href}>; rel=preload; as=#{as_type}"
362
+ preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
338
363
  preload_link += "; type=#{mime_type}" if mime_type
339
364
  preload_link += "; crossorigin=#{crossorigin}" if crossorigin
340
365
  preload_link += "; integrity=#{integrity}" if integrity
@@ -395,7 +420,7 @@ module ActionView
395
420
  check_for_image_tag_errors(options)
396
421
  skip_pipeline = options.delete(:skip_pipeline)
397
422
 
398
- options[:src] = resolve_image_source(source, skip_pipeline)
423
+ options[:src] = resolve_asset_source("image", source, skip_pipeline)
399
424
 
400
425
  if options[:srcset] && !options[:srcset].is_a?(String)
401
426
  options[:srcset] = options[:srcset].map do |src_path, size|
@@ -412,11 +437,72 @@ module ActionView
412
437
  tag("img", options)
413
438
  end
414
439
 
440
+ # Returns an HTML picture tag for the +sources+. If +sources+ is a string,
441
+ # a single picture tag will be returned. If +sources+ is an array, a picture
442
+ # tag with nested source tags for each source will be returned. The
443
+ # +sources+ can be full paths, files that exist in your public images
444
+ # directory, or Active Storage attachments. Since the picture tag requires
445
+ # an img tag, the last element you provide will be used for the img tag.
446
+ # For complete control over the picture tag, a block can be passed, which
447
+ # will populate the contents of the tag accordingly.
448
+ #
449
+ # ==== Options
450
+ #
451
+ # When the last parameter is a hash you can add HTML attributes using that
452
+ # parameter. Apart from all the HTML supported options, the following are supported:
453
+ #
454
+ # * <tt>:image</tt> - Hash of options that are passed directly to the +image_tag+ helper.
455
+ #
456
+ # ==== Examples
457
+ #
458
+ # picture_tag("picture.webp")
459
+ # # => <picture><img src="/images/picture.webp" /></picture>
460
+ # picture_tag("gold.png", :image => { :size => "20" })
461
+ # # => <picture><img height="20" src="/images/gold.png" width="20" /></picture>
462
+ # picture_tag("gold.png", :image => { :size => "45x70" })
463
+ # # => <picture><img height="70" src="/images/gold.png" width="45" /></picture>
464
+ # picture_tag("picture.webp", "picture.png")
465
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img src="/images/picture.png" /></picture>
466
+ # picture_tag("picture.webp", "picture.png", :image => { alt: "Image" })
467
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
468
+ # picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" })
469
+ # # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
470
+ # picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") }
471
+ # # => <picture class="my-class"><source srcset="/images/picture.webp" /><img alt="Image" src="/images/picture.png" /></picture>
472
+ # 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") }
473
+ # # => <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>
474
+ #
475
+ # Active Storage blobs (images that are uploaded by the users of your app):
476
+ #
477
+ # picture_tag(user.profile_picture)
478
+ # # => <picture><img src="/rails/active_storage/blobs/.../profile_picture.webp" /></picture>
479
+ def picture_tag(*sources, &block)
480
+ sources.flatten!
481
+ options = sources.extract_options!.symbolize_keys
482
+ image_options = options.delete(:image) || {}
483
+ skip_pipeline = options.delete(:skip_pipeline)
484
+
485
+ content_tag("picture", options) do
486
+ if block.present?
487
+ capture(&block).html_safe
488
+ elsif sources.size <= 1
489
+ image_tag(sources.last, image_options)
490
+ else
491
+ source_tags = sources.map do |source|
492
+ tag("source",
493
+ srcset: resolve_asset_source("image", source, skip_pipeline),
494
+ type: Template::Types[File.extname(source)[1..]]&.to_s)
495
+ end
496
+ safe_join(source_tags << image_tag(sources.last, image_options))
497
+ end
498
+ end
499
+ end
500
+
415
501
  # Returns an HTML video tag for the +sources+. If +sources+ is a string,
416
502
  # a single video tag will be returned. If +sources+ is an array, a video
417
503
  # tag with nested source tags for each source will be returned. The
418
- # +sources+ can be full paths or files that exist in your public videos
419
- # directory.
504
+ # +sources+ can be full paths, files that exist in your public videos
505
+ # directory, or Active Storage attachments.
420
506
  #
421
507
  # ==== Options
422
508
  #
@@ -455,6 +541,11 @@ module ActionView
455
541
  # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
456
542
  # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
457
543
  # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
544
+ #
545
+ # Active Storage blobs (videos that are uploaded by the users of your app):
546
+ #
547
+ # video_tag(user.intro_video)
548
+ # # => <video src="/rails/active_storage/blobs/.../intro_video.mp4"></video>
458
549
  def video_tag(*sources)
459
550
  options = sources.extract_options!.symbolize_keys
460
551
  public_poster_folder = options.delete(:poster_skip_pipeline)
@@ -468,8 +559,8 @@ module ActionView
468
559
  # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
469
560
  # a single audio tag will be returned. If +sources+ is an array, an audio
470
561
  # tag with nested source tags for each source will be returned. The
471
- # +sources+ can be full paths or files that exist in your public audios
472
- # directory.
562
+ # +sources+ can be full paths, files that exist in your public audios
563
+ # directory, or Active Storage attachments.
473
564
  #
474
565
  # When the last parameter is a hash you can add HTML attributes using that
475
566
  # parameter.
@@ -482,6 +573,11 @@ module ActionView
482
573
  # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
483
574
  # audio_tag("sound.wav", "sound.mid")
484
575
  # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
576
+ #
577
+ # Active Storage blobs (audios that are uploaded by the users of your app):
578
+ #
579
+ # audio_tag(user.name_pronunciation_audio)
580
+ # # => <audio src="/rails/active_storage/blobs/.../name_pronunciation_audio.mp3"></audio>
485
581
  def audio_tag(*sources)
486
582
  multiple_sources_tag_builder("audio", sources)
487
583
  end
@@ -496,29 +592,29 @@ module ActionView
496
592
 
497
593
  if sources.size > 1
498
594
  content_tag(type, options) do
499
- safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
595
+ safe_join sources.map { |source| tag("source", src: resolve_asset_source(type, source, skip_pipeline)) }
500
596
  end
501
597
  else
502
- options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
598
+ options[:src] = resolve_asset_source(type, sources.first, skip_pipeline)
503
599
  content_tag(type, nil, options)
504
600
  end
505
601
  end
506
602
 
507
- def resolve_image_source(source, skip_pipeline)
603
+ def resolve_asset_source(asset_type, source, skip_pipeline)
508
604
  if source.is_a?(Symbol) || source.is_a?(String)
509
- path_to_image(source, skip_pipeline: skip_pipeline)
605
+ path_to_asset(source, type: asset_type.to_sym, skip_pipeline: skip_pipeline)
510
606
  else
511
607
  polymorphic_url(source)
512
608
  end
513
609
  rescue NoMethodError => e
514
- raise ArgumentError, "Can't resolve image into URL: #{e}"
610
+ raise ArgumentError, "Can't resolve #{asset_type} into URL: #{e}"
515
611
  end
516
612
 
517
613
  def extract_dimensions(size)
518
614
  size = size.to_s
519
- if /\A\d+x\d+\z/.match?(size)
615
+ if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size)
520
616
  size.split("x")
521
- elsif /\A\d+\z/.match?(size)
617
+ elsif /\A\d+(?:\.\d+)?\z/.match?(size)
522
618
  [size, size]
523
619
  end
524
620
  end
@@ -539,40 +635,29 @@ module ActionView
539
635
  end
540
636
  end
541
637
 
542
- MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
638
+ # Some HTTP client and proxies have a 4kiB header limit, but more importantly
639
+ # including preload links has diminishing returns so it's best to not go overboard
640
+ MAX_HEADER_SIZE = 1_000 # :nodoc:
641
+
543
642
  def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
544
643
  return if preload_links.empty?
545
- return if respond_to?(:response) && response.sending?
644
+ response_present = respond_to?(:response) && response
645
+ return if response_present && response.sending?
546
646
 
547
647
  if respond_to?(:request) && request
548
648
  request.send_early_hints("Link" => preload_links.join("\n"))
549
649
  end
550
650
 
551
- if respond_to?(:response) && response
552
- header = response.headers["Link"]
553
- header = header ? header.dup : +""
554
-
555
- # rindex count characters not bytes, but we assume non-ascii characters
556
- # are rare in urls, and we have a 192 bytes margin.
557
- last_line_offset = header.rindex("\n")
558
- last_line_size = if last_line_offset
559
- header.bytesize - last_line_offset
560
- else
561
- header.bytesize
562
- end
563
-
651
+ if response_present
652
+ header = +response.headers["Link"].to_s
564
653
  preload_links.each do |link|
565
- if link.bytesize + last_line_size + 1 < max_header_size
566
- unless header.empty?
567
- header << ","
568
- last_line_size += 1
569
- end
654
+ break if header.bytesize + link.bytesize > max_header_size
655
+
656
+ if header.empty?
657
+ header << link
570
658
  else
571
- header << "\n"
572
- last_line_size = 0
659
+ header << "," << link
573
660
  end
574
- header << link
575
- last_line_size += link.bytesize
576
661
  end
577
662
 
578
663
  response.headers["Link"] = header
@@ -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
@@ -219,7 +220,7 @@ module ActionView
219
220
 
220
221
  # Computes the full URL to an asset in the public directory. This
221
222
  # will use +asset_path+ internally, so most of their behaviors
222
- # will be the same. If :host options is set, it overwrites global
223
+ # will be the same. If +:host+ options is set, it overwrites global
223
224
  # +config.action_controller.asset_host+ setting.
224
225
  #
225
226
  # All other options provided are forwarded to +asset_path+ call.
@@ -324,7 +325,7 @@ module ActionView
324
325
 
325
326
  # Computes the full URL to a JavaScript asset in the public javascripts directory.
326
327
  # 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
328
+ # Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
328
329
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
329
330
  #
330
331
  # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
@@ -351,7 +352,7 @@ module ActionView
351
352
 
352
353
  # Computes the full URL to a stylesheet asset in the public stylesheets directory.
353
354
  # 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
355
+ # Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
355
356
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
356
357
  #
357
358
  # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
@@ -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))
@@ -381,7 +382,7 @@ module ActionView
381
382
 
382
383
  # Computes the full URL to an image asset.
383
384
  # 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
385
+ # Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
385
386
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
386
387
  #
387
388
  # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
@@ -407,7 +408,7 @@ module ActionView
407
408
 
408
409
  # Computes the full URL to a video asset in the public videos directory.
409
410
  # 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
411
+ # Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
411
412
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
412
413
  #
413
414
  # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
@@ -433,7 +434,7 @@ module ActionView
433
434
 
434
435
  # Computes the full URL to an audio asset in the public audios directory.
435
436
  # 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
437
+ # Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
437
438
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
438
439
  #
439
440
  # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
@@ -458,7 +459,7 @@ module ActionView
458
459
 
459
460
  # Computes the full URL to a font asset.
460
461
  # 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
462
+ # Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
462
463
  # options is set, it overwrites global +config.action_controller.asset_host+ setting.
463
464
  #
464
465
  # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
@@ -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
 
@@ -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,18 @@ 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
+ case string = buffer.presence || value
53
+ when OutputBuffer
54
+ string.to_s
55
+ when ActiveSupport::SafeBuffer
56
+ string
57
+ when String
58
+ ERB::Util.html_escape(string)
48
59
  end
49
60
  end
50
61
 
@@ -121,7 +132,7 @@ module ActionView
121
132
  # <li><%= link_to 'Home', action: 'index' %></li>
122
133
  # <% end %>
123
134
  #
124
- # And in another place:
135
+ # And in another place:
125
136
  #
126
137
  # <% content_for :navigation do %>
127
138
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -137,7 +148,7 @@ module ActionView
137
148
  # <li><%= link_to 'Home', action: 'index' %></li>
138
149
  # <% end %>
139
150
  #
140
- # <%# Add some other content, or use a different template: %>
151
+ # <%# Add some other content, or use a different template: %>
141
152
  #
142
153
  # <% content_for :navigation, flush: true do %>
143
154
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -172,6 +183,8 @@ module ActionView
172
183
  # concatenate several times to the same buffer when rendering a given
173
184
  # template, you should use +content_for+, if not, use +provide+ to tell
174
185
  # the layout to stop looking for more contents.
186
+ #
187
+ # See ActionController::Streaming for more information.
175
188
  def provide(name, content = nil, &block)
176
189
  content = capture(&block) if block_given?
177
190
  result = @view_flow.append!(name, content) if content
@@ -179,6 +192,7 @@ module ActionView
179
192
  end
180
193
 
181
194
  # <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
195
+ #
182
196
  # Useful to render parts of your layout differently based on what is in your views.
183
197
  #
184
198
  # <%# This is the layout %>