actionview 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -436
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +29 -11
  5. data/lib/action_view/buffers.rb +1 -1
  6. data/lib/action_view/cache_expiry.rb +9 -3
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
  8. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  9. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +8 -2
  11. data/lib/action_view/digestor.rb +6 -2
  12. data/lib/action_view/gem_version.rb +3 -3
  13. data/lib/action_view/helpers/asset_tag_helper.rb +39 -8
  14. data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
  15. data/lib/action_view/helpers/cache_helper.rb +10 -2
  16. data/lib/action_view/helpers/capture_helper.rb +2 -2
  17. data/lib/action_view/helpers/controller_helper.rb +6 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +28 -4
  20. data/lib/action_view/helpers/form_helper.rb +277 -272
  21. data/lib/action_view/helpers/form_options_helper.rb +39 -35
  22. data/lib/action_view/helpers/form_tag_helper.rb +115 -72
  23. data/lib/action_view/helpers/javascript_helper.rb +5 -1
  24. data/lib/action_view/helpers/number_helper.rb +14 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +5 -6
  26. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  27. data/lib/action_view/helpers/sanitize_helper.rb +6 -0
  28. data/lib/action_view/helpers/tag_helper.rb +225 -48
  29. data/lib/action_view/helpers/tags/base.rb +11 -9
  30. data/lib/action_view/helpers/tags/check_box.rb +9 -3
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/file_field.rb +7 -2
  35. data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
  36. data/lib/action_view/helpers/tags/label.rb +3 -10
  37. data/lib/action_view/helpers/tags/radio_button.rb +1 -1
  38. data/lib/action_view/helpers/tags/select.rb +6 -1
  39. data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
  40. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  41. data/lib/action_view/helpers/tags/text_field.rb +1 -1
  42. data/lib/action_view/helpers/text_helper.rb +11 -4
  43. data/lib/action_view/helpers/translation_helper.rb +6 -1
  44. data/lib/action_view/helpers/url_helper.rb +42 -90
  45. data/lib/action_view/layouts.rb +9 -11
  46. data/lib/action_view/locale/en.yml +3 -0
  47. data/lib/action_view/log_subscriber.rb +9 -8
  48. data/lib/action_view/railtie.rb +12 -2
  49. data/lib/action_view/record_identifier.rb +22 -1
  50. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  51. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
  52. data/lib/action_view/render_parser.rb +21 -169
  53. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  54. data/lib/action_view/renderer/partial_renderer.rb +18 -2
  55. data/lib/action_view/renderer/renderer.rb +32 -38
  56. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
  57. data/lib/action_view/renderer/template_renderer.rb +3 -3
  58. data/lib/action_view/rendering.rb +6 -7
  59. data/lib/action_view/structured_event_subscriber.rb +97 -0
  60. data/lib/action_view/template/error.rb +18 -3
  61. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  62. data/lib/action_view/template/handlers/erb.rb +77 -44
  63. data/lib/action_view/template/raw_file.rb +4 -0
  64. data/lib/action_view/template/renderable.rb +7 -1
  65. data/lib/action_view/template/resolver.rb +0 -3
  66. data/lib/action_view/template.rb +39 -12
  67. data/lib/action_view/test_case.rb +57 -62
  68. data/lib/action_view.rb +4 -0
  69. metadata +17 -14
  70. data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/code_generator"
3
4
  require "active_support/core_ext/enumerable"
4
5
  require "active_support/core_ext/string/output_safety"
5
- require "set"
6
+ require "active_support/core_ext/string/inflections"
6
7
  require "action_view/helpers/capture_helper"
7
8
  require "action_view/helpers/output_safety_helper"
8
9
 
@@ -43,11 +44,171 @@ module ActionView
43
44
  PRE_CONTENT_STRINGS["textarea"] = "\n"
44
45
 
45
46
  class TagBuilder # :nodoc:
