actionview 6.0.0 → 6.1.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +169 -162
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view/base.rb +21 -52
  6. data/lib/action_view/cache_expiry.rb +1 -3
  7. data/lib/action_view/context.rb +0 -1
  8. data/lib/action_view/dependency_tracker.rb +10 -4
  9. data/lib/action_view/digestor.rb +3 -2
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +1 -1
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +41 -16
  14. data/lib/action_view/helpers/asset_url_helper.rb +6 -4
  15. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  16. data/lib/action_view/helpers/cache_helper.rb +11 -18
  17. data/lib/action_view/helpers/date_helper.rb +5 -6
  18. data/lib/action_view/helpers/form_helper.rb +61 -19
  19. data/lib/action_view/helpers/form_options_helper.rb +7 -16
  20. data/lib/action_view/helpers/form_tag_helper.rb +9 -7
  21. data/lib/action_view/helpers/javascript_helper.rb +7 -5
  22. data/lib/action_view/helpers/number_helper.rb +9 -8
  23. data/lib/action_view/helpers/rendering_helper.rb +11 -3
  24. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  25. data/lib/action_view/helpers/tag_helper.rb +94 -19
  26. data/lib/action_view/helpers/tags/base.rb +9 -6
  27. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  28. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  29. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  30. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  31. data/lib/action_view/helpers/tags/color_field.rb +0 -1
  32. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  33. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  34. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  35. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  36. data/lib/action_view/helpers/tags/label.rb +4 -1
  37. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  39. data/lib/action_view/helpers/tags/select.rb +1 -2
  40. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  41. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  42. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  43. data/lib/action_view/helpers/text_helper.rb +1 -2
  44. data/lib/action_view/helpers/translation_helper.rb +98 -53
  45. data/lib/action_view/helpers/url_helper.rb +107 -13
  46. data/lib/action_view/layouts.rb +3 -5
  47. data/lib/action_view/log_subscriber.rb +26 -11
  48. data/lib/action_view/lookup_context.rb +7 -21
  49. data/lib/action_view/path_set.rb +0 -4
  50. data/lib/action_view/railtie.rb +35 -46
  51. data/lib/action_view/record_identifier.rb +0 -1
  52. data/lib/action_view/renderer/abstract_renderer.rb +92 -14
  53. data/lib/action_view/renderer/collection_renderer.rb +192 -0
  54. data/lib/action_view/renderer/object_renderer.rb +34 -0
  55. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
  56. data/lib/action_view/renderer/partial_renderer.rb +20 -283
  57. data/lib/action_view/renderer/renderer.rb +44 -1
  58. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -3
  59. data/lib/action_view/renderer/template_renderer.rb +16 -14
  60. data/lib/action_view/rendering.rb +3 -2
  61. data/lib/action_view/routing_url_for.rb +1 -1
  62. data/lib/action_view/template/error.rb +9 -14
  63. data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
  64. data/lib/action_view/template/handlers/erb.rb +10 -15
  65. data/lib/action_view/template/handlers.rb +0 -26
  66. data/lib/action_view/template/html.rb +1 -11
  67. data/lib/action_view/template/raw_file.rb +0 -3
  68. data/lib/action_view/template/renderable.rb +24 -0
  69. data/lib/action_view/template/resolver.rb +83 -45
  70. data/lib/action_view/template/text.rb +0 -3
  71. data/lib/action_view/template.rb +9 -50
  72. data/lib/action_view/test_case.rb +20 -28
  73. data/lib/action_view/testing/resolvers.rb +10 -32
  74. data/lib/action_view/unbound_template.rb +4 -5
  75. data/lib/action_view/view_paths.rb +34 -37
  76. data/lib/action_view.rb +5 -1
  77. data/lib/assets/compiled/rails-ujs.js +3 -3
  78. metadata +17 -11
@@ -21,7 +21,7 @@ module ActionView
21
21
  # could become:
