actionview 6.0.0.beta1 → 6.1.4

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +273 -119
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/cache_expiry.rb +52 -0
  7. data/lib/action_view/context.rb +0 -5
  8. data/lib/action_view/dependency_tracker.rb +10 -4
  9. data/lib/action_view/digestor.rb +11 -19
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +62 -22
  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 +16 -23
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/date_helper.rb +5 -6
  19. data/lib/action_view/helpers/form_helper.rb +70 -34
  20. data/lib/action_view/helpers/form_options_helper.rb +10 -18
  21. data/lib/action_view/helpers/form_tag_helper.rb +12 -9
  22. data/lib/action_view/helpers/javascript_helper.rb +7 -5
  23. data/lib/action_view/helpers/number_helper.rb +9 -8
  24. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  25. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  26. data/lib/action_view/helpers/sanitize_helper.rb +10 -16
  27. data/lib/action_view/helpers/tag_helper.rb +94 -19
  28. data/lib/action_view/helpers/tags/base.rb +10 -7
  29. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  33. data/lib/action_view/helpers/tags/color_field.rb +0 -1
  34. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  35. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  36. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  37. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/label.rb +4 -1
  39. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  40. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  41. data/lib/action_view/helpers/tags/select.rb +1 -2
  42. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  43. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  44. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  45. data/lib/action_view/helpers/text_helper.rb +2 -3
  46. data/lib/action_view/helpers/translation_helper.rb +98 -51
  47. data/lib/action_view/helpers/url_helper.rb +124 -16
  48. data/lib/action_view/layouts.rb +8 -10
  49. data/lib/action_view/log_subscriber.rb +26 -11
  50. data/lib/action_view/lookup_context.rb +59 -31
  51. data/lib/action_view/path_set.rb +3 -12
  52. data/lib/action_view/railtie.rb +39 -41
  53. data/lib/action_view/record_identifier.rb +0 -1
  54. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  55. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  56. data/lib/action_view/renderer/object_renderer.rb +34 -0
  57. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +35 -29
  58. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  59. data/lib/action_view/renderer/renderer.rb +59 -4
  60. data/lib/action_view/renderer/streaming_template_renderer.rb +9 -7
  61. data/lib/action_view/renderer/template_renderer.rb +35 -27
  62. data/lib/action_view/rendering.rb +49 -29
  63. data/lib/action_view/routing_url_for.rb +1 -1
  64. data/lib/action_view/template/error.rb +30 -15
  65. data/lib/action_view/template/handlers/builder.rb +2 -2
  66. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  67. data/lib/action_view/template/handlers/erb.rb +14 -19
  68. data/lib/action_view/template/handlers/html.rb +1 -1
  69. data/lib/action_view/template/handlers/raw.rb +2 -2
  70. data/lib/action_view/template/handlers.rb +1 -1
  71. data/lib/action_view/template/html.rb +5 -6
  72. data/lib/action_view/template/inline.rb +22 -0
  73. data/lib/action_view/template/raw_file.rb +25 -0
  74. data/lib/action_view/template/renderable.rb +24 -0
  75. data/lib/action_view/template/resolver.rb +141 -140
  76. data/lib/action_view/template/sources/file.rb +17 -0
  77. data/lib/action_view/template/sources.rb +13 -0
  78. data/lib/action_view/template/text.rb +2 -3
  79. data/lib/action_view/template.rb +49 -75
  80. data/lib/action_view/test_case.rb +20 -28
  81. data/lib/action_view/testing/resolvers.rb +18 -27
  82. data/lib/action_view/unbound_template.rb +31 -0
  83. data/lib/action_view/view_paths.rb +59 -38
  84. data/lib/action_view.rb +7 -2
  85. data/lib/assets/compiled/rails-ujs.js +25 -16
  86. metadata +30 -18
@@ -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,15 +129,15 @@ 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)
141
- value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s
140
+ value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
142
141
  end
143
142
 
144
143
  def select_content_tag(option_tags, options, html_options)
@@ -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
@@ -23,7 +23,6 @@ module ActionView
23
23
  end
24
24
 
25
25
  private
26
-
27
26
  def checked?(value)
28
27
  value.to_s == @tag_value.to_s
29
28
  end
@@ -15,7 +15,7 @@ module ActionView
15
15
 
16
16
  def render
17
17
  option_tags_options = {
18
- selected: @options.fetch(:selected) { value },
18
+ selected: @options.fetch(:selected) { value.nil? ? "" : value },
19
19
  disabled: @options[:disabled]
20
20
  }
21
21
 
@@ -29,7 +29,6 @@ module ActionView
29
29
  end
30
30
 
31
31
  private
32
-
33
32
  # Grouped choices look like this:
34
33
  #
35
34
  # [nil, []]
@@ -24,7 +24,6 @@ module ActionView
24
24
  end
25
25
 
26
26
  private
27
-
28
27
  def field_type
29
28
  self.class.field_type
30
29
  end
@@ -5,9 +5,8 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class TimeField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
- value.try(:strftime, "%T.%L")
9
+ value&.strftime("%T.%L")
11
10
  end
12
11
  end
13
12
  end
@@ -5,9 +5,8 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class WeekField < DatetimeField # :nodoc:
7
7
  private
8
-
9
8
  def format_date(value)
10
- value.try(:strftime, "%Y-W%V")
9
+ value&.strftime("%Y-W%V")
11
10
  end
12
11
  end
13
12
  end
@@ -105,7 +105,7 @@ module ActionView
105
105
  # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
106
106
  # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
107
107
  # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
108
- # '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
108
+ # <tt><mark>\1</mark></tt>) or passing a block that receives each matched term. By default +text+
109
109
  # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
110
110
  # for <tt>:sanitize</tt> will turn sanitizing off.
111
111
  #
@@ -228,7 +228,7 @@ module ActionView
228
228
  # pluralize(2, 'Person', locale: :de)
229
229
  # # => 2 Personen
230
230
  def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
- word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
231
+ word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
232
232
  singular
233
233
  else
234
234
  plural || singular.pluralize(locale)
@@ -426,7 +426,6 @@ module ActionView
426
426
  end
427
427
 
428
428
  private
429
-
430
429
  def next_index
431
430
  step_index(1)
432
431
  end