46
- include CaptureHelper
47
- include OutputSafetyHelper
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"
55
+ end
56
+ end
57
+
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"
64
+ end
65
+ end
48
66
 
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
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"
77
+ end
78
+ end
79
+
80
+ ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
81
+ define_void_element :area, code_generator: code_generator
82
+ define_void_element :base, code_generator: code_generator
83
+ define_void_element :br, code_generator: code_generator
84
+ define_void_element :col, code_generator: code_generator
85
+ define_void_element :embed, code_generator: code_generator
86
+ define_void_element :hr, code_generator: code_generator
87
+ define_void_element :img, code_generator: code_generator
88
+ define_void_element :input, code_generator: code_generator
89
+ define_void_element :keygen, code_generator: code_generator
90
+ define_void_element :link, code_generator: code_generator
91
+ define_void_element :meta, code_generator: code_generator
92
+ define_void_element :source, code_generator: code_generator
93
+ define_void_element :track, code_generator: code_generator
94
+ define_void_element :wbr, code_generator: code_generator
95
+
96
+ define_self_closing_element :animate, 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
99
+ define_self_closing_element :circle, code_generator: code_generator
100
+ define_self_closing_element :ellipse, code_generator: code_generator
101
+ define_self_closing_element :line, code_generator: code_generator
102
+ define_self_closing_element :path, code_generator: code_generator
103
+ define_self_closing_element :polygon, code_generator: code_generator
104
+ define_self_closing_element :polyline, code_generator: code_generator
105
+ define_self_closing_element :rect, code_generator: code_generator
106
+ define_self_closing_element :set, code_generator: code_generator
107
+ define_self_closing_element :stop, code_generator: code_generator
108
+ define_self_closing_element :use, code_generator: code_generator
109
+ define_self_closing_element :view, code_generator: code_generator
110
+
111
+ define_element :a, code_generator: code_generator
112
+ define_element :abbr, code_generator: code_generator
113
+ define_element :address, code_generator: code_generator
114
+ define_element :article, code_generator: code_generator
115
+ define_element :aside, code_generator: code_generator
116
+ define_element :audio, code_generator: code_generator
117
+ define_element :b, code_generator: code_generator
118
+ define_element :bdi, code_generator: code_generator
119
+ define_element :bdo, code_generator: code_generator
120
+ define_element :blockquote, code_generator: code_generator
121
+ define_element :body, code_generator: code_generator
122
+ define_element :button, code_generator: code_generator
123
+ define_element :canvas, code_generator: code_generator
124
+ define_element :caption, code_generator: code_generator
125
+ define_element :cite, code_generator: code_generator
126
+ define_element :code, code_generator: code_generator
127
+ define_element :colgroup, code_generator: code_generator
128
+ define_element :data, code_generator: code_generator
129
+ define_element :datalist, code_generator: code_generator
130
+ define_element :dd, code_generator: code_generator
131
+ define_element :del, code_generator: code_generator
132
+ define_element :details, code_generator: code_generator
133
+ define_element :dfn, code_generator: code_generator
134
+ define_element :dialog, code_generator: code_generator
135
+ define_element :div, code_generator: code_generator
136
+ define_element :dl, code_generator: code_generator
137
+ define_element :dt, code_generator: code_generator
138
+ define_element :em, code_generator: code_generator
139
+ define_element :fieldset, code_generator: code_generator
140
+ define_element :figcaption, code_generator: code_generator
141
+ define_element :figure, code_generator: code_generator
142
+ define_element :footer, code_generator: code_generator
143
+ define_element :form, code_generator: code_generator
144
+ define_element :h1, code_generator: code_generator
145
+ define_element :h2, code_generator: code_generator
146
+ define_element :h3, code_generator: code_generator
147
+ define_element :h4, code_generator: code_generator
148
+ define_element :h5, code_generator: code_generator
149
+ define_element :h6, code_generator: code_generator
150
+ define_element :head, code_generator: code_generator
151
+ define_element :header, code_generator: code_generator
152
+ define_element :hgroup, code_generator: code_generator
153
+ define_element :html, code_generator: code_generator
154
+ define_element :i, code_generator: code_generator
155
+ define_element :iframe, code_generator: code_generator
156
+ define_element :ins, code_generator: code_generator
157
+ define_element :kbd, code_generator: code_generator
158
+ define_element :label, code_generator: code_generator
159
+ define_element :legend, code_generator: code_generator
160
+ define_element :li, code_generator: code_generator
161
+ define_element :main, code_generator: code_generator
162
+ define_element :map, code_generator: code_generator
163
+ define_element :mark, code_generator: code_generator
164
+ define_element :menu, code_generator: code_generator
165
+ define_element :meter, code_generator: code_generator
166
+ define_element :nav, code_generator: code_generator
167
+ define_element :noscript, code_generator: code_generator
168
+ define_element :object, code_generator: code_generator
169
+ define_element :ol, code_generator: code_generator
170
+ define_element :optgroup, code_generator: code_generator
171
+ define_element :option, code_generator: code_generator
172
+ define_element :output, code_generator: code_generator
173
+ define_element :p, code_generator: code_generator
174
+ define_element :picture, code_generator: code_generator
175
+ define_element :portal, code_generator: code_generator
176
+ define_element :pre, code_generator: code_generator
177
+ define_element :progress, code_generator: code_generator
178
+ define_element :q, code_generator: code_generator
179
+ define_element :rp, code_generator: code_generator
180
+ define_element :rt, code_generator: code_generator
181
+ define_element :ruby, code_generator: code_generator
182
+ define_element :s, code_generator: code_generator
183
+ define_element :samp, code_generator: code_generator
184
+ define_element :script, code_generator: code_generator
185
+ define_element :search, code_generator: code_generator
186
+ define_element :section, code_generator: code_generator
187
+ define_element :select, code_generator: code_generator
188
+ define_element :slot, code_generator: code_generator
189
+ define_element :small, code_generator: code_generator
190
+ define_element :span, code_generator: code_generator
191
+ define_element :strong, code_generator: code_generator
192
+ define_element :style, code_generator: code_generator
193
+ define_element :sub, code_generator: code_generator
194
+ define_element :summary, code_generator: code_generator
195
+ define_element :sup, code_generator: code_generator
196
+ define_element :table, code_generator: code_generator
197
+ define_element :tbody, code_generator: code_generator
198
+ define_element :td, code_generator: code_generator
199
+ define_element :template, code_generator: code_generator
200
+ define_element :textarea, code_generator: code_generator
201
+ define_element :tfoot, code_generator: code_generator
202
+ define_element :th, code_generator: code_generator
203
+ define_element :thead, code_generator: code_generator
204
+ define_element :time, code_generator: code_generator
205
+ define_element :title, code_generator: code_generator
206
+ define_element :tr, code_generator: code_generator
207
+ define_element :u, code_generator: code_generator
208
+ define_element :ul, code_generator: code_generator
209
+ define_element :var, code_generator: code_generator
210
+ define_element :video, code_generator: code_generator
211
+ end
51
212
 