22
22
  #
23
23
  # <select name="post[category]" id="post_category">
24
- # <option value=""></option>
24
+ # <option value="" label=" "></option>
25
25
  # <option value="joke">joke</option>
26
26
  # <option value="poem">poem</option>
27
27
  # </select>
@@ -74,7 +74,6 @@ module ActionView
74
74
  # could become:
75
75
  #
76
76
  # <select name="post[category]" id="post_category">
77
- # <option value=""></option>
78
77
  # <option value="joke">joke</option>
79
78
  # <option value="poem">poem</option>
80
79
  # <option disabled="disabled" value="restricted">restricted</option>
@@ -112,7 +111,7 @@ module ActionView
112
111
  # would become:
113
112
  #
114
113
  # <select name="post[person_id]" id="post_person_id">
115
- # <option value=""></option>
114
+ # <option value="" label=" "></option>
116
115
  # <option value="1" selected="selected">David</option>
117
116
  # <option value="2">Eileen</option>
118
117
  # <option value="3">Rafael</option>
@@ -143,7 +142,7 @@ module ActionView
143
142
  #
144
143
  # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
145
144
  # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
146
- # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
145
+ # if a +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
147
146
  # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
148
147
  # any mass-assignment idiom like
149
148
  #
@@ -569,7 +568,7 @@ module ActionView
569
568
  # be obtained in Active Record as a value object). The +model+ parameter
570
569
  # must respond to +all+ and return an array of objects that represent time
571
570
  # zones; each object must respond to +name+. If a Regexp is given it will
572
- # attempt to match the zones using the <code>=~<code> operator.
571
+ # attempt to match the zones using <code>match?</code> method.
573
572
  #
574
573
  # NOTE: Only the option tags are returned, you have to wrap this call in
575
574
  # a regular HTML select tag.
@@ -581,7 +580,7 @@ module ActionView
581
580
 
582
581
  if priority_zones
583
582
  if priority_zones.is_a?(Regexp)
584
- priority_zones = zones.select { |z| z =~ priority_zones }
583
+ priority_zones = zones.select { |z| z.match?(priority_zones) }
585
584
  end
586
585
 
587
586
  zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
@@ -795,7 +794,7 @@ module ActionView
795
794
  def extract_values_from_collection(collection, value_method, selected)
796
795
  if selected.is_a?(Proc)
797
796
  collection.map do |element|
798
- public_or_deprecated_send(element, value_method) if selected.call(element)
797
+ element.public_send(value_method) if selected.call(element)
799
798
  end.compact
800
799
  else
801
800
  selected
@@ -803,15 +802,7 @@ module ActionView
803
802
  end
804
803
 
805
804
  def value_for_collection(item, value)
806
- value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
807
- end
808
-
809
- def public_or_deprecated_send(item, value)
810
- item.public_send(value)
811
- rescue NoMethodError
812
- raise unless item.respond_to?(value, true) && !item.respond_to?(value)
813
- ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})"
814
- item.send(value)
805
+ value.respond_to?(:call) ? value.call(item) : item.public_send(value)
815
806
  end
816
807
 
817
808
  def prompt_text(prompt)
@@ -4,6 +4,7 @@ require "cgi"
4
4
  require "action_view/helpers/tag_helper"
5
5
  require "active_support/core_ext/string/output_safety"
6
6
  require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/core_ext/symbol/starts_ends_with"
7
8
 
8
9
  module ActionView
9
10
  # = Action View Form Tag Helpers
@@ -134,7 +135,7 @@ module ActionView
134
135
  # # <option selected="selected">MasterCard</option></select>
135
136
  def select_tag(name, option_tags = nil, options = {})
136
137
  option_tags ||= ""
137
- html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
138
+ html_name = (options[:multiple] == true && !name.end_with?("[]")) ? "#{name}[]" : name
138
139
 
139
140
  if options.include?(:include_blank)
