actionview 7.2.2.1 → 8.1.2

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -71
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +11 -11
  5. data/lib/action_view/buffers.rb +1 -1
  6. data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
  7. data/lib/action_view/dependency_tracker/ruby_tracker.rb +2 -19
  8. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  9. data/lib/action_view/dependency_tracker.rb +7 -1
  10. data/lib/action_view/digestor.rb +6 -2
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/asset_tag_helper.rb +25 -6
  13. data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
  14. data/lib/action_view/helpers/cache_helper.rb +10 -2
  15. data/lib/action_view/helpers/capture_helper.rb +2 -2
  16. data/lib/action_view/helpers/controller_helper.rb +6 -2
  17. data/lib/action_view/helpers/date_helper.rb +28 -4
  18. data/lib/action_view/helpers/form_helper.rb +103 -103
  19. data/lib/action_view/helpers/form_options_helper.rb +39 -35
  20. data/lib/action_view/helpers/form_tag_helper.rb +35 -25
  21. data/lib/action_view/helpers/javascript_helper.rb +5 -1
  22. data/lib/action_view/helpers/number_helper.rb +14 -0
  23. data/lib/action_view/helpers/output_safety_helper.rb +1 -2
  24. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  25. data/lib/action_view/helpers/sanitize_helper.rb +6 -0
  26. data/lib/action_view/helpers/tag_helper.rb +57 -73
  27. data/lib/action_view/helpers/tags/base.rb +11 -9
  28. data/lib/action_view/helpers/tags/check_box.rb +9 -3
  29. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  30. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  31. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  32. data/lib/action_view/helpers/tags/file_field.rb +7 -2
  33. data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/label.rb +3 -10
  35. data/lib/action_view/helpers/tags/radio_button.rb +1 -1
  36. data/lib/action_view/helpers/tags/select.rb +6 -1
  37. data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
  38. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  39. data/lib/action_view/helpers/tags/text_field.rb +1 -1
  40. data/lib/action_view/helpers/text_helper.rb +10 -3
  41. data/lib/action_view/helpers/translation_helper.rb +6 -1
  42. data/lib/action_view/helpers/url_helper.rb +39 -13
  43. data/lib/action_view/layouts.rb +7 -7
  44. data/lib/action_view/locale/en.yml +3 -0
  45. data/lib/action_view/log_subscriber.rb +1 -4
  46. data/lib/action_view/railtie.rb +12 -1
  47. data/lib/action_view/record_identifier.rb +22 -1
  48. data/lib/action_view/render_parser/prism_render_parser.rb +13 -1
  49. data/lib/action_view/render_parser/ripper_render_parser.rb +10 -1
  50. data/lib/action_view/renderer/partial_renderer.rb +18 -2
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
  52. data/lib/action_view/renderer/template_renderer.rb +3 -3
  53. data/lib/action_view/rendering.rb +2 -3
  54. data/lib/action_view/structured_event_subscriber.rb +97 -0
  55. data/lib/action_view/template/error.rb +18 -3
  56. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  57. data/lib/action_view/template/handlers/erb.rb +77 -44
  58. data/lib/action_view/template/raw_file.rb +4 -0
  59. data/lib/action_view/template/resolver.rb +0 -1
  60. data/lib/action_view/template.rb +3 -4
  61. data/lib/action_view/test_case.rb +50 -53
  62. data/lib/action_view.rb +4 -0
  63. metadata +15 -16
@@ -1,32 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionView
4
6
  module Helpers # :nodoc:
5
- # = Action View \Rendering \Helpers
7
+ # # Action View Rendering Helpers
6
8
  #
7
- # Implements methods that allow rendering from a view context.
8
- # In order to use this module, all you need is to implement
9
- # view_renderer that returns an ActionView::Renderer object.
9
+ # Implements methods that allow rendering from a view context. In order to use
10
+ # this module, all you need is to implement view_renderer that returns an
11
+ # ActionView::Renderer object.
10
12
  module RenderingHelper
