actionview 7.1.3.4 → 7.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -418
  3. data/lib/action_view/base.rb +20 -3
  4. data/lib/action_view/cache_expiry.rb +9 -3
  5. data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
  6. data/lib/action_view/dependency_tracker.rb +1 -1
  7. data/lib/action_view/gem_version.rb +3 -3
  8. data/lib/action_view/helpers/asset_tag_helper.rb +18 -6
  9. data/lib/action_view/helpers/cache_helper.rb +4 -4
  10. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  11. data/lib/action_view/helpers/form_helper.rb +197 -192
  12. data/lib/action_view/helpers/form_tag_helper.rb +76 -43
  13. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  14. data/lib/action_view/helpers/tag_helper.rb +208 -18
  15. data/lib/action_view/helpers/url_helper.rb +4 -78
  16. data/lib/action_view/layouts.rb +2 -4
  17. data/lib/action_view/log_subscriber.rb +8 -4
  18. data/lib/action_view/railtie.rb +0 -1
  19. data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
  20. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +152 -9
  21. data/lib/action_view/render_parser.rb +21 -169
  22. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  23. data/lib/action_view/renderer/renderer.rb +32 -38
  24. data/lib/action_view/rendering.rb +4 -4
  25. data/lib/action_view/template/renderable.rb +7 -1
  26. data/lib/action_view/template/resolver.rb +0 -2
  27. data/lib/action_view/template.rb +28 -4
  28. data/lib/action_view/test_case.rb +12 -14
  29. data/lib/action_view/unbound_template.rb +4 -4
  30. data/lib/action_view.rb +1 -1
  31. metadata +16 -15
@@ -522,25 +522,6 @@ module ActionView
522
522
  # submit_tag "Edit", class: "edit_button"
523
523
  # # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
524
524
  #
525
- # ==== Deprecated: \Rails UJS attributes
526
- #
527
- # Prior to \Rails 7, \Rails shipped with the JavaScript library called @rails/ujs on by default. Following \Rails 7,
528
- # this library is no longer on by default. This library integrated with the following options:
529
- #
530
- # * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
531
- # drivers will provide a prompt with the question specified. If the user accepts,
532
- # the form is processed normally, otherwise no action is taken.
533
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
534
- # disabled version of the submit button when the form is submitted. This feature is
535
- # provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag
536
- # pass <tt>:data => { disable_with: false }</tt> Defaults to value attribute.
537
- #
538
- # submit_tag "Complete sale", data: { disable_with: "Submitting..." }
539
- # # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" />
540
- #
541
- # submit_tag "Save", data: { confirm: "Are you sure?" }
542
- # # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />
543
- #
544
525
  def submit_tag(value = "Save changes", options = {})
545
526
  options = options.deep_stringify_keys
546
527
  tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)
@@ -582,26 +563,6 @@ module ActionView
582
563
  # # <strong>Ask me!</strong>
583
564
  # # </button>
584
565
  #
585
- # ==== Deprecated: \Rails UJS attributes
586
- #
587
- # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7,
588
- # this library is no longer on by default. This library integrated with the following options:
589
- #
590
- # * <tt>confirm: 'question?'</tt> - If present, the
591
- # unobtrusive JavaScript drivers will provide a prompt with
592
- # the question specified. If the user accepts, the form is
593
- # processed normally, otherwise no action is taken.
594
- # * <tt>:disable_with</tt> - Value of this parameter will be
595
- # used as the value for a disabled version of the submit
596
- # button when the form is submitted. This feature is provided
597
- # by the unobtrusive JavaScript driver.
598
- #
599
- # button_tag "Save", data: { confirm: "Are you sure?" }
600
- # # => <button name="button" type="submit" data-confirm="Are you sure?">Save</button>
601
- #
602
- # button_tag "Checkout", data: { disable_with: "Please wait..." }
603
- # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
604
- #
605
566
  def button_tag(content_or_options = nil, options = nil, &block)
606
567
  if content_or_options.is_a? Hash
607
568
  options = content_or_options
@@ -675,11 +636,13 @@ module ActionView
675
636
  # <% end %>
676
637
  # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
677
638
  def field_set_tag(legend = nil, options = nil, &block)
