actionview 6.1.7.2 → 7.1.3

Sign up to get free protection for your applications and to get access to all the features.
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