actionview 6.1.7.2 → 7.1.3

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -277
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +3 -3
  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 +37 -19
  8. data/lib/action_view/buffers.rb +107 -9
  9. data/lib/action_view/cache_expiry.rb +48 -37
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  12. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  13. data/lib/action_view/dependency_tracker.rb +6 -147
  14. data/lib/action_view/deprecator.rb +7 -0
  15. data/lib/action_view/digestor.rb +8 -5
  16. data/lib/action_view/flows.rb +4 -4
  17. data/lib/action_view/gem_version.rb +4 -4
  18. data/lib/action_view/helpers/active_model_helper.rb +3 -3
  19. data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
  20. data/lib/action_view/helpers/asset_url_helper.rb +22 -21
  21. data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
  22. data/lib/action_view/helpers/cache_helper.rb +55 -12
  23. data/lib/action_view/helpers/capture_helper.rb +34 -14
  24. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  25. data/lib/action_view/helpers/controller_helper.rb +8 -2
  26. data/lib/action_view/helpers/csp_helper.rb +3 -3
  27. data/lib/action_view/helpers/csrf_helper.rb +4 -4
  28. data/lib/action_view/helpers/date_helper.rb +123 -57
  29. data/lib/action_view/helpers/debug_helper.rb +6 -4
  30. data/lib/action_view/helpers/form_helper.rb +253 -97
  31. data/lib/action_view/helpers/form_options_helper.rb +72 -34
  32. data/lib/action_view/helpers/form_tag_helper.rb +189 -58
  33. data/lib/action_view/helpers/javascript_helper.rb +4 -5
  34. data/lib/action_view/helpers/number_helper.rb +43 -335
  35. data/lib/action_view/helpers/output_safety_helper.rb +6 -6
  36. data/lib/action_view/helpers/rendering_helper.rb +6 -7
  37. data/lib/action_view/helpers/sanitize_helper.rb +54 -24
  38. data/lib/action_view/helpers/tag_helper.rb +42 -35
  39. data/lib/action_view/helpers/tags/base.rb +16 -77
  40. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  41. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  42. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  43. data/lib/action_view/helpers/tags/collection_select.rb +4 -1
  44. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  48. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  50. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  51. data/lib/action_view/helpers/tags/select.rb +4 -1
  52. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  53. data/lib/action_view/helpers/tags/time_field.rb +11 -2
  54. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  55. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  56. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  57. data/lib/action_view/helpers/tags.rb +5 -2
  58. data/lib/action_view/helpers/text_helper.rb +180 -97
  59. data/lib/action_view/helpers/translation_helper.rb +14 -45
  60. data/lib/action_view/helpers/url_helper.rb +230 -132
  61. data/lib/action_view/helpers.rb +27 -25
  62. data/lib/action_view/layouts.rb +15 -10
  63. data/lib/action_view/log_subscriber.rb +49 -32
  64. data/lib/action_view/lookup_context.rb +58 -61
  65. data/lib/action_view/model_naming.rb +2 -2
  66. data/lib/action_view/path_registry.rb +57 -0
  67. data/lib/action_view/path_set.rb +28 -35
  68. data/lib/action_view/railtie.rb +44 -9
  69. data/lib/action_view/record_identifier.rb +16 -9
  70. data/lib/action_view/render_parser.rb +188 -0
  71. data/lib/action_view/renderer/abstract_renderer.rb +3 -3
  72. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  73. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
  74. data/lib/action_view/renderer/partial_renderer.rb +3 -36
  75. data/lib/action_view/renderer/renderer.rb +6 -4
  76. data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
  77. data/lib/action_view/renderer/template_renderer.rb +9 -4
  78. data/lib/action_view/rendering.rb +25 -7
  79. data/lib/action_view/ripper_ast_parser.rb +198 -0
  80. data/lib/action_view/routing_url_for.rb +8 -5
  81. data/lib/action_view/template/error.rb +122 -14
  82. data/lib/action_view/template/handlers/builder.rb +4 -4
  83. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  84. data/lib/action_view/template/handlers/erb.rb +79 -1
  85. data/lib/action_view/template/handlers.rb +4 -4
  86. data/lib/action_view/template/html.rb +4 -4
  87. data/lib/action_view/template/inline.rb +3 -3
  88. data/lib/action_view/template/raw_file.rb +4 -4
  89. data/lib/action_view/template/renderable.rb +1 -1
  90. data/lib/action_view/template/resolver.rb +96 -313
  91. data/lib/action_view/template/text.rb +4 -4
  92. data/lib/action_view/template/types.rb +25 -32
  93. data/lib/action_view/template.rb +245 -41
  94. data/lib/action_view/template_details.rb +66 -0
  95. data/lib/action_view/template_path.rb +66 -0
  96. data/lib/action_view/test_case.rb +182 -23
  97. data/lib/action_view/testing/resolvers.rb +11 -12
  98. data/lib/action_view/unbound_template.rb +43 -7
  99. data/lib/action_view/version.rb +1 -1
  100. data/lib/action_view/view_paths.rb +19 -28
  101. data/lib/action_view.rb +6 -4
  102. data/lib/assets/compiled/rails-ujs.js +36 -5
  103. metadata +32 -25