11
- # Returns the result of a render that's dictated by the options hash. The primary options are:
13
+ # Renders a template and returns the result.
14
+ #
15
+ # Pass the template to render as the first argument. This is shorthand
16
+ # syntax for partial rendering, so the template filename should be
17
+ # prefixed with an underscore. The partial renderer looks for the partial
18
+ # template in the directory of the calling template first.
19
+ #
20
+ # <% # app/views/posts/new.html.erb %>
21
+ # <%= render "form" %>
22
+ # # => renders app/views/posts/_form.html.erb
23
+ #
24
+ # Use the complete view path to render a partial from another directory.
25
+ #
26
+ # <% # app/views/posts/show.html.erb %>
27
+ # <%= render "comments/form" %>
28
+ # # => renders app/views/comments/_form.html.erb
29
+ #
30
+ # Without the rendering mode, the second argument can be a Hash of local
31
+ # variable assignments for the template.
32
+ #
33
+ # <% # app/views/posts/new.html.erb %>
34
+ # <%= render "form", post: Post.new %>
35
+ # # => renders app/views/posts/_form.html.erb
36
+ #
37
+ # If the first argument responds to `render_in`, the template will be rendered
38
+ # by calling `render_in` with the current view context.
39
+ #
40
+ # class Greeting
41
+ # def render_in(view_context)
42
+ # view_context.render html: "<h1>Hello, World</h1>"
43
+ # end
44
+ #
45
+ # def format
46
+ # :html
47
+ # end
48
+ # end
49
+ #
50
+ # <%= render Greeting.new %>
51
+ # # => "<h1>Hello, World</h1>"
52
+ #
53
+ # #### Rendering Mode
54
+ #
55
+ # Pass the rendering mode as first argument to override it.
56
+ #
57
+ # `:partial`
58
+ # : See ActionView::PartialRenderer for details.
59
+ #
60
+ # <%= render partial: "form", locals: { post: Post.new } %>
61
+ # # => renders app/views/posts/_form.html.erb
62
+ #
63
+ # `:file`
64
+ # : Renders the contents of a file. This option should **not** be used with
65
+ # unsanitized user input.
66
+ #
67
+ # <%= render file: "/path/to/some/file" %>
68
+ # # => renders /path/to/some/file
69
+ #
70
+ # `:inline`
71
+ # : Renders an ERB template string.
72
+ #
73
+ # <% name = "World" %>
74
+ # <%= render inline: "<h1>Hello, <%= name %>!</h1>" %>
75
+ # # => renders "<h1>Hello, World!</h1>"
76
+ #
77
+ # `:body`
78
+ # : Renders the provided text, and sets the format as `:text`.
79
+ #
80
+ # <%= render body: "Hello, World!" %>
81
+ # # => renders "Hello, World!"
82
+ #
83
+ # `:plain`
84
+ # : Renders the provided text, and sets the format as `:text`.
85
+ #
86
+ # <%= render plain: "Hello, World!" %>
87
+ # # => renders "Hello, World!"
88
+ #
89
+ # `:html`
90
+ # : Renders the provided HTML string, and sets the format as
91
+ # `:html`. If the string is not `html_safe?`, performs HTML escaping on
92
+ # the string before rendering.
93
+ #
94
+ # <%= render html: "<h1>Hello, World!</h1>".html_safe %>
95
+ # # => renders "<h1>Hello, World!</h1>"
96
+ #
97
+ # <%= render html: "<h1>Hello, World!</h1>" %>
98
+ # # => renders "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
99
+ #
100
+ # `:renderable`
101
+ # : Renders the provided object by calling `render_in` with the current view
102
+ # context. The format is determined by calling `format` on the
103
+ # renderable if it responds to `format`, falling back to `:html` by
104
+ # default.
105
+ #
106
+ # <%= render renderable: Greeting.new %>
107
+ # # => renders "<h1>Hello, World</h1>"
108
+ #
109
+ #
110
+ # #### Options
111
+ #
112
+ # `:locals`
113
+ # : Hash of local variable assignments for the template.
114
+ #
115
+ # <%= render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" } %>
116
+ # # => renders "<h1>Hello, World!</h1>"
117
+ #
118
+ # `:formats`
119
+ # : Override the current format to render a template for a different format.
120
+ #
121
+ # <% # app/views/posts/show.html.erb %>
122
+ # <%= render template: "posts/content", formats: [:text] %>
123
+ # # => renders app/views/posts/content.text.erb
12
124
  #
