actionview 7.2.3.1 → 8.1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +198 -109
- data/lib/action_view/base.rb +5 -2
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +3 -20
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +7 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +21 -2
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -2
- data/lib/action_view/helpers/cache_helper.rb +8 -0
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +6 -2
- data/lib/action_view/helpers/date_helper.rb +20 -3
- data/lib/action_view/helpers/form_helper.rb +77 -77
- data/lib/action_view/helpers/form_options_helper.rb +42 -36
- data/lib/action_view/helpers/form_tag_helper.rb +35 -25
- data/lib/action_view/helpers/javascript_helper.rb +5 -1
- data/lib/action_view/helpers/number_helper.rb +14 -0
- data/lib/action_view/helpers/rendering_helper.rb +160 -50
- data/lib/action_view/helpers/sanitize_helper.rb +12 -1
- data/lib/action_view/helpers/tag_helper.rb +70 -77
- data/lib/action_view/helpers/tags/base.rb +11 -9
- data/lib/action_view/helpers/tags/check_box.rb +9 -3
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
- data/lib/action_view/helpers/tags/collection_helpers.rb +11 -1
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +7 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +3 -10
- data/lib/action_view/helpers/tags/radio_button.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +6 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +1 -1
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +23 -7
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +39 -13
- data/lib/action_view/layouts.rb +1 -1
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/log_subscriber.rb +1 -4
- data/lib/action_view/railtie.rb +12 -1
- data/lib/action_view/record_identifier.rb +22 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +13 -1
- data/lib/action_view/render_parser/ripper_render_parser.rb +10 -1
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +5 -1
- data/lib/action_view/renderer/partial_renderer.rb +16 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
- data/lib/action_view/rendering.rb +2 -3
- data/lib/action_view/structured_event_subscriber.rb +97 -0
- data/lib/action_view/template/error.rb +7 -3
- data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +37 -12
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/template/resolver.rb +0 -1
- data/lib/action_view/template.rb +12 -6
- data/lib/action_view/test_case.rb +68 -55
- data/lib/action_view.rb +16 -0
- metadata +20 -26
|
@@ -6,8 +6,11 @@ module ActionView
|
|
|
6
6
|
class FileField < TextField # :nodoc:
|
|
7
7
|
def render
|
|
8
8
|
include_hidden = @options.delete(:include_hidden)
|
|
9
|
+
if @options[:accept].is_a?(Array)
|
|
10
|
+
@options[:accept] = @options[:accept].join(",")
|
|
11
|
+
end
|
|
9
12
|
options = @options.stringify_keys
|
|
10
|
-
|
|
13
|
+
add_default_name_and_field(options)
|
|
11
14
|
|
|
12
15
|
if options["multiple"] && include_hidden
|
|
13
16
|
hidden_field_for_multiple_file(options) + super
|
|
@@ -18,7 +21,9 @@ module ActionView
|
|
|
18
21
|
|
|
19
22
|
private
|
|
20
23
|
def hidden_field_for_multiple_file(options)
|
|
21
|
-
|
|
24
|
+
tag_options = { "name" => options["name"], "type" => "hidden", "value" => "" }
|
|
25
|
+
tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
26
|
+
tag("input", tag_options)
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -48,18 +48,11 @@ module ActionView
|
|
|
48
48
|
def render(&block)
|
|
49
49
|
options = @options.stringify_keys
|
|
50
50
|
tag_value = options.delete("value")
|
|
51
|
-
name_and_id = options.dup
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
name_and_id["id"] = name_and_id["for"]
|
|
55
|
-
else
|
|
56
|
-
name_and_id.delete("id")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
add_default_name_and_id_for_value(tag_value, name_and_id)
|
|
52
|
+
add_default_name_and_field_for_value(tag_value, options, "for")
|
|
60
53
|
options.delete("index")
|
|
54
|
+
options.delete("name")
|
|
61
55
|
options.delete("namespace")
|
|
62
|
-
options["for"] = name_and_id["id"] unless options.key?("for")
|
|
63
56
|
|
|
64
57
|
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
|
|
65
58
|
|
|
@@ -71,7 +64,7 @@ module ActionView
|
|
|
71
64
|
render_component(builder)
|
|
72
65
|
end
|
|
73
66
|
|
|
74
|
-
label_tag(
|
|
67
|
+
label_tag(options["for"], content, options)
|
|
75
68
|
end
|
|
76
69
|
|
|
77
70
|
private
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
options["type"] = "radio"
|
|
19
19
|
options["value"] = @tag_value
|
|
20
20
|
options["checked"] = "checked" if input_checked?(options)
|
|
21
|
-
|
|
21
|
+
add_default_name_and_field_for_value(@tag_value, options)
|
|
22
22
|
tag("input", options)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -37,7 +37,12 @@ module ActionView
|
|
|
37
37
|
# [nil, []]
|
|
38
38
|
# { nil => [] }
|
|
39
39
|
def grouped_choices?
|
|
40
|
-
|
|
40
|
+
return false if @choices.blank?
|
|
41
|
+
|
|
42
|
+
first_choice = @choices.first
|
|
43
|
+
return false unless first_choice.is_a?(Enumerable)
|
|
44
|
+
|
|
45
|
+
first_choice.second.is_a?(Array)
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
@@ -11,7 +11,7 @@ module ActionView
|
|
|
11
11
|
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
add_default_name_and_field(html_options)
|
|
15
15
|
|
|
16
16
|
if placeholder_required?(html_options)
|
|
17
17
|
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
|
@@ -22,7 +22,9 @@ module ActionView
|
|
|
22
22
|
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
|
23
23
|
|
|
24
24
|
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
|
25
|
-
|
|
25
|
+
tag_options = { disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "" }
|
|
26
|
+
tag_options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
27
|
+
tag("input", tag_options) + select
|
|
26
28
|
else
|
|
27
29
|
select
|
|
28
30
|
end
|
|
@@ -37,7 +39,7 @@ module ActionView
|
|
|
37
39
|
if options[:include_blank]
|
|
38
40
|
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
|
39
41
|
label = (" " unless content)
|
|
40
|
-
option_tags = tag_builder.
|
|
42
|
+
option_tags = tag_builder.option(content, value: "", label: label) + "\n" + option_tags
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
if value.blank? && options[:prompt]
|
|
@@ -45,7 +47,7 @@ module ActionView
|
|
|
45
47
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
|
46
48
|
prompt_opts[:selected] = true if options[:selected] == ""
|
|
47
49
|
end
|
|
48
|
-
option_tags = tag_builder.
|
|
50
|
+
option_tags = tag_builder.option(prompt_text(options[:prompt]), **tag_options) + "\n" + option_tags
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
option_tags
|
|
@@ -10,7 +10,7 @@ module ActionView
|
|
|
10
10
|
|
|
11
11
|
def render
|
|
12
12
|
options = @options.stringify_keys
|
|
13
|
-
|
|
13
|
+
add_default_name_and_field(options)
|
|
14
14
|
|
|
15
15
|
if size = options.delete("size")
|
|
16
16
|
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
|
13
13
|
options["size"] = options["maxlength"] unless options.key?("size")
|
|
14
14
|
options["type"] ||= field_type
|
|
15
15
|
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
|
|
16
|
-
|
|
16
|
+
add_default_name_and_field(options)
|
|
17
17
|
tag("input", options)
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -143,8 +143,15 @@ module ActionView
|
|
|
143
143
|
#
|
|
144
144
|
# [+:highlighter+]
|
|
145
145
|
# The highlighter string. Uses <tt>\1</tt> as the placeholder for a
|
|
146
|
-
# phrase
|
|
147
|
-
#
|
|
146
|
+
# phrase. Defaults to <tt>"<mark>\1</mark>"</tt>. The numbered placeholders
|
|
147
|
+
# <tt>\0</tt> to <tt>\9</tt> are expanded; other +String#sub+ replacement escapes
|
|
148
|
+
# are not. The phrase is HTML escaped, quotes included. This option
|
|
149
|
+
# is ignored if a block is specified.
|
|
150
|
+
#
|
|
151
|
+
# WARNING: Put a placeholder only in element content or inside a
|
|
152
|
+
# quoted attribute value. HTML escaping does not make the phrase safe
|
|
153
|
+
# in an unquoted attribute value, in a URL, JavaScript or CSS
|
|
154
|
+
# context, or in an attribute whose value is HTML, such as +srcdoc+.
|
|
148
155
|
#
|
|
149
156
|
# [+:sanitize+]
|
|
150
157
|
# Whether to sanitize +text+ before highlighting. Defaults to true.
|
|
@@ -163,8 +170,8 @@ module ActionView
|
|
|
163
170
|
# highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
|
|
164
171
|
# # => "You searched <em>for</em>: <em>rails</em>"
|
|
165
172
|
#
|
|
166
|
-
# highlight('You searched for: rails', 'rails', highlighter: '<
|
|
167
|
-
# # => "You searched for: <
|
|
173
|
+
# highlight('You searched for: rails', 'rails', highlighter: '<mark title="\1">\1</mark>')
|
|
174
|
+
# # => "You searched for: <mark title=\"rails\">rails</mark>"
|
|
168
175
|
#
|
|
169
176
|
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match)) }
|
|
170
177
|
# # => "You searched for: <a href=\"search?q=rails\">rails</a>"
|
|
@@ -179,14 +186,23 @@ module ActionView
|
|
|
179
186
|
else
|
|
180
187
|
patterns = Array(phrases).map { |phrase| Regexp === phrase ? phrase : Regexp.escape(phrase) }
|
|
181
188
|
pattern = /(#{patterns.join("|")})/i
|
|
189
|
+
# The text after a tag begins with the ">" that closes it. Leave it
|
|
190
|
+
# in the segment so phrases can see it as context, but keep it out
|
|
191
|
+
# of the match so the highlighter can't take the place of the ">".
|
|
192
|
+
pattern_after_tag = /(?!\A)#{pattern}/
|
|
182
193
|
highlighter = options.fetch(:highlighter, '<mark>\1</mark>') unless block
|
|
183
194
|
|
|
184
|
-
text.scan(/<[^>]*|[^<]+/).
|
|
195
|
+
text.scan(/<[^>]*|[^<]+/).each_with_index do |segment, index|
|
|
185
196
|
if !segment.start_with?("<")
|
|
197
|
+
segment_pattern = index > 0 ? pattern_after_tag : pattern
|
|
198
|
+
|
|
186
199
|
if block
|
|
187
|
-
segment.gsub!(
|
|
200
|
+
segment.gsub!(segment_pattern, &block)
|
|
188
201
|
else
|
|
189
|
-
segment.gsub!(
|
|
202
|
+
segment.gsub!(segment_pattern) do
|
|
203
|
+
match = Regexp.last_match
|
|
204
|
+
highlighter.gsub(/\\(\d)/) { ERB::Util.html_escape_once(match[$1.to_i]) }
|
|
205
|
+
end
|
|
190
206
|
end
|
|
191
207
|
end
|
|
192
208
|
end.join
|
|
@@ -140,7 +140,12 @@ module ActionView
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def missing_translation(key, options)
|
|
143
|
-
|
|
143
|
+
locale = options[:locale] || I18n.locale
|
|
144
|
+
|
|
145
|
+
i18n_exception = I18n::MissingTranslation.new(locale, key, options)
|
|
146
|
+
I18n.exception_handler.call(i18n_exception, locale, key, options)
|
|
147
|
+
|
|
148
|
+
keys = I18n.normalize_keys(locale, key, options[:scope])
|
|
144
149
|
|
|
145
150
|
title = +"translation missing: #{keys.join(".")}"
|
|
146
151
|
|
|
@@ -341,8 +341,9 @@ module ActionView
|
|
|
341
341
|
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
|
|
342
342
|
if params
|
|
343
343
|
to_form_params(params).each do |param|
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
options = { type: "hidden", name: param[:name], value: param[:value] }
|
|
345
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
346
|
+
inner_tags.safe_concat tag(:input, **options)
|
|
346
347
|
end
|
|
347
348
|
end
|
|
348
349
|
html = content_tag("form", inner_tags, form_options)
|
|
@@ -500,8 +501,6 @@ module ActionView
|
|
|
500
501
|
content_tag("a", name || email_address, html_options, &block)
|
|
501
502
|
end
|
|
502
503
|
|
|
503
|
-
RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
|
|
504
|
-
|
|
505
504
|
# True if the current request URI was generated by the given +options+.
|
|
506
505
|
#
|
|
507
506
|
# ==== Examples
|
|
@@ -540,32 +539,55 @@ module ActionView
|
|
|
540
539
|
# current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
|
|
541
540
|
# # => true
|
|
542
541
|
#
|
|
543
|
-
#
|
|
542
|
+
# Different actions may share the same URL path but have a different HTTP method. Let's say we
|
|
543
|
+
# sent a POST to <tt>http://www.example.com/products</tt> and rendered a validation error.
|
|
544
544
|
#
|
|
545
545
|
# current_page?(controller: 'product', action: 'index')
|
|
546
546
|
# # => false
|
|
547
547
|
#
|
|
548
|
+
# current_page?(controller: 'product', action: 'create')
|
|
549
|
+
# # => false
|
|
550
|
+
#
|
|
551
|
+
# current_page?(controller: 'product', action: 'create', method: :post)
|
|
552
|
+
# # => true
|
|
553
|
+
#
|
|
554
|
+
# current_page?(controller: 'product', action: 'index', method: [:get, :post])
|
|
555
|
+
# # => true
|
|
556
|
+
#
|
|
548
557
|
# We can also pass in the symbol arguments instead of strings.
|
|
549
558
|
#
|
|
550
|
-
def current_page?(options = nil, check_parameters: false, **options_as_kwargs)
|
|
559
|
+
def current_page?(options = nil, check_parameters: false, method: :get, **options_as_kwargs)
|
|
551
560
|
unless request
|
|
552
561
|
raise "You cannot use helpers that need to determine the current " \
|
|
553
562
|
"page unless your view context provides a Request object " \
|
|
554
563
|
"in a #request method"
|
|
555
564
|
end
|
|
556
565
|
|
|
557
|
-
|
|
566
|
+
if options.is_a?(Hash)
|
|
567
|
+
check_parameters = options.delete(:check_parameters) { check_parameters }
|
|
568
|
+
method = options.delete(:method) { method }
|
|
569
|
+
else
|
|
570
|
+
options ||= options_as_kwargs
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
method_matches = case method
|
|
574
|
+
when :get
|
|
575
|
+
request.get? || request.head?
|
|
576
|
+
when Array
|
|
577
|
+
method.include?(request.method_symbol) || (method.include?(:get) && request.head?)
|
|
578
|
+
else
|
|
579
|
+
method == request.method_symbol
|
|
580
|
+
end
|
|
581
|
+
return false unless method_matches
|
|
558
582
|
|
|
559
|
-
|
|
560
|
-
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
|
561
|
-
url_string = RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
|
583
|
+
url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
|
562
584
|
|
|
563
585
|
# We ignore any extra parameters in the request_uri if the
|
|
564
586
|
# submitted URL doesn't have any either. This lets the function
|
|
565
587
|
# work with things like ?order=asc
|
|
566
588
|
# the behavior can be disabled with check_parameters: true
|
|
567
589
|
request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
|
|
568
|
-
request_uri = RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
|
590
|
+
request_uri = URI::RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
|
569
591
|
|
|
570
592
|
if %r{^\w+://}.match?(url_string)
|
|
571
593
|
request_uri = +"#{request.protocol}#{request.host_with_port}#{request_uri}"
|
|
@@ -753,14 +775,18 @@ module ActionView
|
|
|
753
775
|
else
|
|
754
776
|
token
|
|
755
777
|
end
|
|
756
|
-
|
|
778
|
+
options = { type: "hidden", name: request_forgery_protection_token.to_s, value: token }
|
|
779
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
780
|
+
tag(:input, **options)
|
|
757
781
|
else
|
|
758
782
|
""
|
|
759
783
|
end
|
|
760
784
|
end
|
|
761
785
|
|
|
762
786
|
def method_tag(method)
|
|
763
|
-
|
|
787
|
+
options = { type: "hidden", name: "_method", value: method.to_s }
|
|
788
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
789
|
+
tag("input", **options)
|
|
764
790
|
end
|
|
765
791
|
|
|
766
792
|
# Returns an array of hashes each containing :name and :value keys
|
data/lib/action_view/layouts.rb
CHANGED
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
require "active_support/log_subscriber"
|
|
4
4
|
|
|
5
5
|
module ActionView
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
# Provides functionality so that \Rails can output logs from Action View.
|
|
9
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
10
7
|
VIEWS_PATTERN = /^app\/views\//
|
|
11
8
|
|
|
12
9
|
def initialize
|
data/lib/action_view/railtie.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "action_view"
|
|
4
3
|
require "rails"
|
|
4
|
+
require "action_view"
|
|
5
5
|
|
|
6
6
|
module ActionView
|
|
7
7
|
# = Action View Railtie
|
|
@@ -71,9 +71,16 @@ module ActionView
|
|
|
71
71
|
ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
config.after_initialize do |app|
|
|
75
|
+
ActionView::Helpers::AssetTagHelper.auto_include_nonce_for_scripts = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["script-src", "script-src-elem", "script-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
76
|
+
ActionView::Helpers::AssetTagHelper.auto_include_nonce_for_styles = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["style-src", "style-src-elem", "style-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
77
|
+
ActionView::Helpers::JavaScriptHelper.auto_include_nonce = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["script-src", "script-src-elem", "script-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
config.after_initialize do |app|
|
|
75
81
|
ActiveSupport.on_load(:action_view) do
|
|
76
82
|
app.config.action_view.each do |k, v|
|
|
83
|
+
next if k == :render_tracker
|
|
77
84
|
send "#{k}=", v
|
|
78
85
|
end
|
|
79
86
|
end
|
|
@@ -95,6 +102,10 @@ module ActionView
|
|
|
95
102
|
end
|
|
96
103
|
end
|
|
97
104
|
|
|
105
|
+
initializer "action_view.set_render_tracker" do |app|
|
|
106
|
+
ActionView.render_tracker = app.config.action_view.render_tracker
|
|
107
|
+
end
|
|
108
|
+
|
|
98
109
|
initializer "action_view.setup_action_pack" do |app|
|
|
99
110
|
ActiveSupport.on_load(:action_controller) do
|
|
100
111
|
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
|
|
@@ -11,7 +11,7 @@ module ActionView
|
|
|
11
11
|
#
|
|
12
12
|
# Consider for example the following code that form of post:
|
|
13
13
|
#
|
|
14
|
-
# <%=
|
|
14
|
+
# <%= form_with(model: post) do |f| %>
|
|
15
15
|
# <%= f.text_field :body %>
|
|
16
16
|
# <% end %>
|
|
17
17
|
#
|
|
@@ -101,6 +101,27 @@ module ActionView
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# The DOM target convention is to concatenate any number of parameters into a string.
|
|
105
|
+
# Records are passed through dom_id, while string and symbols are retained.
|
|
106
|
+
#
|
|
107
|
+
# dom_target(Post.find(45)) # => "post_45"
|
|
108
|
+
# dom_target(Post.find(45), :edit) # => "post_45_edit"
|
|
109
|
+
# dom_target(Post.find(45), :edit, :special) # => "post_45_edit_special"
|
|
110
|
+
# dom_target(Post.find(45), Comment.find(1)) # => "post_45_comment_1"
|
|
111
|
+
def dom_target(*objects)
|
|
112
|
+
objects.map! do |object|
|
|
113
|
+
case object
|
|
114
|
+
when Symbol, String
|
|
115
|
+
object
|
|
116
|
+
when Class
|
|
117
|
+
dom_class(object)
|
|
118
|
+
else
|
|
119
|
+
dom_id(object)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
objects.join(JOIN)
|
|
123
|
+
end
|
|
124
|
+
|
|
104
125
|
private
|
|
105
126
|
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
|
|
106
127
|
# This can be overwritten to customize the default generated string representation if desired.
|
|
@@ -97,9 +97,21 @@ module ActionView
|
|
|
97
97
|
def render_call_template(node)
|
|
98
98
|
object_template = false
|
|
99
99
|
template =
|
|
100
|
-
|
|
100
|
+
case node.type
|
|
101
|
+
when :string_node
|
|
101
102
|
path = node.unescaped
|
|
102
103
|
path.include?("/") ? path : "#{directory}/#{path}"
|
|
104
|
+
when :interpolated_string_node
|
|
105
|
+
node.parts.map do |node|
|
|
106
|
+
case node.type
|
|
107
|
+
when :string_node
|
|
108
|
+
node.unescaped
|
|
109
|
+
when :embedded_statements_node
|
|
110
|
+
"*"
|
|
111
|
+
else
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
end.join("")
|
|
103
115
|
else
|
|
104
116
|
dependency =
|
|
105
117
|
case node.type
|
|
@@ -66,7 +66,16 @@ module ActionView
|
|
|
66
66
|
|
|
67
67
|
def to_string
|
|
68
68
|
raise unless string?
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
# s(:string_literal, s(:string_content, map))
|
|
71
|
+
self[0].map do |node|
|
|
72
|
+
case node.type
|
|
73
|
+
when :@tstring_content
|
|
74
|
+
node[0]
|
|
75
|
+
when :string_embexpr
|
|
76
|
+
"*"
|
|
77
|
+
end
|
|
78
|
+
end.join("")
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
def hash?
|
|
@@ -111,7 +111,11 @@ module ActionView
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
unless entries_to_write.empty?
|
|
114
|
-
|
|
114
|
+
if @options[:cached].is_a?(Hash) && @options[:cached].key?(:expires_in)
|
|
115
|
+
collection_cache.write_multi(entries_to_write, expires_in: @options[:cached][:expires_in])
|
|
116
|
+
else
|
|
117
|
+
collection_cache.write_multi(entries_to_write)
|
|
118
|
+
end
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
keyed_partials
|
|
@@ -48,6 +48,22 @@ module ActionView
|
|
|
48
48
|
#
|
|
49
49
|
# <%= render partial: "account", locals: { user: @buyer } %>
|
|
50
50
|
#
|
|
51
|
+
# == \Rendering variants of a partial
|
|
52
|
+
#
|
|
53
|
+
# The <tt>:variants</tt> option can be used to render a different template variant of a partial. For instance:
|
|
54
|
+
#
|
|
55
|
+
# <%= render partial: "account", variants: :mobile %>
|
|
56
|
+
#
|
|
57
|
+
# This will render <tt>_account.html+mobile.erb</tt>. This option also accepts multiple variants
|
|
58
|
+
# like so:
|
|
59
|
+
#
|
|
60
|
+
# <%= render partial: "account", variants: [:desktop, :mobile] %>
|
|
61
|
+
#
|
|
62
|
+
# This will look for the following templates and render the first one that exists:
|
|
63
|
+
# * <tt>_account.html+desktop.erb</tt>
|
|
64
|
+
# * <tt>_account.html+mobile.erb</tt>
|
|
65
|
+
# * <tt>_account.html.erb</tt>
|
|
66
|
+
#
|
|
51
67
|
# == \Rendering a collection of partials
|
|
52
68
|
#
|
|
53
69
|
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "fiber"
|
|
4
3
|
|
|
5
4
|
module ActionView
|
|
6
5
|
# == TODO
|
|
@@ -26,6 +25,13 @@ module ActionView
|
|
|
26
25
|
self
|
|
27
26
|
end
|
|
28
27
|
|
|
28
|
+
# Returns the complete body as a string.
|
|
29
|
+
def body
|
|
30
|
+
buffer = String.new
|
|
31
|
+
each { |part| buffer << part }
|
|
32
|
+
buffer
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
private
|
|
30
36
|
# This is the same logging logic as in ShowExceptions middleware.
|
|
31
37
|
def log_error(exception)
|
|
@@ -43,7 +49,7 @@ module ActionView
|
|
|
43
49
|
# object that responds to each. This object is initialized with a block
|
|
44
50
|
# that knows how to render the template.
|
|
45
51
|
def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
|
|
46
|
-
return [super.body] unless
|
|
52
|
+
return [super.body] unless template.supports_streaming?
|
|
47
53
|
|
|
48
54
|
locals ||= {}
|
|
49
55
|
layout = find_layout(layout_name, locals.keys, [formats.first])
|
|
@@ -118,6 +118,7 @@ module ActionView
|
|
|
118
118
|
|
|
119
119
|
def render_to_body(options = {})
|
|
120
120
|
_process_options(options)
|
|
121
|
+
_process_render_template_options(options)
|
|
121
122
|
_render_template(options)
|
|
122
123
|
end
|
|
123
124
|
|
|
@@ -173,8 +174,7 @@ module ActionView
|
|
|
173
174
|
end
|
|
174
175
|
|
|
175
176
|
# Normalize options.
|
|
176
|
-
def
|
|
177
|
-
options = super(options)
|
|
177
|
+
def _process_render_template_options(options)
|
|
178
178
|
if options[:partial] == true
|
|
179
179
|
options[:partial] = action_name
|
|
180
180
|
end
|
|
@@ -184,7 +184,6 @@ module ActionView
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
options[:template] ||= (options[:action] || action_name).to_s
|
|
187
|
-
options
|
|
188
187
|
end
|
|
189
188
|
end
|
|
190
189
|
end
|