@@ -3,20 +3,23 @@
3
3
  require "rails-html-sanitizer"
4
4
 
5
5
  module ActionView
6
- # = Action View Sanitize Helpers
7
- module Helpers #:nodoc:
6
+ module Helpers # :nodoc:
7
+ # = Action View Sanitize \Helpers
8
+ #
8
9
  # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
9
10
  # These helper methods extend Action View making them callable within your template files.
10
11
  module SanitizeHelper
12
+ mattr_accessor :sanitizer_vendor, default: Rails::HTML4::Sanitizer
13
+
11
14
  extend ActiveSupport::Concern
15
+
12
16
  # Sanitizes HTML input, stripping all but known-safe tags and attributes.
13
17
  #
14
- # It also strips href/src attributes with unsafe protocols like
15
- # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
16
- # ASCII, and hex character references to work around these protocol filters.
17
- # All special characters will be escaped.
18
+ # It also strips +href+ / +src+ attributes with unsafe protocols like +javascript:+, while
19
+ # also protecting against attempts to use Unicode, ASCII, and hex character references to work
20
+ # around these protocol filters.
18
21
  #
19
- # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
22
+ # The default sanitizer is +Rails::HTML5::SafeListSanitizer+. See {Rails HTML
20
23
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
21
24
  #
22
25
  # Custom sanitization rules can also be provided.
@@ -26,26 +29,31 @@ module ActionView
26
29
  #
27
30
  # ==== Options
28
31
  #
29
- # * <tt>:tags</tt> - An array of allowed tags.
30
- # * <tt>:attributes</tt> - An array of allowed attributes.
31
- # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
32
+ # [+:tags+]
33
+ # An array of allowed tags.
34
+ #
35
+ # [+:attributes+]
36
+ # An array of allowed attributes.
37
+ #
38
+ # [+:scrubber+]
39
+ # A {Rails::HTML scrubber}[https://github.com/rails/rails-html-sanitizer]
32
40
  # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
33
41
  # defines custom sanitization rules. A custom scrubber takes precedence over
34
42
  # custom tags and attributes.
35
43
  #
36
44
  # ==== Examples
37
45
  #
38
- # Normal use:
46
+ # ===== Normal use
39
47
  #
40
48
  # <%= sanitize @comment.body %>
41
49
  #
42
- # Providing custom lists of permitted tags and attributes:
50
+ # ===== Providing custom lists of permitted tags and attributes
43
51
  #
44
52
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
45
53
  #
46
- # Providing a custom Rails::Html scrubber:
54
+ # ===== Providing a custom +Rails::HTML+ scrubber
47
55
  #
48
- # class CommentScrubber < Rails::Html::PermitScrubber
56
+ # class CommentScrubber < Rails::HTML::PermitScrubber
49
57
  # def initialize
50
58
  # super
51
59
  # self.tags = %w( form script comment blockquote )
@@ -57,32 +65,54 @@ module ActionView
57
65
  # end