140
141
  include_blank = options[:include_blank]
@@ -166,6 +167,8 @@ module ActionView
166
167
  # * <tt>:size</tt> - The number of visible characters that will fit in the input.
167
168
  # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
168
169
  # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
170
+ # If set to true, use a translation is found in the current I18n locale
171
+ # (through helpers.placeholders.<modelname>.<attribute>).
169
172
  # * Any other key creates standard HTML attributes for the tag.
170
173
  #
171
174
  # ==== Examples
@@ -894,16 +897,15 @@ module ActionView
894
897
  end
895
898
 
896
899
  def set_default_disable_with(value, tag_options)
897
- return unless ActionView::Base.automatically_disable_submit_tag
898
- data = tag_options["data"]
900
+ data = tag_options.fetch("data", {})
899
901
 
900
- unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false)
902
+ if tag_options["data-disable-with"] == false || data["disable_with"] == false
903
+ data.delete("disable_with")
904
+ elsif ActionView::Base.automatically_disable_submit_tag
901
905
  disable_with_text = tag_options["data-disable-with"]
902
- disable_with_text ||= data["disable_with"] if data
906
+ disable_with_text ||= data["disable_with"]
903
907
  disable_with_text ||= value.to_s.clone
904
908
  tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
905
- else
906
- data.delete("disable_with") if data
907
909
  end
908
910
 
909
911
  tag_options.delete("data-disable-with")
@@ -12,7 +12,9 @@ module ActionView
12
12
  "\n" => '\n',
13
13
  "\r" => '\n',
14
14
  '"' => '\\"',
15
- "'" => "\\'"
15
+ "'" => "\\'",
16
+ "`" => "\\`",
17
+ "$" => "\\$"
16
18
  }
17
19
 
18
20
  JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
@@ -29,7 +31,7 @@ module ActionView
29
31
  if javascript.empty?
30
32
  result = ""
31
33
  else
32
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
34
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
33
35
  end
34
36
  javascript.html_safe? ? result.html_safe : result
35
37
  end
@@ -49,10 +51,10 @@ module ActionView
49
51
  # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
50
52
  # tag.
51
53
  #
52
- # javascript_tag "alert('All is good')", defer: 'defer'
54
+ # javascript_tag "alert('All is good')", type: 'application/javascript'
53
55
  #
54
56
  # Returns:
55
- # <script defer="defer">
57
+ # <script type="application/javascript">
56
58
  # //<![CDATA[
57
59
  # alert('All is good')
58
60
  # //]]>
@@ -61,7 +63,7 @@ module ActionView
61
63
  # Instead of passing the content as an argument, you can also use a block
62
64
  # in which case, you pass your +html_options+ as the first parameter.
63
65
  #
64
- # <%= javascript_tag defer: 'defer' do -%>
66
+ # <%= javascript_tag type: 'application/javascript' do -%>
65
67
  # alert('All is good')
66
68
  # <% end -%>
67
69
  #
@@ -57,7 +57,7 @@ module ActionView
57
57
  #
58
58
  # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
59
59
  # # => "(755) 6123-4567"
60
- # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
60
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
61
61
  # # => "133-1234-5678"
62
62
  def number_to_phone(number, options = {})
63
63
  return unless number
@@ -114,6 +114,8 @@ module ActionView
114
114
  #
115
115
  # number_to_currency("123a456", raise: true) # => InvalidNumberError
116
116
  #
117
+ # number_to_currency(-0.456789, precision: 0)
118
+ # # => "$0"
117
119
  # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
118
120
  # # => ($1,234,567,890.50)
119
121
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
@@ -251,7 +253,7 @@ module ActionView
251
253
  end
252
254
 
253
255
  # Formats the bytes in +number+ into a more understandable
254
- # representation (e.g., giving it 1500 yields 1.5 KB). This
256
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
255
257
  # method is useful for reporting file sizes to users. You can
256
258
  # customize the format in the +options+ hash.