52
213
  def initialize(view_context)
53
214
  @view_context = view_context
@@ -62,53 +223,40 @@ module ActionView
62
223
  tag_options(attributes.to_h).to_s.strip.html_safe
63
224
  end
64
225
 
65
- def p(*arguments, **options, &block)
66
- tag_string(:p, *arguments, **options, &block)
67
- end
68
-
69
- def tag_string(name, content = nil, escape: true, **options, &block)
70
- content = @view_context.capture(self, &block) if block_given?
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
74
- else
75
- content_tag_string(name.to_s.dasherize, content || "", options, escape)
76
- end
77
- end
78
-
79
- def content_tag_string(name, content, options, escape = true)
226
+ def content_tag_string(name, content, options, escape = true) # :nodoc:
80
227
  tag_options = tag_options(options, escape) if options
81
228
 
82
- if escape
83
- name = ERB::Util.xml_name_escape(name)
229
+ if escape && content.present?
84
230
  content = ERB::Util.unwrapped_html_escape(content)
85
231
  end
86
-
87
232
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
88
233
  end
89
234
 
90
- def tag_options(options, escape = true)
235
+ def tag_options(options, escape = true) # :nodoc:
91
236
  return if options.blank?
92
237
  output = +""
93
238
  sep = " "
94
239
  options.each_pair do |key, value|
240
+ next if key.blank?
241
+
95
242
  type = TAG_TYPES[key]
96
243
  if type == :data && value.is_a?(Hash)
97
244
  value.each_pair do |k, v|
98
- next if v.nil?
245
+ next if k.blank? || v.nil?
246
+
99
247
  output << sep
100
248
  output << prefix_tag_option(key, k, v, escape)
101
249
  end
102
250
  elsif type == :aria && value.is_a?(Hash)
103
251
  value.each_pair do |k, v|
104
- next if v.nil?
252
+ next if k.blank? || v.nil?
105
253
 
106
254
  case v
107
255
  when Array, Hash
108
256
  tokens = TagHelper.build_tag_values(v)
109
257
  next if tokens.none?
110
258
 
111
- v = safe_join(tokens, " ")
259
+ v = @view_context.safe_join(tokens, " ")
112
260
  else
113
261
  v = v.to_s
114
262
  end
@@ -129,28 +277,38 @@ module ActionView
129
277
  output unless output.empty?
130
278
  end
131
279
 