58
66
  # end
59
67
  #
68
+ # <code></code>
69
+ #
60
70
  # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
61
71
  #
62
72
  # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
63
- # documentation about Rails::Html scrubbers.
73
+ # documentation about +Rails::HTML+ scrubbers.
64
74
  #
65
- # Providing a custom Loofah::Scrubber:
75
+ # ===== Providing a custom +Loofah::Scrubber+
66
76
  #
67
77
  # scrubber = Loofah::Scrubber.new do |node|
68
78
  # node.remove if node.name == 'script'
69
79
  # end
70
80
  #
81
+ # <code></code>
82
+ #
71
83
  # <%= sanitize @comment.body, scrubber: scrubber %>
72
84
  #
73
85
  # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
74
- # information about defining custom Loofah::Scrubber objects.
86
+ # information about defining custom +Loofah::Scrubber+ objects.
87
+ #
88
+ # ==== Global Configuration
75
89
  #
76
90
  # To set the default allowed tags or attributes across your application:
77
91
  #
78
92
  # # In config/application.rb
79
93
  # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
80
94
  # config.action_view.sanitized_allowed_attributes = ['href', 'title']
95
+ #
96
+ # The default, starting in \Rails 7.1, is to use an HTML5 parser for sanitization (if it is
97
+ # available, see NOTE below). If you wish to revert back to the previous HTML4 behavior, you
98
+ # can do so by setting the following in your application configuration:
99
+ #
100
+ # # In config/application.rb
101
+ # config.action_view.sanitizer_vendor = Rails::HTML4::Sanitizer
102
+ #
103
+ # Or, if you're upgrading from a previous version of \Rails and wish to opt into the HTML5
104
+ # behavior:
105
+ #
106
+ # # In config/application.rb
107
+ # config.action_view.sanitizer_vendor = Rails::HTML5::Sanitizer
108
+ #
109
+ # NOTE: +Rails::HTML5::Sanitizer+ is not supported on JRuby, so on JRuby platforms \Rails will
110
+ # fall back to using +Rails::HTML4::Sanitizer+.
81
111
  def sanitize(html, options = {})
82
112
  self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
83
113
  end
84
114
 
85
- # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
115
+ # Sanitizes a block of CSS code. Used by #sanitize when it comes across a style attribute.
86
116
  def sanitize_css(style)
87
117
  self.class.safe_list_sanitizer.sanitize_css(style)
88
118
  end
@@ -101,7 +131,7 @@ module ActionView
101
131
  # strip_tags("> A quote from Smith & Wesson")
102
132
  # # => &gt; A quote from Smith &amp; Wesson
103
133
  def strip_tags(html)
104
- self.class.full_sanitizer.sanitize(html)
134
+ self.class.full_sanitizer.sanitize(html)&.html_safe
105
135
  end
106
136
 
107
137
  # Strips all link tags from +html+ leaving just the link text.
@@ -121,11 +151,11 @@ module ActionView
121
151
  self.class.link_sanitizer.sanitize(html)
122
152
  end
123
153
 
124
- module ClassMethods #:nodoc:
154
+ module ClassMethods # :nodoc:
125
155
  attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
126
156
 
127
157
  def sanitizer_vendor
128
- Rails::Html::Sanitizer
158
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor
129
159
  end
130
160
 
131
161
  def sanitized_allowed_tags
@@ -136,7 +166,7 @@ module ActionView
136
166
  sanitizer_vendor.safe_list_sanitizer.allowed_attributes
137
167
  end
138
168
 
139
- # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
169
+ # Gets the Rails::HTML::FullSanitizer instance used by +strip_tags+. Replace with
140
170
  # any object that responds to +sanitize+.
141
171
  #
142
172
  # class Application < Rails::Application
@@ -146,7 +176,7 @@ module ActionView
146
176
  @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
147
177
  end
148
178
 
149
- # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
179
+ # Gets the Rails::HTML::LinkSanitizer instance used by +strip_links+.
150
180
  # Replace with any object that responds to +sanitize+.
151
181
  #
152
182
  # class Application < Rails::Application
