actionview 4.2.11.1 → 6.1.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +232 -186
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +115 -39
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +282 -83
  15. data/lib/action_view/helpers/asset_url_helper.rb +175 -69
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +107 -43
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +232 -130
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +808 -146
  25. data/lib/action_view/helpers/form_options_helper.rb +124 -78
  26. data/lib/action_view/helpers/form_tag_helper.rb +120 -74
  27. data/lib/action_view/helpers/javascript_helper.rb +33 -17
  28. data/lib/action_view/helpers/number_helper.rb +87 -62
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +21 -10
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +269 -68
  33. data/lib/action_view/helpers/tags/base.rb +141 -97
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
  50. data/lib/action_view/helpers/tags/label.rb +7 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +150 -68
  71. data/lib/action_view/helpers/url_helper.rb +284 -117
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +77 -10
  75. data/lib/action_view/lookup_context.rb +134 -91
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +26 -24
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +151 -14
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +55 -303
  85. data/lib/action_view/renderer/renderer.rb +66 -9
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +82 -73
  88. data/lib/action_view/rendering.rb +71 -45
  89. data/lib/action_view/routing_url_for.rb +34 -23
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +12 -8
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +263 -197
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +108 -92
  108. data/lib/action_view/test_case.rb +66 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +73 -58
  113. data/lib/action_view.rb +14 -8
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +42 -29
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,38 +1,240 @@
1
- require 'active_support/core_ext/string/output_safety'
2
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+ require "set"
3
5
 
4
6
  module ActionView
5
7
  # = Action View Tag Helpers
6
8
  module Helpers #:nodoc:
7
- # Provides methods to generate HTML tags programmatically when you can't use
8
- # a Builder. By default, they output XHTML compliant tags.
9
+ # Provides methods to generate HTML tags programmatically both as a modern
10
+ # HTML5 compliant builder style and legacy XHTML compliant tags.
9
11
  module TagHelper
10
12
  extend ActiveSupport::Concern
11
13
  include CaptureHelper
12
14
  include OutputSafetyHelper
13
15
 
14
- BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
15
- autoplay controls loop selected hidden scoped async
16
- defer reversed ismap seamless muted required
17
- autofocus novalidate formnovalidate open pubdate
18
- itemscope allowfullscreen default inert sortable
19
- truespeed typemustmatch).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
+
26
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
27
+ BOOLEAN_ATTRIBUTES.freeze
28
+
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
37
+
38
+ PRE_CONTENT_STRINGS = Hash.new { "" }
39
+ PRE_CONTENT_STRINGS[:textarea] = "\n"
40
+ PRE_CONTENT_STRINGS["textarea"] = "\n"
41
+
42
+ class TagBuilder #:nodoc:
43
+ include CaptureHelper
44
+ include OutputSafetyHelper
45
+
46
+ VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
47
+
48
+ def initialize(view_context)
49
+ @view_context = view_context
50
+ end
51
+
52
+ def p(*arguments, **options, &block)
53
+ tag_string(:p, *arguments, **options, &block)
54
+ end
55
+
56
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
57
+ content = @view_context.capture(self, &block) if block_given?
58
+ if VOID_ELEMENTS.include?(name) && content.nil?
59
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
60
+ else
61
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
62
+ end
63
+ end
64
+
65
+ def content_tag_string(name, content, options, escape = true)
66
+ tag_options = tag_options(options, escape) if options
67
+ content = ERB::Util.unwrapped_html_escape(content) if escape
68
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
69
+ end
20
70
 
21
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
71
+ def tag_options(options, escape = true)
72
+ return if options.blank?
73
+ output = +""
74
+ sep = " "
75
+ options.each_pair do |key, value|
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)
84
+ value.each_pair do |k, v|
85
+ next if v.nil?
22
86
 
23
- TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
87
+ case v
88
+ when Array, Hash
89
+ tokens = TagHelper.build_tag_values(v)
90
+ next if tokens.none?
24
91
 
25
- PRE_CONTENT_STRINGS = {
26
- :textarea => "\n"
27
- }
92
+ v = safe_join(tokens, " ")
93
+ else
94
+ v = v.to_s
95
+ end
28
96
 