13
- # * <tt>:partial</tt> - See ActionView::PartialRenderer.
14
- # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add +:locals+ to pass in those.
15
- # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
16
- # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
17
- # type as <tt>text/plain</tt>.
18
- # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
19
- # performs HTML escape on the string first. Setting the content type as
20
- # <tt>text/html</tt>.
21
- # * <tt>:body</tt> - Renders the text passed in, and inherits the content
22
- # type of <tt>text/plain</tt> from ActionDispatch::Response object.
125
+ # `:variants`
126
+ # : Render a template for a different variant.
23
127
  #
24
- # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
128
+ # <% # app/views/posts/show.html.erb %>
129
+ # <%= render template: "posts/content", variants: [:tablet] %>
130
+ # # => renders app/views/posts/content.html+tablet.erb
25
131
  #
26
- # If an object responding to +render_in+ is passed, +render_in+ is called on the object,
27
- # passing in the current view context.
132
+ # `:handlers`
133
+ # : Render a template for a different handler.
28
134
  #
29
- # Otherwise, a partial is rendered using the second parameter as the locals hash.
135
+ # <% # app/views/posts/show.html.erb %>
136
+ # <%= render template: "posts/content", handlers: [:builder] %>
137
+ # # => renders app/views/posts/content.html.builder
30
138
  def render(options = {}, locals = {}, &block)
31
139
  case options
32
140
  when Hash
@@ -47,52 +155,54 @@ module ActionView
47
155
  end
48
156
 
49
157
  # Overrides _layout_for in the context object so it supports the case a block is
50
- # passed to a partial. Returns the contents that are yielded to a layout, given a
51
- # name or a block.
158
+ # passed to a partial. Returns the contents that are yielded to a layout, given
159
+ # a name or a block.
52
160
  #
53
- # You can think of a layout as a method that is called with a block. If the user calls
54
- # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
55
- # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
161
+ # You can think of a layout as a method that is called with a block. If the user
162
+ # calls `yield :some_name`, the block, by default, returns
163
+ # `content_for(:some_name)`. If the user calls simply `yield`, the default block
164
+ # returns `content_for(:layout)`.
56
165
  #
57
166
  # The user can override this default by passing a block to the layout:
58
167
  #
59
- # # The template
60
- # <%= render layout: "my_layout" do %>
61
- # Content
62
- # <% end %>
168
+ # # The template
169
+ # <%= render layout: "my_layout" do %>
170
+ # Content
171
+ # <% end %>
63
172
  #
64
- # # The layout
65
- # <html>
66
- # <%= yield %>
67
- # </html>
173
+ # # The layout
174
+ # <html>
175
+ # <%= yield %>
176
+ # </html>
68
177
  #
69
- # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
70
- # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
178
+ # In this case, instead of the default block, which would return `content_for(:layout)`,
179
+ # this method returns the block that was passed in to `render :layout`, and the response
71
180
  # would be
72
181
  #
73
- # <html>
74
- # Content
75
- # </html>
182
+ # <html>
183
+ # Content
184
+ # </html>
76
185
  #
77
- # Finally, the block can take block arguments, which can be passed in by +yield+:
186
+ # Finally, the block can take block arguments, which can be passed in by
187
+ # `yield`:
78
188
  #
79
- # # The template
80
- # <%= render layout: "my_layout" do |customer| %>
81
- # Hello <%= customer.name %>
82
- # <% end %>
189
+ # # The template
190
+ # <%= render layout: "my_layout" do |customer| %>
191
+ # Hello <%= customer.name %>
192
+ # <% end %>
83
193
  #
84
- # # The layout
85
- # <html>
86
- # <%= yield Struct.new(:name).new("David") %>
87
- # </html>
194
+ # # The layout
195
+ # <html>
196
+ # <%= yield Struct.new(:name).new("David") %>
197
+ # </html>
88
198
  #