@@ -156,7 +186,7 @@ module ActionView
156
186
  @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
157
187
  end
158
188
 
159
- # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
189
+ # Gets the Rails::HTML::SafeListSanitizer instance used by sanitize and +sanitize_css+.
160
190
  # Replace with any object that responds to +sanitize+.
161
191
  #
162
192
  # class Application < Rails::Application
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
3
4
  require "active_support/core_ext/string/output_safety"
4
5
  require "set"
6
+ require "action_view/helpers/capture_helper"
7
+ require "action_view/helpers/output_safety_helper"
5
8
 
6
9
  module ActionView
7
- # = Action View Tag Helpers
8
- module Helpers #:nodoc:
10
+ module Helpers # :nodoc:
11
+ # = Action View Tag \Helpers
12
+ #
9
13
  # Provides methods to generate HTML tags programmatically both as a modern
10
14
  # HTML5 compliant builder style and legacy XHTML compliant tags.
11
15
  module TagHelper
12
- extend ActiveSupport::Concern
13
16
  include CaptureHelper
14
17
  include OutputSafetyHelper
15
18
 
@@ -39,26 +42,35 @@ module ActionView
39
42
  PRE_CONTENT_STRINGS[:textarea] = "\n"
40
43
  PRE_CONTENT_STRINGS["textarea"] = "\n"
41
44
 
42
- class TagBuilder #:nodoc:
45
+ class TagBuilder # :nodoc:
43
46
  include CaptureHelper
44
47
  include OutputSafetyHelper
45
48
 
46
- VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
49
+ HTML_VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
50
+ SVG_SELF_CLOSING_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
47
51
 
48
52
  def initialize(view_context)
49
53
  @view_context = view_context
50
54
  end
51
55
 
56
+ # Transforms a Hash into HTML Attributes, ready to be interpolated into
57
+ # ERB.
58
+ #
59
+ # <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %> >
60
+ # # => <input type="text" aria-label="Search">
61
+ def attributes(attributes)
62
+ tag_options(attributes.to_h).to_s.strip.html_safe
63
+ end
64
+
52
65
  def p(*arguments, **options, &block)
53
66
  tag_string(:p, *arguments, **options, &block)
54
67
  end
55
68
 
56
- def tag_string(name, content = nil, **options, &block)
57
- escape = handle_deprecated_escape_options(options)
58
-
69
+ def tag_string(name, content = nil, escape: true, **options, &block)
59
70
  content = @view_context.capture(self, &block) if block_given?
60
- if VOID_ELEMENTS.include?(name) && content.nil?
61
- "<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe
71
+ self_closing = SVG_SELF_CLOSING_ELEMENTS.include?(name)
72
+ if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
73
+ "<#{name.to_s.dasherize}#{tag_options(options, escape)}#{self_closing ? " />" : ">"}".html_safe
62
74
  else
63
75
  content_tag_string(name.to_s.dasherize, content || "", options, escape)
64
76
  end
@@ -128,6 +140,8 @@ module ActionView
128
140
  when Array, Hash
129
141
  value = TagHelper.build_tag_values(value) if key.to_s == "class"
130
142
  value = escape ? safe_join(value, " ") : value.join(" ")
143
+ when Regexp
144
+ value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
131
145
  else
132
146
  value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
133
147
  end
@@ -149,27 +163,6 @@ module ActionView
149
163
  true
150
164
  end
151
165
 
152
- def handle_deprecated_escape_options(options)
153
- # The option :escape_attributes has been merged into the options hash to be
154
- # able to warn when it is used, so we need to handle default values here.
155
- escape_option_provided = options.has_key?(:escape)
156
- escape_attributes_option_provided = options.has_key?(:escape_attributes)
157
-
158
- if escape_attributes_option_provided
159
- ActiveSupport::Deprecation.warn(<<~MSG)
160
- Use of the option :escape_attributes is deprecated. It currently \
161
- escapes both names and values of tags and attributes and it is \
162
- equivalent to :escape. If any of them are enabled, the escaping \
163
- is fully enabled.
164
- MSG
165
- end
166
-
167
- return true unless escape_option_provided || escape_attributes_option_provided
168
- escape_option = options.delete(:escape)
169
- escape_attributes_option = options.delete(:escape_attributes)
170
- escape_option || escape_attributes_option
171
- end
172
-
173
166
  def method_missing(called, *args, **options, &block)