257
259
  #
@@ -297,7 +299,7 @@ module ActionView
297
299
  end
298
300
 
299
301
  # Pretty prints (formats and approximates) a number in a way it
300
- # is more readable by humans (eg.: 1200000000 becomes "1.2
302
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
301
303
  # Billion"). This is useful for numbers that can get very large
302
304
  # (and too hard to read).
303
305
  #
@@ -305,7 +307,7 @@ module ActionView
305
307
  # size.
306
308
  #
307
309
  # You can also define your own unit-quantifier names if you want
308
- # to use other decimal units (eg.: 1500 becomes "1.5
310
+ # to use other decimal units (e.g.: 1500 becomes "1.5
309
311
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
310
312
  # define a wide range of unit quantifiers, even fractional ones
311
313
  # (centi, deci, mili, etc).
@@ -403,7 +405,6 @@ module ActionView
403
405
  end
404
406
 
405
407
  private
406
-
407
408
  def delegate_number_helper_method(method, number, options)
408
409
  return unless number
409
410
  options = escape_unsafe_options(options.symbolize_keys)
@@ -424,9 +425,9 @@ module ActionView
424
425
  end
425
426
 
426
427
  def escape_units(units)
427
- Hash[units.map do |k, v|
428
- [k, ERB::Util.html_escape(v)]
429
- end]
428
+ units.transform_values do |v|
429
+ ERB::Util.html_escape(v)
430
+ end
430
431
  end
431
432
 
432
433
  def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
@@ -22,8 +22,12 @@ module ActionView
22
22
  # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
23
23
  # object.
24
24
  #
25
- # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
26
- # as the locals hash.
25
+ # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
26
+ #
27
+ # If an object responding to `render_in` is passed, `render_in` is called on the object,
28
+ # passing in the current view context.
29
+ #
30
+ # Otherwise, a partial is rendered using the second parameter as the locals hash.
27
31
  def render(options = {}, locals = {}, &block)
28
32
  case options
29
33
  when Hash
@@ -35,7 +39,11 @@ module ActionView
35
39
  end
36
40
  end
37
41
  else
38
- view_renderer.render_partial(self, partial: options, locals: locals, &block)
42
+ if options.respond_to?(:render_in)
43
+ options.render_in(self, &block)
44
+ else
45
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
46
+ end
39
47
  end
40
48
  end
41
49
 
@@ -129,11 +129,11 @@ module ActionView
129
129
  end
130
130
 
131
131
  def sanitized_allowed_tags
132
- safe_list_sanitizer.allowed_tags
132
+ sanitizer_vendor.safe_list_sanitizer.allowed_tags
133
133
  end
134
134
 
135
135
  def sanitized_allowed_attributes
136
- safe_list_sanitizer.allowed_attributes
136
+ sanitizer_vendor.safe_list_sanitizer.allowed_attributes
137
137
  end
138
138
 
139
139
  # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
@@ -13,19 +13,27 @@ module ActionView
13
13
  include CaptureHelper
14
14
  include OutputSafetyHelper
15
15
 
16
- BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
17
- compact controls declare default defaultchecked
18
- defaultmuted defaultselected defer disabled
19
- enabled formnovalidate hidden indeterminate inert
20
- ismap itemscope loop multiple muted nohref
21
- noresize noshade novalidate nowrap open
22
- pauseonexit readonly required reversed scoped
23
- seamless selected sortable truespeed typemustmatch
24
- visible).to_set
16
+ BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
17
+ autoplay checked compact controls declare default
18
+ defaultchecked defaultmuted defaultselected defer
19
+ disabled enabled formnovalidate hidden indeterminate
20
+ inert ismap itemscope loop multiple muted nohref
21
+ nomodule noresize noshade novalidate nowrap open
22
+ pauseonexit playsinline readonly required reversed
23
+ scoped seamless selected sortable truespeed
24
+ typemustmatch visible).to_set
25
25
 