132
- def boolean_tag_option(key)
133
- %(#{key}="#{key}")
134
- end
280
+ private
281
+ def tag_string(name, content = nil, options, escape: true, &block)
282
+ content = @view_context.capture(self, &block) if block
135
283
 
136
- def tag_option(key, value, escape)
137
- key = ERB::Util.xml_name_escape(key) if escape
138
-
139
- case value
140
- when Array, Hash
141
- value = TagHelper.build_tag_values(value) if key.to_s == "class"
142
- value = escape ? safe_join(value, " ") : value.join(" ")
143
- when Regexp
144
- value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
145
- else
146
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
284
+ content_tag_string(name, content, options, escape)
147
285
  end
148
- value = value.gsub('"', "&quot;") if value.include?('"')
149
286
 
150
- %(#{key}="#{value}")
151
- end
287
+ def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
288
+ "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
289
+ end
290
+
291
+ def boolean_tag_option(key)
292
+ %(#{key}="#{key}")
293
+ end
294
+
295
+ def tag_option(key, value, escape)
296
+ key = ERB::Util.xml_name_escape(key) if escape
297
+
298
+ case value
299
+ when Array, Hash
300
+ value = TagHelper.build_tag_values(value) if key.to_s == "class"
301
+ value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
302
+ when Regexp
303
+ value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
304
+ else
305
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
306
+ end
307
+ value = value.gsub('"', "&quot;") if value.include?('"')
308
+
309
+ %(#{key}="#{value}")
310
+ end
152
311
 
153
- private
154
312
  def prefix_tag_option(prefix, key, value, escape)
155
313
  key = "#{prefix}-#{key.to_s.dasherize}"
156
314
  unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
@@ -163,8 +321,12 @@ module ActionView
163
321
  true
164
322
  end
165
323
 
166
- def method_missing(called, *args, **options, &block)
167
- tag_string(called, *args, **options, &block)
324
+ def method_missing(called, *args, escape: true, **options, &block)
325
+ name = called.name.dasherize
326
+
327
+ TagHelper.ensure_valid_html5_tag_name(name)
328
+
329
+ tag_string(name, *args, options, escape: escape, &block)
168
330
  end
169
331
  end
170
332
 
@@ -245,6 +407,14 @@ module ActionView
245
407
  # # A void element:
246
408
  # tag.br # => <br>
247
409
  #
410
+ # Note that when using the block form options should be wrapped in
411
+ # parenthesis.
412
+ #
413
+ # <%= tag.a(href: "/about", class: "font-bold") do %>
414
+ # About the author
415
+ # <% end %>
416
+ # # => <a href="/about" class="font-bold">About the author</a>
417
+ #
248
418
  # === Building HTML attributes
249
419
  #
250
420
  # Transforms a Hash into HTML attributes, ready to be interpolated into
@@ -310,7 +480,7 @@ module ActionView
310
480
  if name.nil?
311
481
  tag_builder
312
482
  else
313
- name = ERB::Util.xml_name_escape(name) if escape
483
+ ensure_valid_html5_tag_name(name)
314
484
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
315
485
  end
316
486
  end
@@ -344,6 +514,8 @@ module ActionView
344
514
  # <% end -%>
345
515
  # # => <div class="strong">Hello world!</div>
346
516
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
517
+ ensure_valid_html5_tag_name(name)
518
+
347
519
  if block_given?
348
520
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
349
521
  tag_builder.content_tag_string(name, capture(&block), options, escape)
@@ -400,6 +572,11 @@ module ActionView
400
572
  end
401
573
 
402
574
  private
575
+ def ensure_valid_html5_tag_name(name)
576
+ raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
577
+ end
578
+ module_function :ensure_valid_html5_tag_name
579
+
403
580
  def build_tag_values(*args)
404
581
  tag_values = []
405
582
 
@@ -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
@@ -11,7 +11,7 @@ module ActionView
11
11
  html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
12
12
  end
13
13
 
14
- add_default_name_and_id(html_options)
14
+ add_default_name_and_field(html_options)
15
15
 
16
16
  if placeholder_required?(html_options)
17
17
  raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
@@ -22,7 +22,9 @@ module ActionView
22
22
  select = content_tag("select", add_options(option_tags, options, value), html_options)
23
23
 
24
24
  if html_options["multiple"] && options.fetch(:include_hidden, true)
25
- tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
25
+ tag_options = { disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "" }
26
+ tag_options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
27
+ tag("input", tag_options) + select
26
28
  else
27
29
  select
28
30
  end
@@ -37,7 +39,7 @@ module ActionView
37
39
  if options[:include_blank]
38
40
  content = (options[:include_blank] if options[:include_blank].is_a?(String))
39
41
  label = (" " unless content)
40
- option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
42
+ option_tags = tag_builder.option(content, value: "", label: label) + "\n" + option_tags
41
43
  end
42
44
 
43
45
  if value.blank? && options[:prompt]
@@ -45,7 +47,7 @@ module ActionView
45
47
  prompt_opts[:disabled] = true if options[:disabled] == ""
46
48
  prompt_opts[:selected] = true if options[:selected] == ""
47
49
  end
48
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
50
+ option_tags = tag_builder.option(prompt_text(options[:prompt]), **tag_options) + "\n" + option_tags
49
51
  end
50
52
 
51
53
  option_tags
@@ -10,7 +10,7 @@ module ActionView
10
10
 
11
11
  def render
12
12
  options = @options.stringify_keys
13
- add_default_name_and_id(options)
13
+ add_default_name_and_field(options)
14
14
 
15
15
  if size = options.delete("size")
16
16
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
@@ -13,7 +13,7 @@ module ActionView
13
13
  options["size"] = options["maxlength"] unless options.key?("size")
14
14
  options["type"] ||= field_type
15
15
  options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
16
- add_default_name_and_id(options)
16
+ add_default_name_and_field(options)
17
17
  tag("input", options)
18
18
  end
19
19