174
167
  tag_string(called, *args, **options, &block)
175
168
  end
@@ -225,7 +218,7 @@ module ActionView
225
218
  #
226
219
  # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
227
220
  #
228
- # Data attribute values are encoded to JSON, with the exception of strings, symbols and
221
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols, and
229
222
  # BigDecimals.
230
223
  # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
231
224
  # from 1.4.3.
@@ -252,9 +245,23 @@ module ActionView
252
245
  # # A void element:
253
246
  # tag.br # => <br>
254
247
  #
248
+ # === Building HTML attributes
249
+ #
250
+ # Transforms a Hash into HTML attributes, ready to be interpolated into
251
+ # ERB. Includes or omits boolean attributes based on their truthiness.
252
+ # Transforms keys nested within
253
+ # <tt>aria:</tt> or <tt>data:</tt> objects into <tt>aria-</tt> and <tt>data-</tt>
254
+ # prefixed attributes:
255
+ #
256
+ # <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
257
+ # # => <input type="text" aria-label="Search">
258
+ #
259
+ # <button <%= tag.attributes id: "call-to-action", disabled: false, aria: { expanded: false } %> class="primary">Get Started!</button>
260
+ # # => <button id="call-to-action" aria-expanded="false" class="primary">Get Started!</button>
261
+ #
255
262
  # === Legacy syntax
256
263
  #
257
- # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
264
+ # The following format is for legacy syntax support. It will be deprecated in future versions of \Rails.
258
265
  #
259
266
  # tag(name, options = nil, open = false, escape = true)
260
267
  #
@@ -357,7 +364,7 @@ module ActionView
357
364
  # token_list(nil, false, 123, "", "foo", { bar: true })
358
365
  # # => "123 foo bar"
359
366
  def token_list(*args)
360
- tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
367
+ tokens = build_tag_values(*args).flat_map { |value| CGI.unescape_html(value.to_s).split(/\s+/) }.uniq
361
368
 
362
369
  safe_join(tokens, " ")
363
370
  end
@@ -377,7 +384,7 @@ module ActionView
377
384
  # cdata_section("hello]]>world")
378
385
  # # => <![CDATA[hello]]]]><![CDATA[>world]]>
379
386
  def cdata_section(content)
380
- splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
387
+ splitted = content.to_s.gsub(/\]\]>/, "]]]]><![CDATA[>")
381
388
  "<![CDATA[#{splitted}]]>".html_safe
382
389
  end
383
390
 
@@ -5,7 +5,6 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class Base # :nodoc:
7
7
  include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
8
- include FormOptionsHelper
9
8
 
10
9
  attr_reader :object
11
10
 
@@ -35,22 +34,24 @@ module ActionView
35
34
 
36
35
  private
37
36
  def value
37
+ return unless object
38
+
38
39
  if @allow_method_names_outside_object
39
- object.public_send @method_name if object && object.respond_to?(@method_name)
40
+ object.public_send @method_name if object.respond_to?(@method_name)
40
41
  else
41
- object.public_send @method_name if object
42
+ object.public_send @method_name
42
43
  end
43
44
  end
44
45
 
45
46
  def value_before_type_cast
46
- unless object.nil?
47
- method_before_type_cast = @method_name + "_before_type_cast"
47
+ return unless object
48
48
 
49
- if value_came_from_user? && object.respond_to?(method_before_type_cast)
50
- object.public_send(method_before_type_cast)
51
- else
52
- value
53
- end
49
+ method_before_type_cast = @method_name + "_before_type_cast"
50
+
51
+ if value_came_from_user? && object.respond_to?(method_before_type_cast)
52
+ object.public_send(method_before_type_cast)
53
+ else
54
+ value
54
55
  end
55
56
  end
56
57
 