26
26
  BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
27
+ BOOLEAN_ATTRIBUTES.freeze
27
28
 
28
- TAG_PREFIXES = ["aria", "data", :aria, :data].to_set
29
+ ARIA_PREFIXES = ["aria", :aria].to_set.freeze
30
+ DATA_PREFIXES = ["data", :data].to_set.freeze
31
+
32
+ TAG_TYPES = {}
33
+ TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
34
+ TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
35
+ TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
36
+ TAG_TYPES.freeze
29
37
 
30
38
  PRE_CONTENT_STRINGS = Hash.new { "" }
31
39
  PRE_CONTENT_STRINGS[:textarea] = "\n"
@@ -41,6 +49,10 @@ module ActionView
41
49
  @view_context = view_context
42
50
  end
43
51
 
52
+ def p(*arguments, **options, &block)
53
+ tag_string(:p, *arguments, **options, &block)
54
+ end
55
+
44
56
  def tag_string(name, content = nil, escape_attributes: true, **options, &block)
45
57
  content = @view_context.capture(self, &block) if block_given?
46
58
  if VOID_ELEMENTS.include?(name) && content.nil?
@@ -61,13 +73,31 @@ module ActionView
61
73
  output = +""
62
74
  sep = " "
63
75
  options.each_pair do |key, value|
64
- if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
76
+ type = TAG_TYPES[key]
77
+ if type == :data && value.is_a?(Hash)
78
+ value.each_pair do |k, v|
79
+ next if v.nil?
80
+ output << sep
81
+ output << prefix_tag_option(key, k, v, escape)
82
+ end
83
+ elsif type == :aria && value.is_a?(Hash)
65
84
  value.each_pair do |k, v|
66
85
  next if v.nil?
86
+
87
+ case v
88
+ when Array, Hash
89
+ tokens = TagHelper.build_tag_values(v)
90
+ next if tokens.none?
91
+
92
+ v = safe_join(tokens, " ")
93
+ else
94
+ v = v.to_s
95
+ end
96
+
67
97
  output << sep
68
98
  output << prefix_tag_option(key, k, v, escape)
69
99
  end
70
- elsif BOOLEAN_ATTRIBUTES.include?(key)
100
+ elsif type == :boolean
71
101
  if value
72
102
  output << sep
73
103
  output << boolean_tag_option(key)
@@ -85,12 +115,14 @@ module ActionView
85
115
  end
86
116
 
87
117
  def tag_option(key, value, escape)
88
- if value.is_a?(Array)
118
+ case value
119
+ when Array, Hash
120
+ value = TagHelper.build_tag_values(value) if key.to_s == "class"
89
121
  value = escape ? safe_join(value, " ") : value.join(" ")
90
122
  else
91
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
123
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
92
124
  end