678
- output = tag(:fieldset, options, true)
679
- output.safe_concat(content_tag("legend", legend)) unless legend.blank?
680
- output.concat(capture(&block)) if block_given?
681
- output.safe_concat("</fieldset>")
639
+ content = []
640
+ content << content_tag("legend", legend) unless legend.blank?
641
+ content << capture(&block) if block_given?
642
+
643
+ content_tag(:fieldset, safe_join(content), options)
682
644
  end
645
+ alias_method :fieldset_tag, :field_set_tag
683
646
 
684
647
  # Creates a text field of type "color".
685
648
  #
@@ -784,6 +747,23 @@ module ActionView
784
747
  # * <tt>:max</tt> - The maximum acceptable value.
785
748
  # * <tt>:step</tt> - The acceptable value granularity.
786
749
  # * <tt>:include_seconds</tt> - Include seconds and ms in the output timestamp format (true by default).
750
+ #
751
+ # ==== Examples
752
+ #
753
+ # time_field_tag 'name'
754
+ # # => <input id="name" name="name" type="time" />
755
+ #
756
+ # time_field_tag 'time', '01:01'
757
+ # # => <input id="time" name="time" type="time" value="01:01" />
758
+ #
759
+ # time_field_tag 'time', nil, class: 'special_input'
760
+ # # => <input class="special_input" id="time" name="time" type="time" />
761
+ #
762
+ # time_field_tag 'time', '01:01', include_seconds: true
763
+ # # => <input id="time" name="time" type="time" value="01:01:00.000" />
764
+ #
765
+ # time_field_tag 'time', '01:01', min: '00:00', max: '23:59', step: 1
766
+ # # => <input id="time" max="23:59" min="00:00" name="time" step="1" type="time" value="01:01" />
787
767
  def time_field_tag(name, value = nil, options = {})
788
768
  text_field_tag(name, value, options.merge(type: :time))
789
769
  end
@@ -798,6 +778,20 @@ module ActionView
798
778
  # * <tt>:max</tt> - The maximum acceptable value.
799
779
  # * <tt>:step</tt> - The acceptable value granularity.
800
780
  # * <tt>:include_seconds</tt> - Include seconds in the output timestamp format (true by default).
781
+ #
782
+ # ==== Examples
783
+ #
784
+ # datetime_field_tag 'name'
785
+ # # => <input id="name" name="name" type="datetime-local" />
786
+ #
787
+ # datetime_field_tag 'datetime', '2014-01-01T01:01'
788
+ # # => <input id="datetime" name="datetime" type="datetime-local" value="2014-01-01T01:01" />
789
+ #
790
+ # datetime_field_tag 'datetime', nil, class: 'special_input'
791
+ # # => <input class="special_input" id="datetime" name="datetime" type="datetime-local" />
792
+ #
793
+ # datetime_field_tag 'datetime', '2014-01-01T01:01', class: 'special_input', disabled: true
794
+ # # => <input disabled="disabled" class="special_input" id="datetime" name="datetime" type="datetime-local" value="2014-01-01T01:01" />
801
795
  def datetime_field_tag(name, value = nil, options = {})
802
796
  text_field_tag(name, value, options.merge(type: "datetime-local"))
803
797
  end
@@ -813,6 +807,20 @@ module ActionView
813
807
  # * <tt>:min</tt> - The minimum acceptable value.
814
808
  # * <tt>:max</tt> - The maximum acceptable value.
815
809
  # * <tt>:step</tt> - The acceptable value granularity.
810
+ #
811
+ # ==== Examples
812
+ #
813
+ # month_field_tag 'name'
814
+ # # => <input id="name" name="name" type="month" />
815
+ #
816
+ # month_field_tag 'month', '2014-01'
817
+ # # => <input id="month" name="month" type="month" value="2014-01" />
818
+ #
819
+ # month_field_tag 'month', nil, class: 'special_input'
820
+ # # => <input class="special_input" id="month" name="month" type="month" />
821
+ #
822
+ # month_field_tag 'month', '2014-01', class: 'special_input', disabled: true
823
+ # # => <input disabled="disabled" class="special_input" id="month" name="month" type="month" value="2014-01" />
816
824
  def month_field_tag(name, value = nil, options = {})