29
- # Returns an empty HTML tag of type +name+ which by default is XHTML
97
+ output << sep
98
+ output << prefix_tag_option(key, k, v, escape)
99
+ end
100
+ elsif type == :boolean
101
+ if value
102
+ output << sep
103
+ output << boolean_tag_option(key)
104
+ end
105
+ elsif !value.nil?
106
+ output << sep
107
+ output << tag_option(key, value, escape)
108
+ end
109
+ end
110
+ output unless output.empty?
111
+ end
112
+
113
+ def boolean_tag_option(key)
114
+ %(#{key}="#{key}")
115
+ end
116
+
117
+ def tag_option(key, value, escape)
118
+ case value
119
+ when Array, Hash
120
+ value = TagHelper.build_tag_values(value) if key.to_s == "class"
121
+ value = escape ? safe_join(value, " ") : value.join(" ")
122
+ else
123
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
124
+ end
125
+ value = value.gsub('"', "&quot;") if value.include?('"')
126
+ %(#{key}="#{value}")
127
+ end
128
+
129
+ private
130
+ def prefix_tag_option(prefix, key, value, escape)
131
+ key = "#{prefix}-#{key.to_s.dasherize}"
132
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
133
+ value = value.to_json
134
+ end
135
+ tag_option(key, value, escape)
136
+ end
137
+
138
+ def respond_to_missing?(*args)
139
+ true
140
+ end
141
+
142
+ def method_missing(called, *args, **options, &block)
143
+ tag_string(called, *args, **options, &block)
144
+ end
145
+ end
146
+
147
+ # Returns an HTML tag.
148
+ #
149
+ # === Building HTML tags
150
+ #
151
+ # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
152
+ #
153
+ # tag.<tag name>(optional content, options)
154
+ #
155
+ # where tag name can be e.g. br, div, section, article, or any tag really.
156
+ #
157
+ # ==== Passing content
158
+ #
159
+ # Tags can pass content to embed within it:
160
+ #
161
+ # tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
162
+ #
163
+ # tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
164
+ #
165
+ # Content can also be captured with a block, which is useful in templates:
166
+ #
167
+ # <%= tag.p do %>
168
+ # The next great American novel starts here.
169
+ # <% end %>
170
+ # # => <p>The next great American novel starts here.</p>
171
+ #
172
+ # ==== Options
173
+ #
174
+ # Use symbol keyed options to add attributes to the generated tag.
175
+ #
176
+ # tag.section class: %w( kitties puppies )
177
+ # # => <section class="kitties puppies"></section>
178
+ #
179
+ # tag.section id: dom_id(@post)
180
+ # # => <section id="<generated dom id>"></section>
181
+ #
182
+ # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
183
+ #
184
+ # tag.input type: 'text', disabled: true
185
+ # # => <input type="text" disabled="disabled">
186
+ #
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.
189
+ #
190
+ # To play nicely with JavaScript conventions, sub-attributes are dasherized.
191
+ #
192
+ # tag.article data: { user_id: 123 }
193
+ # # => <article data-user-id="123"></article>
194
+ #
195
+ # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
196
+ #
197
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols and
198
+ # BigDecimals.
199
+ # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
200
+ # from 1.4.3.
201
+ #
202
+ # tag.div data: { city_state: %w( Chicago IL ) }
203
+ # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
204
+ #
205
+ # The generated attributes are escaped by default. This can be disabled using
206
+ # +escape_attributes+.
207
+ #
208
+ # tag.img src: 'open & shut.png'
209
+ # # => <img src="open &amp; shut.png">
210
+ #
211
+ # tag.img src: 'open & shut.png', escape_attributes: false
212
+ # # => <img src="open & shut.png">
213
+ #
214
+ # The tag builder respects
215
+ # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
216
+ # if no content is passed, and omits closing tags for those elements.
217
+ #
218
+ # # A standard element:
219
+ # tag.div # => <div></div>
220
+ #
221
+ # # A void element:
222
+ # tag.br # => <br>
223
+ #
224
+ # === Legacy syntax
225
+ #
226
+ # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
227
+ #
228
+ # tag(name, options = nil, open = false, escape = true)
229
+ #
230
+ # It returns an empty HTML tag of type +name+ which by default is XHTML
30
231
  # compliant. Set +open+ to true to create an open tag compatible
31
232
  # with HTML 4.0 and below. Add HTML attributes by passing an attributes
32
233
  # hash to +options+. Set +escape+ to false to disable attribute value
33
234
  # escaping.
34
235
  #
35
236
  # ==== Options
237
+ #
36
238
  # You can use symbols or strings for the attribute names.
37
239
  #
38
240
  # Use +true+ with boolean attributes that can render with no value, like
@@ -41,16 +243,8 @@ module ActionView
41
243
  # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
42
244
  # pointing to a hash of sub-attributes.
43
245
  #
44
- # To play nicely with JavaScript conventions sub-attributes are dasherized.
45
- # For example, a key +user_id+ would render as <tt>data-user-id</tt> and
46
- # thus accessed as <tt>dataset.userId</tt>.
47
- #
48
- # Values are encoded to JSON, with the exception of strings, symbols and
49
- # BigDecimals.
50
- # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
51
- # from 1.4.3.
52
- #
53
246
  # ==== Examples
247
+ #
54
248
  # tag("br")
55
249
  # # => <br />
56
250
  #
@@ -66,13 +260,20 @@ module ActionView
66
260
  # tag("img", src: "open & shut.png")
67
261
  # # => <img src="open &amp; shut.png" />
68
262
  #
69
- # tag("img", {src: "open &amp; shut.png"}, false, false)
263
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
70
264
  # # => <img src="open &amp; shut.png" />
71
265
  #
72
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
266
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
73
267
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
74
- def tag(name, options = nil, open = false, escape = true)
75
- "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
268
+ #
269
+ # tag("div", class: { highlight: current_user.admin? })
270
+ # # => <div class="highlight" />
271
+ def tag(name = nil, options = nil, open = false, escape = true)
272
+ if name.nil?
273
+ tag_builder
274
+ else
275
+ "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
276
+ end
76
277
  end
77
278
 
78
279
  # Returns an HTML block tag of type +name+ surrounding the +content+. Add
@@ -80,6 +281,7 @@ module ActionView
80
281
  # Instead of passing the content as an argument, you can also use a block
81
282
  # in which case, you pass your +options+ as the second parameter.
82
283
  # Set escape to false to disable attribute value escaping.
284
+ # Note: this is legacy syntax, see +tag+ method description for details.
83
285
  #
84
286
  # ==== Options
85
287
  # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
@@ -93,6 +295,8 @@ module ActionView
93
295
  # # => <div class="strong"><p>Hello world!</p></div>
94
296
  # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
95
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>
96
300
  # content_tag("select", options, multiple: true)
97
301
  # # => <select multiple="multiple">...options...</select>
98
302
  #
@@ -103,12 +307,30 @@ module ActionView
103
307
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
104
308
  if block_given?
105
309
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
106
- content_tag_string(name, capture(&block), options, escape)
310
+ tag_builder.content_tag_string(name, capture(&block), options, escape)
107
311
  else
108
- content_tag_string(name, content_or_options_with_block, options, escape)
312
+ tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
109
313
  end
110
314
  end
111
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
+
112
334
  # Returns a CDATA section with the given +content+. CDATA sections
113
335
  # are used to escape blocks of text containing characters which would
114
336
  # otherwise be recognized as markup. CDATA sections begin with the string
@@ -123,7 +345,7 @@ module ActionView
123
345
  # cdata_section("hello]]>world")
124
346
  # # => <![CDATA[hello]]]]><![CDATA[>world]]>
125
347
  def cdata_section(content)
126
- splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>')
348
+ splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
127
349
  "<![CDATA[#{splitted}]]>".html_safe
128
350
  end
129
351
 
@@ -139,49 +361,28 @@ module ActionView
139
361
  end
140
362
 
141
363
  private
364
+ def build_tag_values(*args)
365
+ tag_values = []
142
366
 
143
- def content_tag_string(name, content, options, escape = true)
144
- tag_options = tag_options(options, escape) if options
145
- content = ERB::Util.unwrapped_html_escape(content) if escape
146
- "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
147
- end
148
-
149
- def tag_options(options, escape = true)
150
- return if options.blank?
151
- attrs = []
152
- options.each_pair do |key, value|
153
- if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
154
- value.each_pair do |k, v|
155
- attrs << prefix_tag_option(key, k, v, escape)
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?
156
372
  end
157
- elsif BOOLEAN_ATTRIBUTES.include?(key)
158
- attrs << boolean_tag_option(key) if value
159
- elsif !value.nil?
160
- attrs << tag_option(key, value, escape)
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?
161
377
  end
162
378
  end
163
- " #{attrs * ' '}" unless attrs.empty?
164
- end
165
379
 
166
- def prefix_tag_option(prefix, key, value, escape)
167
- key = "#{prefix}-#{key.to_s.dasherize}"
168
- unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
169
- value = value.to_json
170
- end
171
- tag_option(key, value, escape)
380
+ tag_values
172
381
  end
382
+ module_function :build_tag_values
173
383
 
174
- def boolean_tag_option(key)
175
- %(#{key}="#{key}")
176
- end
177
-
178
- def tag_option(key, value, escape)
179
- if value.is_a?(Array)
180
- value = escape ? safe_join(value, " ") : value.join(" ")
181
- else
182
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
183
- end
184
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
384
+ def tag_builder
385
+ @tag_builder ||= TagBuilder.new(self)
185
386
  end
186
387
  end
187
388
  end