93
- value.gsub!('"', "&quot;")
125
+ value = value.gsub('"', "&quot;") if value.include?('"')
94
126
  %(#{key}="#{value}")
95
127
  end
96
128
 
@@ -107,8 +139,8 @@ module ActionView
107
139
  true
108
140
  end
109
141
 
110
- def method_missing(called, *args, &block)
111
- tag_string(called, *args, &block)
142
+ def method_missing(called, *args, **options, &block)
143
+ tag_string(called, *args, **options, &block)
112
144
  end
113
145
  end
114
146
 
@@ -152,8 +184,8 @@ module ActionView
152
184
  # tag.input type: 'text', disabled: true
153
185
  # # => <input type="text" disabled="disabled">
154
186
  #
155
- # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
156
- # pointing to a hash of sub-attributes.
187
+ # HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
188
+ # single +data+ or +aria+ key pointing to a hash of sub-attributes.
157
189
  #
158
190
  # To play nicely with JavaScript conventions, sub-attributes are dasherized.
159
191
  #
@@ -233,6 +265,9 @@ module ActionView
233
265
  #
234
266
  # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
235
267
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
268
+ #
269
+ # tag("div", class: { highlight: current_user.admin? })
270
+ # # => <div class="highlight" />
236
271
  def tag(name = nil, options = nil, open = false, escape = true)
237
272
  if name.nil?
238
273
  tag_builder
@@ -260,6 +295,8 @@ module ActionView
260
295
  # # => <div class="strong"><p>Hello world!</p></div>
261
296
  # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
262
297
  # # => <div class="strong highlight">Hello world!</div>
298
+ # content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
299
+ # # => <div class="strong highlight">Hello world!</div>
263
300
  # content_tag("select", options, multiple: true)
264
301
  # # => <select multiple="multiple">...options...</select>
265
302
  #
@@ -276,6 +313,24 @@ module ActionView
276
313
  end
277
314
  end
278
315
 
316
+ # Returns a string of tokens built from +args+.
317
+ #
318
+ # ==== Examples
319
+ # token_list("foo", "bar")
320
+ # # => "foo bar"
321
+ # token_list("foo", "foo bar")
322
+ # # => "foo bar"
323
+ # token_list({ foo: true, bar: false })
324
+ # # => "foo"
325
+ # token_list(nil, false, 123, "", "foo", { bar: true })
326
+ # # => "123 foo bar"
327
+ def token_list(*args)
328
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
329
+
330
+ safe_join(tokens, " ")
331
+ end
332
+ alias_method :class_names, :token_list
333
+
279
334
  # Returns a CDATA section with the given +content+. CDATA sections
280
335
  # are used to escape blocks of text containing characters which would
281
336
  # otherwise be recognized as markup. CDATA sections begin with the string
@@ -306,6 +361,26 @@ module ActionView
306
361
  end
307
362
 
308
363
  private
364
+ def build_tag_values(*args)
365
+ tag_values = []
366
+
367
+ args.each do |tag_value|
368
+ case tag_value
369
+ when Hash
370
+ tag_value.each do |key, val|
371
+ tag_values << key.to_s if val && key.present?
372
+ end
373
+ when Array
374
+ tag_values.concat build_tag_values(*tag_value)
375
+ else
376
+ tag_values << tag_value.to_s if tag_value.present?
377
+ end
378
+ end
379
+
380
+ tag_values
381
+ end
382
+ module_function :build_tag_values
383
+
309
384
  def tag_builder
310
385
  @tag_builder ||= TagBuilder.new(self)
311
386
  end
@@ -34,7 +34,6 @@ module ActionView
34
34
  end
35
35
 
36
36
  private
37
-
38
37
  def value
39
38
  if @allow_method_names_outside_object
40
39
  object.public_send @method_name if object && object.respond_to?(@method_name)
@@ -106,7 +105,7 @@ module ActionView
106
105
  end
107
106
 
108
107
  def tag_name(multiple = false, index = nil)
109
- # a little duplication to construct less strings
108
+ # a little duplication to construct fewer strings
110
109
  case
111
110
  when @object_name.empty?
112
111
  "#{sanitized_method_name}#{multiple ? "[]" : ""}"
@@ -118,7 +117,7 @@ module ActionView
118
117
  end
119
118
 
120
119
  def tag_id(index = nil)
121
- # a little duplication to construct less strings
120
+ # a little duplication to construct fewer strings
122
121
  case
123
122
  when @object_name.empty?
124
123
  sanitized_method_name.dup
@@ -130,11 +129,11 @@ module ActionView
130
129
  end
131
130
 
132
131
  def sanitized_object_name
133
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
132
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
134
133
  end
135
134
 
136
135
  def sanitized_method_name
137
- @sanitized_method_name ||= @method_name.sub(/\?$/, "")
136
+ @sanitized_method_name ||= @method_name.delete_suffix("?")
138
137
  end
139
138
 
140
139
  def sanitized_value(value)
@@ -167,8 +166,11 @@ module ActionView
167
166
 
168
167
  def add_options(option_tags, options, value = nil)
169
168
  if options[:include_blank]
170
- option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags
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
171
172
  end
173
+
172
174
  if value.blank? && options[:prompt]
173
175
  tag_options = { value: "" }.tap do |prompt_opts|
174
176
  prompt_opts[:disabled] = true if options[:disabled] == ""
@@ -176,6 +178,7 @@ module ActionView
176
178
  end
177
179
  option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
178
180
  end
181
+
179
182
  option_tags
180
183
  end
181
184
 
@@ -39,7 +39,6 @@ module ActionView
39
39
  end
40
40
 
41
41
  private
42
-
43
42
  def checked?(value)
44
43
  case value
45
44
  when TrueClass, FalseClass
@@ -22,7 +22,6 @@ module ActionView
22
22
  end
23
23
 
24
24
  private
25
-
26
25
  def render_component(builder)
27
26
  builder.check_box + builder.label
28
27
  end
@@ -37,7 +37,6 @@ module ActionView
37
37
  end
38
38
 
39
39
  private
40
-
41
40
  def instantiate_builder(builder_class, item, value, text, html_options)
42
41
  builder_class.new(@template_object, @object_name, @method_name, item,
43
42
  sanitize_attribute_name(value), text, value, html_options)
@@ -21,7 +21,6 @@ module ActionView
21
21
  end
22
22
 
23
23
  private
24
-
25
24
  def render_component(builder)
26
25
  builder.radio_button + builder.label
27
26
  end
@@ -12,7 +12,6 @@ module ActionView
12
12
  end
13
13
 
14
14
  private
15
-
16
15
  def validate_color_string(string)
17
16
  regex = /#[0-9a-fA-F]{6}/
18
17
  if regex.match?(string)
@@ -5,9 +5,8 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class DateField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
- value.try(:strftime, "%Y-%m-%d")
9
+ value&.strftime("%Y-%m-%d")
11
10
  end
12
11
  end
13
12
  end
@@ -13,7 +13,7 @@ module ActionView
13
13
  end
14
14
 
15
15
  def render
16
- error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
16
+ error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe)
17
17
  end