817
825
  text_field_tag(name, value, options.merge(type: :month))
818
826
  end
@@ -826,6 +834,20 @@ module ActionView
826
834
  # * <tt>:min</tt> - The minimum acceptable value.
827
835
  # * <tt>:max</tt> - The maximum acceptable value.
828
836
  # * <tt>:step</tt> - The acceptable value granularity.
837
+ #
838
+ # ==== Examples
839
+ #
840
+ # week_field_tag 'name'
841
+ # # => <input id="name" name="name" type="week" />
842
+ #
843
+ # week_field_tag 'week', '2014-W01'
844
+ # # => <input id="week" name="week" type="week" value="2014-W01" />
845
+ #
846
+ # week_field_tag 'week', nil, class: 'special_input'
847
+ # # => <input class="special_input" id="week" name="week" type="week" />
848
+ #
849
+ # week_field_tag 'week', '2014-W01', class: 'special_input', disabled: true
850
+ # # => <input disabled="disabled" class="special_input" id="week" name="week" type="week" value="2014-W01" />
829
851
  def week_field_tag(name, value = nil, options = {})
830
852
  text_field_tag(name, value, options.merge(type: :week))
831
853
  end
@@ -934,6 +956,17 @@ module ActionView
934
956
  # ==== Options
935
957
  #
936
958
  # Supports the same options as #number_field_tag.
959
+ #
960
+ # ==== Examples
961
+ #
962
+ # range_field_tag 'quantity', '1'
963
+ # # => <input id="quantity" name="quantity" type="range" value="1" />
964
+ #
965
+ # range_field_tag 'quantity', in: 1...10
966
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="range" />
967
+ #
968
+ # range_field_tag 'quantity', min: 1, max: 10, step: 2
969
+ # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="range"
937
970
  def range_field_tag(name, value = nil, options = {})
938
971
  number_field_tag(name, value, options.merge(type: :range))
939
972
  end
@@ -24,11 +24,11 @@ module ActionView # :nodoc:
24
24
  # the supplied separator, are HTML escaped unless they are HTML
25
25
  # safe, and the returned string is marked as HTML safe.
26
26
  #
27
- # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
28
- # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
27
+ # safe_join([tag.p("foo"), "<p>bar</p>"], "<br>")
28
+ # # => "<p>foo</p>&lt;br&gt;&lt;p&gt;bar&lt;/p&gt;"
29
29
  #
30
- # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
31
- # # => "<p>foo</p><br /><p>bar</p>"
30
+ # safe_join([tag.p("foo"), tag.p("bar")], tag.br)
31
+ # # => "<p>foo</p><br><p>bar</p>"
32
32
  #
33
33
  def safe_join(array, sep = $,)
34
34
  sep = ERB::Util.unwrapped_html_escape(sep)
@@ -1,7 +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"
6
+ require "active_support/core_ext/string/inflections"
5
7
  require "set"
6
8
  require "action_view/helpers/capture_helper"
7
9
  require "action_view/helpers/output_safety_helper"
@@ -46,8 +48,183 @@ module ActionView
46
48
  include CaptureHelper
47
49
  include OutputSafetyHelper