@@ -97,7 +98,7 @@ module ActionView
97
98
  options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
98
99
 
99
100
  if generate_ids?
100
- options["id"] = options.fetch("id") { tag_id(index) }
101
+ options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
101
102
  if namespace = options.delete("namespace")
102
103
  options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
103
104
  end
@@ -105,31 +106,11 @@ module ActionView
105
106
  end
106
107
 
107
108
  def tag_name(multiple = false, index = nil)
108
- # a little duplication to construct fewer strings
109
- case
110
- when @object_name.empty?
111
- "#{sanitized_method_name}#{multiple ? "[]" : ""}"
112
- when index
113
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
114
- else
115
- "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
116
- end
117
- end
118
-
119
- def tag_id(index = nil)
120
- # a little duplication to construct fewer strings
121
- case
122
- when @object_name.empty?
123
- sanitized_method_name.dup
124
- when index
125
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
126
- else
127
- "#{sanitized_object_name}_#{sanitized_method_name}"
128
- end
109
+ @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
129
110
  end
130
111
 
131
- def sanitized_object_name
132
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
112
+ def tag_id(index = nil, namespace = nil)
113
+ @template_object.field_id(@object_name, @method_name, index: index, namespace: namespace)
133
114
  end
134
115
 
135
116
  def sanitized_method_name
@@ -137,49 +118,7 @@ module ActionView
137
118
  end
138
119
 
139
120
  def sanitized_value(value)
140
- value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
141
- end
142
-
143
- def select_content_tag(option_tags, options, html_options)
144
- html_options = html_options.stringify_keys
145
- add_default_name_and_id(html_options)
146
-
147
- if placeholder_required?(html_options)
148
- raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
149
- options[:include_blank] ||= true unless options[:prompt]
150
- end
151
-
152
- value = options.fetch(:selected) { value() }
153
- select = content_tag("select", add_options(option_tags, options, value), html_options)
154
-
155
- if html_options["multiple"] && options.fetch(:include_hidden, true)
156
- tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
157
- else
158
- select
159
- end
160
- end
161
-
162
- def placeholder_required?(html_options)
163
- # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
164
- html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
165
- end
166
-
167
- def add_options(option_tags, options, value = nil)
168
- if options[:include_blank]
169
- content = (options[:include_blank] if options[:include_blank].is_a?(String))
170
- label = (" " unless content)
171
- option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
172
- end
173
-
174
- if value.blank? && options[:prompt]
175
- tag_options = { value: "" }.tap do |prompt_opts|
176
- prompt_opts[:disabled] = true if options[:disabled] == ""
177
- prompt_opts[:selected] = true if options[:selected] == ""
178
- end
179
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
180
- end
181
-
182
- option_tags
121
+ value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
183
122
  end
184
123
 
185
124
  def name_and_id_index(options)
@@ -5,7 +5,7 @@ require "action_view/helpers/tags/checkable"
5
5
  module ActionView
6
6
  module Helpers
7
7
  module Tags # :nodoc:
8
- class CheckBox < Base #:nodoc:
8
+ class CheckBox < Base # :nodoc:
9
9
  include Checkable
10
10
 
11
11
  def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
@@ -7,6 +7,7 @@ module ActionView
7
7
  module Tags # :nodoc:
8
8
  class CollectionCheckBoxes < Base # :nodoc:
9
9
  include CollectionHelpers
10
+ include FormOptionsHelper
10
11
 
11
12
  class CheckBoxBuilder < Builder # :nodoc:
12
13
  def check_box(extra_html_options = {})
@@ -7,6 +7,7 @@ module ActionView
7
7
  module Tags # :nodoc:
8
8
  class CollectionRadioButtons < Base # :nodoc:
9
9
  include CollectionHelpers
10
+ include FormOptionsHelper
10
11
 
11
12
  class RadioButtonBuilder < Builder # :nodoc:
12
13
  def radio_button(extra_html_options = {})
@@ -3,7 +3,10 @@
3
3
  module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
- class CollectionSelect < Base #:nodoc:
6
+ class CollectionSelect < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
8
11
  @collection = collection