18
18
 
19
19
  class << self
@@ -23,7 +23,6 @@ module ActionView
23
23
  end
24
24
 
25
25
  private
26
-
27
26
  def select_type
28
27
  self.class.select_type
29
28
  end
@@ -59,7 +58,7 @@ module ActionView
59
58
  time = Time.current
60
59
 
61
60
  [:year, :month, :day, :hour, :min, :sec].each do |key|
62
- default[key] ||= time.send(key)
61
+ default[key] ||= time.public_send(key)
63
62
  end
64
63
 
65
64
  Time.utc(
@@ -14,7 +14,6 @@ module ActionView
14
14
  end
15
15
 
16
16
  private
17
-
18
17
  def format_date(value)
19
18
  raise NotImplementedError
20
19
  end
@@ -11,9 +11,8 @@ module ActionView
11
11
  end
12
12
 
13
13
  private
14
-
15
14
  def format_date(value)
16
- value.try(:strftime, "%Y-%m-%dT%T")
15
+ value&.strftime("%Y-%m-%dT%T")
17
16
  end
18
17
  end
19
18
  end
@@ -25,6 +25,10 @@ module ActionView
25
25
 
26
26
  content
27
27
  end
28
+
29
+ def to_s
30
+ translation
31
+ end
28
32
  end
29
33
 
30
34
  def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
@@ -71,7 +75,6 @@ module ActionView
71
75
  end
72
76
 
73
77
  private
74
-
75
78
  def render_component(builder)
76
79
  builder.translation
77
80
  end
@@ -5,9 +5,8 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class MonthField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
- value.try(:strftime, "%Y-%m")
9
+ value&.strftime("%Y-%m")
11
10
  end
12
11
  end
13
12
  end