48
50
 
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
51
+ def self.define_element(name, code_generator:, method_name: name.to_s.underscore)
52
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
53
+ batch.push(<<~RUBY) unless instance_methods.include?(method_name.to_sym)
54
+ def #{method_name}(content = nil, escape: true, **options, &block)
55
+ tag_string("#{name}", content, options, escape: escape, &block)
56
+ end
57
+ RUBY
58
+ end
59
+ end
60
+
61
+ def self.define_void_element(name, code_generator:, method_name: name.to_s.underscore)
62
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
63
+ batch.push(<<~RUBY)
64
+ def #{method_name}(content = nil, escape: true, **options, &block)
65
+ if content || block
66
+ ActionView.deprecator.warn <<~TEXT
67
+ Putting content inside a void element (#{name}) is invalid
68
+ according to the HTML5 spec, and so it is being deprecated
69
+ without replacement. In Rails 8.0, passing content as a
70
+ positional argument will raise, and using a block will have
71
+ no effect.
72
+ TEXT
73
+ tag_string("#{name}", content, options, escape: escape, &block)
74
+ else
75
+ self_closing_tag_string("#{name}", options, escape, ">")
76
+ end
77
+ end
78
+ RUBY
79
+ end
80
+ end
81
+
82
+ def self.define_self_closing_element(name, code_generator:, method_name: name.to_s.underscore)
83
+ code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
84
+ batch.push(<<~RUBY)
85
+ def #{method_name}(content = nil, escape: true, **options, &block)
86
+ if content || block
87
+ tag_string("#{name}", content, options, escape: escape, &block)
88
+ else
89
+ self_closing_tag_string("#{name}", options, escape)
90
+ end
91
+ end
92
+ RUBY
93
+ end
94
+ end
95
+
96
+ ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
97
+ define_void_element :area, code_generator: code_generator
98
+ define_void_element :base, code_generator: code_generator
99
+ define_void_element :br, code_generator: code_generator
100
+ define_void_element :col, code_generator: code_generator
101
+ define_void_element :embed, code_generator: code_generator
102
+ define_void_element :hr, code_generator: code_generator
103
+ define_void_element :img, code_generator: code_generator
104
+ define_void_element :input, code_generator: code_generator
105
+ define_void_element :keygen, code_generator: code_generator
106
+ define_void_element :link, code_generator: code_generator
107
+ define_void_element :meta, code_generator: code_generator
108
+ define_void_element :source, code_generator: code_generator
109
+ define_void_element :track, code_generator: code_generator
110
+ define_void_element :wbr, code_generator: code_generator
111
+
112
+ define_self_closing_element :animate, code_generator: code_generator
113
+ define_self_closing_element :animateMotion, code_generator: code_generator
114
+ define_self_closing_element :animateTransform, code_generator: code_generator
115
+ define_self_closing_element :circle, code_generator: code_generator
116
+ define_self_closing_element :ellipse, code_generator: code_generator
117
+ define_self_closing_element :line, code_generator: code_generator
118
+ define_self_closing_element :path, code_generator: code_generator
119
+ define_self_closing_element :polygon, code_generator: code_generator
120
+ define_self_closing_element :polyline, code_generator: code_generator
121
+ define_self_closing_element :rect, code_generator: code_generator
122
+ define_self_closing_element :set, code_generator: code_generator
123
+ define_self_closing_element :stop, code_generator: code_generator
124
+ define_self_closing_element :use, code_generator: code_generator
125
+ define_self_closing_element :view, code_generator: code_generator
126
+
127
+ define_element :a, code_generator: code_generator
128
+ define_element :abbr, code_generator: code_generator
129
+ define_element :address, code_generator: code_generator
130
+ define_element :article, code_generator: code_generator
131
+ define_element :aside, code_generator: code_generator
132
+ define_element :audio, code_generator: code_generator
133
+ define_element :b, code_generator: code_generator
134
+ define_element :bdi, code_generator: code_generator
135
+ define_element :bdo, code_generator: code_generator
136
+ define_element :blockquote, code_generator: code_generator
137
+ define_element :body, code_generator: code_generator
138
+ define_element :button, code_generator: code_generator
139
+ define_element :canvas, code_generator: code_generator
140
+ define_element :caption, code_generator: code_generator
141
+ define_element :cite, code_generator: code_generator
142
+ define_element :code, code_generator: code_generator
143
+ define_element :colgroup, code_generator: code_generator
144
+ define_element :data, code_generator: code_generator
145
+ define_element :datalist, code_generator: code_generator
146
+ define_element :dd, code_generator: code_generator
147
+ define_element :del, code_generator: code_generator
148
+ define_element :details, code_generator: code_generator
149
+ define_element :dfn, code_generator: code_generator
150
+ define_element :dialog, code_generator: code_generator
151
+ define_element :div, code_generator: code_generator
152
+ define_element :dl, code_generator: code_generator
153
+ define_element :dt, code_generator: code_generator
154
+ define_element :em, code_generator: code_generator
155
+ define_element :fieldset, code_generator: code_generator
156
+ define_element :figcaption, code_generator: code_generator
157
+ define_element :figure, code_generator: code_generator
158
+ define_element :footer, code_generator: code_generator
159
+ define_element :form, code_generator: code_generator
160
+ define_element :h1, code_generator: code_generator
161
+ define_element :h2, code_generator: code_generator
162
+ define_element :h3, code_generator: code_generator
163
+ define_element :h4, code_generator: code_generator
164
+ define_element :h5, code_generator: code_generator
165
+ define_element :h6, code_generator: code_generator
166
+ define_element :head, code_generator: code_generator
167
+ define_element :header, code_generator: code_generator
168
+ define_element :hgroup, code_generator: code_generator
169
+ define_element :html, code_generator: code_generator
170
+ define_element :i, code_generator: code_generator
171
+ define_element :iframe, code_generator: code_generator
172
+ define_element :ins, code_generator: code_generator
173
+ define_element :kbd, code_generator: code_generator
174
+ define_element :label, code_generator: code_generator
175
+ define_element :legend, code_generator: code_generator
176
+ define_element :li, code_generator: code_generator
177
+ define_element :main, code_generator: code_generator
178
+ define_element :map, code_generator: code_generator
179
+ define_element :mark, code_generator: code_generator
180
+ define_element :menu, code_generator: code_generator
181
+ define_element :meter, code_generator: code_generator
182
+ define_element :nav, code_generator: code_generator
183
+ define_element :noscript, code_generator: code_generator
184
+ define_element :object, code_generator: code_generator
185
+ define_element :ol, code_generator: code_generator
186
+ define_element :optgroup, code_generator: code_generator
187
+ define_element :option, code_generator: code_generator
188
+ define_element :output, code_generator: code_generator
189
+ define_element :p, code_generator: code_generator
190
+ define_element :picture, code_generator: code_generator
191
+ define_element :portal, code_generator: code_generator
192
+ define_element :pre, code_generator: code_generator
193
+ define_element :progress, code_generator: code_generator
194
+ define_element :q, code_generator: code_generator
195
+ define_element :rp, code_generator: code_generator
196
+ define_element :rt, code_generator: code_generator
197
+ define_element :ruby, code_generator: code_generator
198
+ define_element :s, code_generator: code_generator
199
+ define_element :samp, code_generator: code_generator
200
+ define_element :script, code_generator: code_generator
201
+ define_element :search, code_generator: code_generator
202
+ define_element :section, code_generator: code_generator
203
+ define_element :select, code_generator: code_generator
204
+ define_element :slot, code_generator: code_generator
205
+ define_element :small, code_generator: code_generator
206
+ define_element :span, code_generator: code_generator
207
+ define_element :strong, code_generator: code_generator
208
+ define_element :style, code_generator: code_generator
209
+ define_element :sub, code_generator: code_generator
210
+ define_element :summary, code_generator: code_generator
211
+ define_element :sup, code_generator: code_generator
212
+ define_element :table, code_generator: code_generator
213
+ define_element :tbody, code_generator: code_generator
214
+ define_element :td, code_generator: code_generator
215
+ define_element :template, code_generator: code_generator
216
+ define_element :textarea, code_generator: code_generator
217
+ define_element :tfoot, code_generator: code_generator
218
+ define_element :th, code_generator: code_generator
219
+ define_element :thead, code_generator: code_generator
220
+ define_element :time, code_generator: code_generator
221
+ define_element :title, code_generator: code_generator
222
+ define_element :tr, code_generator: code_generator
223
+ define_element :u, code_generator: code_generator
224
+ define_element :ul, code_generator: code_generator
225
+ define_element :var, code_generator: code_generator
226
+ define_element :video, code_generator: code_generator
227
+ end
51
228
 
52
229
  def initialize(view_context)
53
230
  @view_context = view_context
@@ -62,28 +239,22 @@ module ActionView
62
239
  tag_options(attributes.to_h).to_s.strip.html_safe
63
240
  end
64
241
 
65
- def p(*arguments, **options, &block)
66
- tag_string(:p, *arguments, **options, &block)
242
+ def tag_string(name, content = nil, options, escape: true, &block)
243
+ content = @view_context.capture(self, &block) if block
244
+
245
+ content_tag_string(name, content, options, escape)
67
246
  end
68
247
 
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
248
+ def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
249
+ "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
77
250
  end
78
251
 
79
252
  def content_tag_string(name, content, options, escape = true)
80
253
  tag_options = tag_options(options, escape) if options
81
254
 
82
- if escape
83
- name = ERB::Util.xml_name_escape(name)
255
+ if escape && content.present?
84
256
  content = ERB::Util.unwrapped_html_escape(content)
85
257
  end
86
-
87
258
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
88
259
  end
89
260
 
@@ -163,8 +334,12 @@ module ActionView
163
334
  true
164
335
  end
165
336
 
166
- def method_missing(called, *args, **options, &block)
167
- tag_string(called, *args, **options, &block)
337
+ def method_missing(called, *args, escape: true, **options, &block)
338
+ name = called.name.dasherize
339
+
340
+ TagHelper.ensure_valid_html5_tag_name(name)
341
+
342
+ tag_string(name, *args, options, escape: escape, &block)
168
343
  end
169
344
  end
170
345
 
@@ -245,6 +420,14 @@ module ActionView
245
420
  # # A void element:
246
421
  # tag.br # => <br>
247
422
  #
423
+ # Note that when using the block form options should be wrapped in
424
+ # parenthesis.
425
+ #
426
+ # <%= tag.a(href: "/about", class: "font-bold") do %>
427
+ # About the author
428
+ # <% end %>
429
+ # # => <a href="/about" class="font-bold">About the author</a>
430
+ #
248
431
  # === Building HTML attributes
249
432
  #
250
433
  # Transforms a Hash into HTML attributes, ready to be interpolated into
@@ -310,7 +493,7 @@ module ActionView
310
493
  if name.nil?
311
494
  tag_builder
312
495
  else
313
- name = ERB::Util.xml_name_escape(name) if escape
496
+ ensure_valid_html5_tag_name(name)
314
497
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
315
498
  end
316
499
  end
@@ -344,6 +527,8 @@ module ActionView
344
527
  # <% end -%>
345
528
  # # => <div class="strong">Hello world!</div>
346
529
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
530
+ ensure_valid_html5_tag_name(name)
531
+
347
532
  if block_given?
348
533
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
349
534
  tag_builder.content_tag_string(name, capture(&block), options, escape)
@@ -400,6 +585,11 @@ module ActionView
400
585
  end
401
586
 
402
587
  private
588
+ def ensure_valid_html5_tag_name(name)
589
+ raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
590
+ end
591
+ module_function :ensure_valid_html5_tag_name
592
+
403
593
  def build_tag_values(*args)
404
594
  tag_values = []
405
595
 
@@ -195,42 +195,6 @@ module ActionView
195
195
  # link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" }
196
196
  # # => <a href="https://rubyonrails.org/" data-turbo-confirm="Are you sure?">Visit Other Site</a>
197
197
  #
198
- # ==== Deprecated: \Rails UJS Attributes
199
- #
200
- # Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
201
- # this library is no longer on by default. This library integrated with the following options:
202
- #
203
- # * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
204
- # create an HTML form and immediately submit the form for processing using
205
- # the HTTP verb specified. Useful for having links perform a POST operation
206
- # in dangerous actions like deleting a record (which search bots can follow
207
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
208
- # Note that if the user has JavaScript disabled, the request will fall back
209
- # to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
210
- # disabled clicking the link will have no effect. If you are relying on the
211
- # POST behavior, you should check for it in your controller's action by using
212
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
213
- # * <tt>remote: true</tt> - This will allow <tt>@rails/ujs</tt>
214
- # to make an Ajax request to the URL in question instead of following
215
- # the link.
216
- #
217
- # <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
218
- #
219
- # * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
220
- # to prompt with the question specified (in this case, the
221
- # resulting text would be <tt>question?</tt>). If the user accepts, the
222
- # link is processed normally, otherwise no action is taken.
223
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the
224
- # name for a disabled version of the link.
225
- #
226
- # ===== \Rails UJS Examples
227
- #
228
- # link_to "Remove Profile", profile_path(@profile), method: :delete
229
- # # => <a href="/profiles/1" rel="nofollow" data-method="delete">Remove Profile</a>
230
- #
231
- # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
232
- # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
233
- #
234
198
  def link_to(name = nil, options = nil, html_options = nil, &block)
235
199
  html_options, options, name = options, name, block if block_given?
236
200
  options ||= {}
@@ -256,8 +220,9 @@ module ActionView
256
220
  # +:form_class+ option within +html_options+. It defaults to
257
221
  # <tt>"button_to"</tt> to allow styling of the form and its children.
258
222
  #
259
- # The form submits a POST request by default. You can specify a different
260
- # HTTP verb via the +:method+ option within +html_options+.
223
+ # The form submits a POST request by default if the object is not persisted;
224
+ # conversely, if the object is persisted, it will submit a PATCH request.
225
+ # To specify a different HTTP verb use the +:method+ option within +html_options+.
261
226
  #
262
227
  # If the HTML button generated from +button_to+ does not work with your layout, you can
263
228
  # consider using the +link_to+ method with the +data-turbo-method+
@@ -328,32 +293,6 @@ module ActionView
328
293
  # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
329
294
  # # </form>"
330
295
  #
331
- # ==== Deprecated: \Rails UJS Attributes
332
- #
333
- # Prior to \Rails 7, \Rails shipped with a JavaScript library called <tt>@rails/ujs</tt> on by default. Following \Rails 7,
334
- # this library is no longer on by default. This library integrated with the following options:
335
- #
336
- # * <tt>:remote</tt> - If set to true, will allow <tt>@rails/ujs</tt> to control the
337
- # submit behavior. By default this behavior is an Ajax submit.
338
- #
339
- # <tt>@rails/ujs</tt> also integrated with the following +:data+ options:
340
- #
341
- # * <tt>confirm: "question?"</tt> - This will allow <tt>@rails/ujs</tt>
342
- # to prompt with the question specified (in this case, the
343
- # resulting text would be <tt>question?</tt>). If the user accepts, the
344
- # button is processed normally, otherwise no action is taken.
345
- # * <tt>:disable_with</tt> - Value of this parameter will be
346
- # used as the value for a disabled version of the submit
347
- # button when the form is submitted.
348
- #
349
- # ===== \Rails UJS Examples
350
- #
351
- # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
352
- # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
353
- # # <button type="submit">Create</button>
354
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
355
- # # </form>"
356
- #
357
296
  def button_to(name = nil, options = nil, html_options = nil, &block)
358
297
  html_options, options = options, name if block_given?
359
298
  html_options ||= {}
@@ -636,19 +575,6 @@ module ActionView
636
575
  url_string == request_uri
637
576
  end
638
577
 
639
- if RUBY_VERSION.start_with?("2.7")
640
- using Module.new {
641
- refine UrlHelper do
642
- alias :_current_page? :current_page?
643
- end
644
- }
645
-
646
- def current_page?(*args) # :nodoc:
647
- options = args.pop
648
- options.is_a?(Hash) ? _current_page?(*args, **options) : _current_page?(*args, options)
649
- end
650
- end
651
-
652
578
  # Creates an SMS anchor link tag to the specified +phone_number+. When the
653
579
  # link is clicked, the default SMS messaging app is opened ready to send a
654
580
  # message to the linked phone number. If the +body+ option is specified,
@@ -784,7 +710,7 @@ module ActionView
784
710
  end
785
711
 
786
712
  def add_method_to_attributes!(html_options, method)
787
- if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
713
+ if method_not_get_method?(method) && !html_options["rel"]&.include?("nofollow")
788
714
  if html_options["rel"].blank?
789
715
  html_options["rel"] = "nofollow"
790
716
  else
@@ -209,11 +209,9 @@ module ActionView
209
209
 
210
210
  included do
211
211
  class_attribute :_layout, instance_accessor: false
212
- class_attribute :_layout_conditions, instance_accessor: false, default: {}
212
+ class_attribute :_layout_conditions, instance_accessor: false, instance_reader: true, default: {}
213
213
 
214
214
  _write_layout_method
215
-
216
- delegate :_layout_conditions, to: :class
217
215
  end
218
216
 
219
217
  module ClassMethods
@@ -430,7 +428,7 @@ module ActionView
430
428
  end
431
429
 
432
430
  def _include_layout?(options)
433
- (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
431
+ !options.keys.intersect?([:body, :plain, :html, :inline, :partial]) || options.key?(:layout)
434
432
  end
435
433
  end
436
434
  end