89
- # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
199
+ # In this case, the layout would receive the block passed into `render :layout`,
90
200
  # and the struct specified would be passed into the block as an argument. The result
91
201
  # would be
92
202
  #
93
- # <html>
94
- # Hello David
95
- # </html>
203
+ # <html>
204
+ # Hello David
205
+ # </html>
96
206
  #
97
207
  def _layout_for(*args, &block)
98
208
  name = args.first
@@ -24,6 +24,12 @@ module ActionView
24
24
  #
25
25
  # Custom sanitization rules can also be provided.
26
26
  #
27
+ # <b>Warning</b>: Adding disallowed tags or attributes to the allowlists may introduce
28
+ # vulnerabilities into your application. Please rely on the default allowlists whenever
29
+ # possible, because they are curated to maintain security and safety. If you think that the
30
+ # default allowlists should be expanded, please {open an issue on the rails-html-sanitizer
31
+ # project}[https://github.com/rails/rails-html-sanitizer/issues].
32
+ #
27
33
  # Please note that sanitizing user-provided text does not guarantee that the
28
34
  # resulting markup is valid or even well-formed.
29
35
  #
@@ -4,7 +4,6 @@ require "active_support/code_generator"
4
4
  require "active_support/core_ext/enumerable"
5
5
  require "active_support/core_ext/string/output_safety"
6
6
  require "active_support/core_ext/string/inflections"
7
- require "set"
8
7
  require "action_view/helpers/capture_helper"
9
8
  require "action_view/helpers/output_safety_helper"
10
9
 
@@ -45,51 +44,36 @@ module ActionView
45
44
  PRE_CONTENT_STRINGS["textarea"] = "\n"
46
45
 
47
46
  class TagBuilder # :nodoc:
48
- include CaptureHelper
49
- include OutputSafetyHelper
50
-
51
- def self.define_element(name, code_generator:, method_name: name.to_s.underscore)
52
- code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
53
- batch.push(<<~RUBY) unless instance_methods.include?(method_name.to_sym)
54
- def #{method_name}(content = nil, escape: true, **options, &block)
55
- tag_string("#{name}", content, options, escape: escape, &block)
56
- end
57
- RUBY
47
+ def self.define_element(name, code_generator:, method_name: name)
48
+ return if method_defined?(name)
49
+
50
+ code_generator.class_eval do |batch|
51
+ batch << "\n" <<
52
+ "def #{method_name}(content = nil, escape: true, **options, &block)" <<
53
+ " tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
54
+ "end"
58
55
  end
59
56
  end
60
57
 
