actionview 8.0.2 → 8.1.0.beta1
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 +42 -57
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +4 -2
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +6 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +20 -4
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
- 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 +25 -1
- data/lib/action_view/helpers/form_helper.rb +2 -2
- data/lib/action_view/helpers/form_options_helper.rb +16 -14
- data/lib/action_view/helpers/form_tag_helper.rb +17 -9
- 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/tag_helper.rb +31 -34
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +6 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +2 -2
- data/lib/action_view/helpers/text_helper.rb +10 -3
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +37 -9
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/railtie.rb +11 -0
- data/lib/action_view/record_identifier.rb +21 -0
- data/lib/action_view/renderer/partial_renderer.rb +16 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -1
- 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 +36 -11
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/test_case.rb +50 -53
- data/lib/action_view.rb +4 -0
- metadata +12 -12
@@ -44,9 +44,6 @@ module ActionView
|
|
44
44
|
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
45
45
|
|
46
46
|
class TagBuilder # :nodoc:
|
47
|
-
include CaptureHelper
|
48
|
-
include OutputSafetyHelper
|
49
|
-
|
50
47
|
def self.define_element(name, code_generator:, method_name: name)
|
51
48
|
return if method_defined?(name)
|
52
49
|
|
@@ -226,17 +223,7 @@ module ActionView
|
|
226
223
|
tag_options(attributes.to_h).to_s.strip.html_safe
|
227
224
|
end
|
228
225
|
|
229
|
-
def
|
230
|
-
content = @view_context.capture(self, &block) if block
|
231
|
-
|
232
|
-
content_tag_string(name, content, options, escape)
|
233
|
-
end
|
234
|
-
|
235
|
-
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
236
|
-
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
237
|
-
end
|
238
|
-
|
239
|
-
def content_tag_string(name, content, options, escape = true)
|
226
|
+
def content_tag_string(name, content, options, escape = true) # :nodoc:
|
240
227
|
tag_options = tag_options(options, escape) if options
|
241
228
|
|
242
229
|
if escape && content.present?
|
@@ -245,7 +232,7 @@ module ActionView
|
|
245
232
|
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
246
233
|
end
|
247
234
|
|
248
|
-
def tag_options(options, escape = true)
|
235
|
+
def tag_options(options, escape = true) # :nodoc:
|
249
236
|
return if options.blank?
|
250
237
|
output = +""
|
251
238
|
sep = " "
|
@@ -266,7 +253,7 @@ module ActionView
|
|
266
253
|
tokens = TagHelper.build_tag_values(v)
|
267
254
|
next if tokens.none?
|
268
255
|
|
269
|
-
v = safe_join(tokens, " ")
|
256
|
+
v = @view_context.safe_join(tokens, " ")
|
270
257
|
else
|
271
258
|
v = v.to_s
|
272
259
|
end
|
@@ -287,28 +274,38 @@ module ActionView
|
|
287
274
|
output unless output.empty?
|
288
275
|
end
|
289
276
|
|
290
|
-
|
291
|
-
|
292
|
-
|
277
|
+
private
|
278
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
279
|
+
content = @view_context.capture(self, &block) if block
|
293
280
|
|
294
|
-
|
295
|
-
key = ERB::Util.xml_name_escape(key) if escape
|
296
|
-
|
297
|
-
case value
|
298
|
-
when Array, Hash
|
299
|
-
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
300
|
-
value = escape ? safe_join(value, " ") : value.join(" ")
|
301
|
-
when Regexp
|
302
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
303
|
-
else
|
304
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
281
|
+
content_tag_string(name, content, options, escape)
|
305
282
|
end
|
306
|
-
value = value.gsub('"', """) if value.include?('"')
|
307
283
|
|
308
|
-
|
309
|
-
|
284
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
285
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
286
|
+
end
|
287
|
+
|
288
|
+
def boolean_tag_option(key)
|
289
|
+
%(#{key}="#{key}")
|
290
|
+
end
|
291
|
+
|
292
|
+
def tag_option(key, value, escape)
|
293
|
+
key = ERB::Util.xml_name_escape(key) if escape
|
294
|
+
|
295
|
+
case value
|
296
|
+
when Array, Hash
|
297
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
298
|
+
value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
|
299
|
+
when Regexp
|
300
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
301
|
+
else
|
302
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
303
|
+
end
|
304
|
+
value = value.gsub('"', """) if value.include?('"')
|
305
|
+
|
306
|
+
%(#{key}="#{value}")
|
307
|
+
end
|
310
308
|
|
311
|
-
private
|
312
309
|
def prefix_tag_option(prefix, key, value, escape)
|
313
310
|
key = "#{prefix}-#{key.to_s.dasherize}"
|
314
311
|
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
@@ -6,7 +6,7 @@ module ActionView
|
|
6
6
|
class DatetimeField < TextField # :nodoc:
|
7
7
|
def render
|
8
8
|
options = @options.stringify_keys
|
9
|
-
options["value"] = datetime_value(options
|
9
|
+
options["value"] = datetime_value(options.fetch("value", value))
|
10
10
|
options["min"] = format_datetime(parse_datetime(options["min"]))
|
11
11
|
options["max"] = format_datetime(parse_datetime(options["max"]))
|
12
12
|
@options = options
|
@@ -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
|
@@ -37,7 +37,7 @@ module ActionView
|
|
37
37
|
if options[:include_blank]
|
38
38
|
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
39
39
|
label = (" " unless content)
|
40
|
-
option_tags = tag_builder.
|
40
|
+
option_tags = tag_builder.option(content, value: "", label: label) + "\n" + option_tags
|
41
41
|
end
|
42
42
|
|
43
43
|
if value.blank? && options[:prompt]
|
@@ -45,7 +45,7 @@ module ActionView
|
|
45
45
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
46
46
|
prompt_opts[:selected] = true if options[:selected] == ""
|
47
47
|
end
|
48
|
-
option_tags = tag_builder.
|
48
|
+
option_tags = tag_builder.option(prompt_text(options[:prompt]), **tag_options) + "\n" + option_tags
|
49
49
|
end
|
50
50
|
|
51
51
|
option_tags
|
@@ -260,7 +260,14 @@ module ActionView
|
|
260
260
|
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
|
261
261
|
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
|
262
262
|
|
263
|
-
affix = [
|
263
|
+
affix = [
|
264
|
+
first_part,
|
265
|
+
!first_part.empty? ? separator : "",
|
266
|
+
phrase,
|
267
|
+
!second_part.empty? ? separator : "",
|
268
|
+
second_part
|
269
|
+
].join.strip
|
270
|
+
|
264
271
|
[prefix, affix, postfix].join
|
265
272
|
end
|
266
273
|
|
@@ -271,7 +278,7 @@ module ActionView
|
|
271
278
|
#
|
272
279
|
# The word will be pluralized using rules defined for the locale
|
273
280
|
# (you must define your own inflection rules for languages other than English).
|
274
|
-
# See ActiveSupport::Inflector.pluralize
|
281
|
+
# See ActiveSupport::Inflector.pluralize.
|
275
282
|
#
|
276
283
|
# pluralize(1, 'person')
|
277
284
|
# # => "1 person"
|
@@ -346,7 +353,7 @@ module ActionView
|
|
346
353
|
# ==== Options
|
347
354
|
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
|
348
355
|
# * <tt>:sanitize_options</tt> - Any extra options you want appended to the sanitize.
|
349
|
-
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt
|
356
|
+
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>.
|
350
357
|
#
|
351
358
|
# ==== Examples
|
352
359
|
# my_text = "Here is some basic text...\n...with a line break."
|
@@ -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)
|
@@ -538,24 +539,47 @@ module ActionView
|
|
538
539
|
# current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
|
539
540
|
# # => true
|
540
541
|
#
|
541
|
-
#
|
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.
|
542
544
|
#
|
543
545
|
# current_page?(controller: 'product', action: 'index')
|
544
546
|
# # => false
|
545
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
|
+
#
|
546
557
|
# We can also pass in the symbol arguments instead of strings.
|
547
558
|
#
|
548
|
-
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)
|
549
560
|
unless request
|
550
561
|
raise "You cannot use helpers that need to determine the current " \
|
551
562
|
"page unless your view context provides a Request object " \
|
552
563
|
"in a #request method"
|
553
564
|
end
|
554
565
|
|
555
|
-
|
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
|
556
582
|
|
557
|
-
options ||= options_as_kwargs
|
558
|
-
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
559
583
|
url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
560
584
|
|
561
585
|
# We ignore any extra parameters in the request_uri if the
|
@@ -751,14 +775,18 @@ module ActionView
|
|
751
775
|
else
|
752
776
|
token
|
753
777
|
end
|
754
|
-
|
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)
|
755
781
|
else
|
756
782
|
""
|
757
783
|
end
|
758
784
|
end
|
759
785
|
|
760
786
|
def method_tag(method)
|
761
|
-
|
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)
|
762
790
|
end
|
763
791
|
|
764
792
|
# Returns an array of hashes each containing :name and :value keys
|
data/lib/action_view/railtie.rb
CHANGED
@@ -72,8 +72,19 @@ module ActionView
|
|
72
72
|
end
|
73
73
|
|
74
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
|
+
|
80
|
+
config.after_initialize do |app|
|
81
|
+
config.after_initialize do
|
82
|
+
ActionView.render_tracker = config.action_view.render_tracker
|
83
|
+
end
|
84
|
+
|
75
85
|
ActiveSupport.on_load(:action_view) do
|
76
86
|
app.config.action_view.each do |k, v|
|
87
|
+
next if k == :render_tracker
|
77
88
|
send "#{k}=", v
|
78
89
|
end
|
79
90
|
end
|
@@ -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.
|
@@ -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
|
@@ -25,6 +25,13 @@ module ActionView
|
|
25
25
|
self
|
26
26
|
end
|
27
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
|
+
|
28
35
|
private
|
29
36
|
# This is the same logging logic as in ShowExceptions middleware.
|
30
37
|
def log_error(exception)
|
@@ -42,7 +49,7 @@ module ActionView
|
|
42
49
|
# object that responds to each. This object is initialized with a block
|
43
50
|
# that knows how to render the template.
|
44
51
|
def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
|
45
|
-
return [super.body] unless
|
52
|
+
return [super.body] unless template.supports_streaming?
|
46
53
|
|
47
54
|
locals ||= {}
|
48
55
|
layout = find_layout(layout_name, locals.keys, [formats.first])
|
@@ -260,9 +260,13 @@ module ActionView
|
|
260
260
|
end
|
261
261
|
|
262
262
|
def message
|
263
|
-
|
264
|
-
|
265
|
-
|
263
|
+
if template.is_a?(Template::Inline)
|
264
|
+
<<~MESSAGE
|
265
|
+
Encountered a syntax error while rendering template: check #{@offending_code_string}
|
266
|
+
MESSAGE
|
267
|
+
else
|
268
|
+
"Encountered a syntax error while rendering template located at: #{template.short_identifier}"
|
269
|
+
end
|
266
270
|
end
|
267
271
|
|
268
272
|
def annotated_source_code
|
@@ -18,7 +18,7 @@ module ActionView
|
|
18
18
|
properties[:preamble] ||= ""
|
19
19
|
properties[:postamble] ||= "#{properties[:bufvar]}"
|
20
20
|
|
21
|
-
# Tell
|
21
|
+
# Tell Erubi whether the template will be compiled with `frozen_string_literal: true`
|
22
22
|
properties[:freeze_template_literals] = !Template.frozen_string_literal
|
23
23
|
|
24
24
|
properties[:escapefunc] = ""
|
@@ -40,17 +40,19 @@ module ActionView
|
|
40
40
|
|
41
41
|
# Translate an error location returned by ErrorHighlight to the correct
|
42
42
|
# source location inside the template.
|
43
|
-
def translate_location(spot,
|
44
|
-
|
43
|
+
def translate_location(spot, _backtrace_location, source)
|
44
|
+
compiled = spot[:script_lines]
|
45
|
+
highlight = compiled[spot[:first_lineno] - 1]&.byteslice((spot[:first_column] - 1)...spot[:last_column])
|
46
|
+
return nil if highlight.blank?
|
47
|
+
|
45
48
|
source_lines = source.lines
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
lineno_delta = find_lineno_offset(compiled, source_lines, highlight, spot[:first_lineno])
|
50
|
+
|
51
|
+
tokens = ::ERB::Util.tokenize(source_lines[spot[:first_lineno] - lineno_delta - 1])
|
52
|
+
column_delta = find_offset(spot[:snippet], tokens, spot[:first_column])
|
53
|
+
|
50
54
|
spot[:first_lineno] -= lineno_delta
|
51
55
|
spot[:last_lineno] -= lineno_delta
|
52
|
-
|
53
|
-
column_delta = spot[:first_column] - new_first_column
|
54
56
|
spot[:first_column] -= column_delta
|
55
57
|
spot[:last_column] -= column_delta
|
56
58
|
spot[:script_lines] = source_lines
|
@@ -107,6 +109,28 @@ module ActionView
|
|
107
109
|
raise WrongEncodingError.new(string, string.encoding)
|
108
110
|
end
|
109
111
|
|
112
|
+
# Return the offset between the error lineno and the source lineno.
|
113
|
+
# Searches in reverse from the backtrace lineno so we have a better
|
114
|
+
# chance of finding the correct line
|
115
|
+
#
|
116
|
+
# The compiled template is likely to be longer than the source.
|
117
|
+
# Use the difference between the compiled and source sizes to
|
118
|
+
# determine the earliest line that could contain the highlight.
|
119
|
+
def find_lineno_offset(compiled, source_lines, highlight, error_lineno)
|
120
|
+
first_index = error_lineno - 1 - compiled.size + source_lines.size
|
121
|
+
first_index = 0 if first_index < 0
|
122
|
+
|
123
|
+
last_index = error_lineno - 1
|
124
|
+
last_index = source_lines.size - 1 if last_index >= source_lines.size
|
125
|
+
|
126
|
+
last_index.downto(first_index) do |line_index|
|
127
|
+
next unless source_lines[line_index].include?(highlight)
|
128
|
+
return error_lineno - 1 - line_index
|
129
|
+
end
|
130
|
+
|
131
|
+
raise LocationParsingError, "Couldn't find code snippet"
|
132
|
+
end
|
133
|
+
|
110
134
|
# Find which token in the source template spans the byte range that
|
111
135
|
# contains the error_column, then return the offset compared to the
|
112
136
|
# original source template.
|
@@ -137,7 +161,7 @@ module ActionView
|
|
137
161
|
matched_str = true
|
138
162
|
|
139
163
|
if name == :CODE && compiled.pos <= error_column && compiled.pos + str.bytesize >= error_column
|
140
|
-
return
|
164
|
+
return compiled.pos - offset
|
141
165
|
end
|
142
166
|
|
143
167
|
compiled.pos += str.bytesize
|
@@ -152,8 +176,9 @@ module ActionView
|
|
152
176
|
|
153
177
|
def offset_source_tokens(source_tokens)
|
154
178
|
source_offset = 0
|
155
|
-
with_offset = source_tokens.filter_map do |
|
156
|
-
result = [
|
179
|
+
with_offset = source_tokens.filter_map do |name, str|
|
180
|
+
result = [:CODE, str, source_offset] if name == :CODE || name == :PLAIN
|
181
|
+
result = [:TEXT, str, source_offset] if name == :TEXT
|
157
182
|
source_offset += str.bytesize
|
158
183
|
result
|
159
184
|
end
|
@@ -60,7 +60,56 @@ module ActionView
|
|
60
60
|
include ActiveSupport::Testing::ConstantLookup
|
61
61
|
|
62
62
|
delegate :lookup_context, to: :controller
|
63
|
-
attr_accessor :controller, :request, :output_buffer
|
63
|
+
attr_accessor :controller, :request, :output_buffer
|
64
|
+
|
65
|
+
# Returns the content rendered by the last +render+ call.
|
66
|
+
#
|
67
|
+
# The returned object behaves like a string but also exposes a number of methods
|
68
|
+
# that allows you to parse the content string in formats registered using
|
69
|
+
# <tt>.register_parser</tt>.
|
70
|
+
#
|
71
|
+
# By default includes the following parsers:
|
72
|
+
#
|
73
|
+
# +.html+
|
74
|
+
#
|
75
|
+
# Parse the <tt>rendered</tt> content String into HTML. By default, this means
|
76
|
+
# a <tt>Nokogiri::XML::Node</tt>.
|
77
|
+
#
|
78
|
+
# test "renders HTML" do
|
79
|
+
# article = Article.create!(title: "Hello, world")
|
80
|
+
#
|
81
|
+
# render partial: "articles/article", locals: { article: article }
|
82
|
+
#
|
83
|
+
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
|
87
|
+
# re-register an <tt>:html</tt> parser with a call to
|
88
|
+
# <tt>Capybara.string</tt>:
|
89
|
+
#
|
90
|
+
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
91
|
+
#
|
92
|
+
# test "renders HTML" do
|
93
|
+
# article = Article.create!(title: "Hello, world")
|
94
|
+
#
|
95
|
+
# render partial: article
|
96
|
+
#
|
97
|
+
# rendered.html.assert_css "h1", text: "Hello, world"
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# +.json+
|
101
|
+
#
|
102
|
+
# Parse the <tt>rendered</tt> content String into JSON. By default, this means
|
103
|
+
# a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
|
104
|
+
#
|
105
|
+
# test "renders JSON" do
|
106
|
+
# article = Article.create!(title: "Hello, world")
|
107
|
+
#
|
108
|
+
# render formats: :json, partial: "articles/article", locals: { article: article }
|
109
|
+
#
|
110
|
+
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
111
|
+
# end
|
112
|
+
attr_accessor :rendered
|
64
113
|
|
65
114
|
module ClassMethods
|
66
115
|
def inherited(descendant) # :nodoc:
|
@@ -243,57 +292,6 @@ module ActionView
|
|
243
292
|
@_rendered_views ||= RenderedViewsCollection.new
|
244
293
|
end
|
245
294
|
|
246
|
-
##
|
247
|
-
# :method: rendered
|
248
|
-
#
|
249
|
-
# Returns the content rendered by the last +render+ call.
|
250
|
-
#
|
251
|
-
# The returned object behaves like a string but also exposes a number of methods
|
252
|
-
# that allows you to parse the content string in formats registered using
|
253
|
-
# <tt>.register_parser</tt>.
|
254
|
-
#
|
255
|
-
# By default includes the following parsers:
|
256
|
-
#
|
257
|
-
# +.html+
|
258
|
-
#
|
259
|
-
# Parse the <tt>rendered</tt> content String into HTML. By default, this means
|
260
|
-
# a <tt>Nokogiri::XML::Node</tt>.
|
261
|
-
#
|
262
|
-
# test "renders HTML" do
|
263
|
-
# article = Article.create!(title: "Hello, world")
|
264
|
-
#
|
265
|
-
# render partial: "articles/article", locals: { article: article }
|
266
|
-
#
|
267
|
-
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
268
|
-
# end
|
269
|
-
#
|
270
|
-
# To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
|
271
|
-
# re-register an <tt>:html</tt> parser with a call to
|
272
|
-
# <tt>Capybara.string</tt>:
|
273
|
-
#
|
274
|
-
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
275
|
-
#
|
276
|
-
# test "renders HTML" do
|
277
|
-
# article = Article.create!(title: "Hello, world")
|
278
|
-
#
|
279
|
-
# render partial: article
|
280
|
-
#
|
281
|
-
# rendered.html.assert_css "h1", text: "Hello, world"
|
282
|
-
# end
|
283
|
-
#
|
284
|
-
# +.json+
|
285
|
-
#
|
286
|
-
# Parse the <tt>rendered</tt> content String into JSON. By default, this means
|
287
|
-
# a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
|
288
|
-
#
|
289
|
-
# test "renders JSON" do
|
290
|
-
# article = Article.create!(title: "Hello, world")
|
291
|
-
#
|
292
|
-
# render formats: :json, partial: "articles/article", locals: { article: article }
|
293
|
-
#
|
294
|
-
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
295
|
-
# end
|
296
|
-
|
297
295
|
def _routes
|
298
296
|
@controller._routes if @controller.respond_to?(:_routes)
|
299
297
|
end
|
@@ -301,7 +299,6 @@ module ActionView
|
|
301
299
|
class RenderedViewContent < String # :nodoc:
|
302
300
|
end
|
303
301
|
|
304
|
-
# Need to experiment if this priority is the best one: rendered => output_buffer
|
305
302
|
class RenderedViewsCollection
|
306
303
|
def initialize
|
307
304
|
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
|
data/lib/action_view.rb
CHANGED
@@ -81,6 +81,7 @@ module ActionView
|
|
81
81
|
autoload :MissingTemplate
|
82
82
|
autoload :ActionViewError
|
83
83
|
autoload :EncodingError
|
84
|
+
autoload :StrictLocalsError
|
84
85
|
autoload :TemplateError
|
85
86
|
autoload :SyntaxErrorInTemplate
|
86
87
|
autoload :WrongEncodingError
|
@@ -90,6 +91,9 @@ module ActionView
|
|
90
91
|
autoload :CacheExpiry
|
91
92
|
autoload :TestCase
|
92
93
|
|
94
|
+
singleton_class.attr_accessor :render_tracker
|
95
|
+
self.render_tracker = :regex
|
96
|
+
|
93
97
|
def self.eager_load!
|
94
98
|
super
|
95
99
|
ActionView::Helpers.eager_load!
|