9
12
  @value_method = value_method
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class DateField < DatetimeField # :nodoc:
7
7
  private
8
- def format_date(value)
8
+ def format_datetime(value)
9
9
  value&.strftime("%Y-%m-%d")
10
10
  end
11
11
  end
@@ -6,6 +6,8 @@ module ActionView
6
6
  module Helpers
7
7
  module Tags # :nodoc:
8
8
  class DateSelect < Base # :nodoc:
9
+ include SelectRenderer
10
+
9
11
  def initialize(object_name, method_name, template_object, options, html_options)
10
12
  @html_options = html_options
11
13
 
@@ -6,20 +6,28 @@ module ActionView
6
6
  class DatetimeField < TextField # :nodoc:
7
7
  def render
8
8
  options = @options.stringify_keys
9
- options["value"] ||= format_date(value)
10
- options["min"] = format_date(datetime_value(options["min"]))
11
- options["max"] = format_date(datetime_value(options["max"]))
9
+ options["value"] = datetime_value(options["value"] || value)
10
+ options["min"] = format_datetime(parse_datetime(options["min"]))
11
+ options["max"] = format_datetime(parse_datetime(options["max"]))
12
12
  @options = options
13
13
  super
14
14
  end
15
15
 
16
16
  private
17
- def format_date(value)
17
+ def datetime_value(value)
18
+ if value.is_a?(String)
19
+ value
20
+ else
21
+ format_datetime(value)
22
+ end
23
+ end
24
+
25
+ def format_datetime(value)
18
26
  raise NotImplementedError
19
27
  end
20
28
 
21
- def datetime_value(value)
22
- if value.is_a? String
29
+ def parse_datetime(value)
30
+ if value.is_a?(String)
23
31
  DateTime.parse(value) rescue nil
24
32
  else
25
33
  value
@@ -4,6 +4,11 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class DatetimeLocalField < DatetimeField # :nodoc:
7
+ def initialize(object_name, method_name, template_object, options = {})
8
+ @include_seconds = options.delete(:include_seconds) { true }
9
+ super
10
+ end
11
+
7
12
  class << self
8
13
  def field_type
9
14
  @field_type ||= "datetime-local"
@@ -11,8 +16,12 @@ module ActionView
11
16
  end
12
17
 
13
18
  private
14
- def format_date(value)
15
- value&.strftime("%Y-%m-%dT%T")
19
+ def format_datetime(value)
20
+ if @include_seconds
21
+ value&.strftime("%Y-%m-%dT%T")
22
+ else
23
+ value&.strftime("%Y-%m-%dT%H:%M")
24
+ end
16
25
  end
17
26
  end
18
27
  end
@@ -4,6 +4,22 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class FileField < TextField # :nodoc:
7
+ def render
8
+ include_hidden = @options.delete(:include_hidden)
9
+ options = @options.stringify_keys
10
+ add_default_name_and_id(options)
11
+
12
+ if options["multiple"] && include_hidden
13
+ hidden_field_for_multiple_file(options) + super
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ private
20
+ def hidden_field_for_multiple_file(options)
21
+ tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off")
22
+ end
7
23
  end
8
24
  end
9
25
  end
@@ -4,6 +4,9 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class GroupedCollectionSelect < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
8
11
  @collection = collection
9
12
  @group_method = group_method
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class MonthField < DatetimeField # :nodoc:
7
7
  private
8
- def format_date(value)
8
+ def format_datetime(value)
9
9
  value&.strftime("%Y-%m")
10
10
  end
11
11
  end
@@ -4,6 +4,9 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class Select < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, choices, options, html_options)
8
11
  @choices = block_given? ? template_object.capture { yield || "" } : choices
9
12
  @choices = @choices.to_a if @choices.is_a?(Range)
@@ -34,7 +37,7 @@ module ActionView
34
37
  # [nil, []]
35
38
  # { nil => [] }
36
39
  def grouped_choices?
37
- !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
40
+ !@choices.blank? && @choices.first.respond_to?(:second) && Array === @choices.first.second
38
41
  end
39
42
  end
40
43
  end