61
- def self.define_void_element(name, code_generator:, method_name: name.to_s.underscore)
62
- code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
63
- batch.push(<<~RUBY)
64
- def #{method_name}(content = nil, escape: true, **options, &block)
65
- if content || block
66
- ActionView.deprecator.warn <<~TEXT
67
- Putting content inside a void element (#{name}) is invalid
68
- according to the HTML5 spec, and so it is being deprecated
69
- without replacement. In Rails 8.0, passing content as a
70
- positional argument will raise, and using a block will have
71
- no effect.
72
- TEXT
73
- tag_string("#{name}", content, options, escape: escape, &block)
74
- else
75
- self_closing_tag_string("#{name}", options, escape, ">")
76
- end
77
- end
78
- RUBY
58
+ def self.define_void_element(name, code_generator:, method_name: name)
59
+ code_generator.class_eval do |batch|
60
+ batch << "\n" <<
61
+ "def #{method_name}(escape: true, **options, &block)" <<
62
+ " self_closing_tag_string(#{name.inspect}, options, escape, '>')" <<
63
+ "end"
79
64
  end
80
65
  end
81
66
 
82
- def self.define_self_closing_element(name, code_generator:, method_name: name.to_s.underscore)
83
- code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
84
- batch.push(<<~RUBY)
85
- def #{method_name}(content = nil, escape: true, **options, &block)
86
- if content || block
87
- tag_string("#{name}", content, options, escape: escape, &block)
88
- else
89
- self_closing_tag_string("#{name}", options, escape)
90
- end
91
- end
92
- RUBY
67
+ def self.define_self_closing_element(name, code_generator:, method_name: name)
68
+ code_generator.class_eval do |batch|
69
+ batch << "\n" <<
70
+ "def #{method_name}(content = nil, escape: true, **options, &block)" <<
71
+ " if content || block" <<
72
+ " tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
73
+ " else" <<
74
+ " self_closing_tag_string(#{name.inspect}, options, escape)" <<
75
+ " end" <<
76
+ "end"
93
77
  end
94
78
  end
95
79
 
@@ -110,8 +94,8 @@ module ActionView
110
94
  define_void_element :wbr, code_generator: code_generator
111
95
 
112
96
  define_self_closing_element :animate, code_generator: code_generator
113
- define_self_closing_element :animateMotion, code_generator: code_generator
114
- define_self_closing_element :animateTransform, code_generator: code_generator
97
+ define_self_closing_element :animateMotion, code_generator: code_generator, method_name: :animate_motion
98
+ define_self_closing_element :animateTransform, code_generator: code_generator, method_name: :animate_transform
115
99
  define_self_closing_element :circle, code_generator: code_generator
116
100
  define_self_closing_element :ellipse, code_generator: code_generator
117
101
  define_self_closing_element :line, code_generator: code_generator
@@ -239,17 +223,7 @@ module ActionView
239
223
  tag_options(attributes.to_h).to_s.strip.html_safe
240
224
  end
241
225
 
242
- def tag_string(name, content = nil, options, escape: true, &block)
243
- content = @view_context.capture(self, &block) if block
244
-
245
- content_tag_string(name, content, options, escape)
246
- end
247
-
248
- def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
249
- "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
250
- end
251
-
252
- def content_tag_string(name, content, options, escape = true)
226
+ def content_tag_string(name, content, options, escape = true) # :nodoc:
253
227
  tag_options = tag_options(options, escape) if options
254
228
 
255
229
  if escape && content.present?
@@ -258,7 +232,7 @@ module ActionView
258
232
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
259
233
  end
260
234
 
261
- def tag_options(options, escape = true)
235
+ def tag_options(options, escape = true) # :nodoc:
262
236
  return if options.blank?
263
237
  output = +""
264
238
  sep = " "
@@ -279,7 +253,7 @@ module ActionView
279
253
  tokens = TagHelper.build_tag_values(v)
280
254
  next if tokens.none?
281
255
 
282
- v = safe_join(tokens, " ")
256
+ v = @view_context.safe_join(tokens, " ")
283
257
  else
284
258
  v = v.to_s
285
259
  end
@@ -300,28 +274,38 @@ module ActionView
300
274
  output unless output.empty?
301
275
  end
302
276
 
303
- def boolean_tag_option(key)
304
- %(#{key}="#{key}")
305
- end
277
+ private
278
+ def tag_string(name, content = nil, options, escape: true, &block)
279
+ content = @view_context.capture(self, &block) if block
306
280
 
307
- def tag_option(key, value, escape)
308
- key = ERB::Util.xml_name_escape(key) if escape
309
-
310
- case value
311
- when Array, Hash
312
- value = TagHelper.build_tag_values(value) if key.to_s == "class"
313
- value = escape ? safe_join(value, " ") : value.join(" ")
314
- when Regexp
315
- value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
316
- else
317
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
281
+ content_tag_string(name, content, options, escape)
318
282
  end
319
- value = value.gsub('"', "&quot;") if value.include?('"')
320
283
 
321
- %(#{key}="#{value}")
322
- end
284
+ def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
285
+ "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
286
+ end
287
+
288
+ def boolean_tag_option(key)
289
+ %(#{key}="#{key}")
290
+ end
291
+
292
+ def tag_option(key, value, escape)
293
+ key = ERB::Util.xml_name_escape(key) if escape
294
+
295
+ case value
296
+ when Array, Hash
297
+ value = TagHelper.build_tag_values(value) if key.to_s == "class"
298
+ value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
299
+ when Regexp
300
+ value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
301
+ else
302
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
303
+ end
304
+ value = value.gsub('"', "&quot;") if value.include?('"')
305
+
306
+ %(#{key}="#{value}")
307
+ end
323
308
 
324
- private
325
309
  def prefix_tag_option(prefix, key, value, escape)
326
310
  key = "#{prefix}-#{key.to_s.dasherize}"
327
311
  unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
@@ -80,30 +80,32 @@ module ActionView
80
80
  end
81
81
  end
82
82
 
83
- def add_default_name_and_id_for_value(tag_value, options)
83
+ def add_default_name_and_field_for_value(tag_value, options, field = "id")
84
84
  if tag_value.nil?
85
- add_default_name_and_id(options)
85
+ add_default_name_and_field(options, field)
86
86
  else
87
- specified_id = options["id"]
88
- add_default_name_and_id(options)
87
+ specified_field = options[field]
88
+ add_default_name_and_field(options, field)
89
89
 
90
- if specified_id.blank? && options["id"].present?
91
- options["id"] += "_#{sanitized_value(tag_value)}"
90
+ if specified_field.blank? && options[field].present?
91
+ options[field] += "_#{sanitized_value(tag_value)}"
92
92
  end
93
93
  end
94
94
  end
95
+ alias_method :add_default_name_and_id_for_value, :add_default_name_and_field_for_value
95
96
 
96
- def add_default_name_and_id(options)
97
+ def add_default_name_and_field(options, field = "id")
97
98
  index = name_and_id_index(options)
98
99
  options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
99
100
 
100
101
  if generate_ids?
101
- options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
102
+ options[field] = options.fetch(field) { tag_id(index, options.delete("namespace")) }
102
103
  if namespace = options.delete("namespace")
103
- options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
104
+ options[field] = options[field] ? "#{namespace}_#{options[field]}" : namespace
104
105
  end
105
106
  end
106
107
  end
108
+ alias_method :add_default_name_and_id, :add_default_name_and_field
107
109
 
108
110
  def tag_name(multiple = false, index = nil)
109
111
  @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
@@ -21,10 +21,10 @@ module ActionView
21
21
  options["checked"] = "checked" if input_checked?(options)
22
22
 
23
23
  if options["multiple"]
24
- add_default_name_and_id_for_value(@checked_value, options)
24
+ add_default_name_and_field_for_value(@checked_value, options)
25
25
  options.delete("multiple")
26
26
  else
27
- add_default_name_and_id(options)
27
+ add_default_name_and_field(options)
28
28
  end
29
29
 
30
30
  include_hidden = options.delete("include_hidden") { true }
@@ -57,7 +57,13 @@ module ActionView
57
57
  end
58
58
 
59
59
  def hidden_field_for_checkbox(options)
60
- @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe
60
+ if @unchecked_value
61
+ tag_options = options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)
62
+ tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
63
+ tag("input", tag_options)
64
+ else
65
+ "".html_safe
66
+ end
61
67
  end
62
68
  end
63
69
  end
@@ -10,12 +10,13 @@ module ActionView
10
10
  include FormOptionsHelper
11
11
 
12
12
  class CheckBoxBuilder < Builder # :nodoc:
13
- def check_box(extra_html_options = {})
13
+ def checkbox(extra_html_options = {})
14
14
  html_options = extra_html_options.merge(@input_html_options)
15
15
  html_options[:multiple] = true
16
16
  html_options[:skip_default_ids] = false
17
- @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
17
+ @template_object.checkbox(@object_name, @method_name, html_options, @value, nil)
18
18
  end
19
+ alias_method :check_box, :checkbox
19
20
  end
20
21
 
21
22
  def render(&block)
@@ -24,7 +25,7 @@ module ActionView
24
25
 
25
26
  private
26
27
  def render_component(builder)
27
- builder.check_box + builder.label
28
+ builder.checkbox + builder.label
28
29
  end
29
30
 
30
31
  def hidden_field_name
@@ -106,7 +106,8 @@ module ActionView
106
106
 
107
107
  def hidden_field
108
108
  hidden_name = @html_options[:name] || hidden_field_name
109
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
109
+ options = { id: nil, form: @html_options[:form] }
110
+ @template_object.hidden_field_tag(hidden_name, "", options)
110
111
  end
111
112
 
112
113
  def hidden_field_name
@@ -6,7 +6,7 @@ module ActionView
6
6
  class DatetimeField < TextField # :nodoc:
7
7
  def render
8
8
  options = @options.stringify_keys
9
- options["value"] = datetime_value(options["value"] || value)
9
+ options["value"] = datetime_value(options.fetch("value", value))
10
10
  options["min"] = format_datetime(parse_datetime(options["min"]))
11
11
  options["max"] = format_datetime(parse_datetime(options["max"]))
12
12
  @options = options
@@ -6,8 +6,11 @@ module ActionView
6
6
  class FileField < TextField # :nodoc:
7
7
  def render
8
8
  include_hidden = @options.delete(:include_hidden)
9
+ if @options[:accept].is_a?(Array)
10
+ @options[:accept] = @options[:accept].join(",")
11
+ end
9
12
  options = @options.stringify_keys
10
- add_default_name_and_id(options)
13
+ add_default_name_and_field(options)
11
14
 
12
15
  if options["multiple"] && include_hidden
13
16
  hidden_field_for_multiple_file(options) + super
@@ -18,7 +21,9 @@ module ActionView
18
21
 
19
22
  private
20
23
  def hidden_field_for_multiple_file(options)
21
- tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off")
24
+ tag_options = { "name" => options["name"], "type" => "hidden", "value" => "" }
25
+ tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
26
+ tag("input", tag_options)
22
27
  end
23
28
  end
24
29
  end
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class HiddenField < TextField # :nodoc:
7
7
  def render
8
- @options[:autocomplete] = "off"
8
+ @options.reverse_merge!(autocomplete: "off") unless ActionView::Base.remove_hidden_field_autocomplete
9
9
  super
10
10
  end
11
11
  end
@@ -48,18 +48,11 @@ module ActionView
48
48
  def render(&block)
49
49
  options = @options.stringify_keys
50
50
  tag_value = options.delete("value")
51
- name_and_id = options.dup
52
51
 
53
- if name_and_id["for"]
54
- name_and_id["id"] = name_and_id["for"]
55
- else
56
- name_and_id.delete("id")
57
- end
58
-
59
- add_default_name_and_id_for_value(tag_value, name_and_id)
52
+ add_default_name_and_field_for_value(tag_value, options, "for")
60
53
  options.delete("index")
54
+ options.delete("name")
61
55
  options.delete("namespace")
62
- options["for"] = name_and_id["id"] unless options.key?("for")
63
56
 
64
57
  builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
65
58
 
@@ -71,7 +64,7 @@ module ActionView
71
64
  render_component(builder)
72
65
  end
73
66
 
74
- label_tag(name_and_id["id"], content, options)
67
+ label_tag(options["for"], content, options)
75
68
  end
76
69
 
77
70
  private
@@ -18,7 +18,7 @@ module ActionView
18
18
  options["type"] = "radio"
19
19
  options["value"] = @tag_value
20
20
  options["checked"] = "checked" if input_checked?(options)
21
- add_default_name_and_id_for_value(@tag_value, options)
21
+ add_default_name_and_field_for_value(@tag_value, options)
22
22
  tag("input", options)
23
23
  end
24
24
 
@@ -37,7 +37,12 @@ module ActionView
37
37
  # [nil, []]
38
38
  # { nil => [] }
39
39
  def grouped_choices?
40
- !@choices.blank? && @choices.first.respond_to?(:second) && Array === @choices.first.second
40
+ return false if @choices.blank?
41
+
42
+ first_choice = @choices.first
43
+ return false unless first_choice.is_a?(Enumerable)
44
+
45
+ first_choice.second.is_a?(Array)
41
46
  end
42
47
  end
43
48
  end