actionview 8.0.2.1 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -59
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +4 -2
  5. data/lib/action_view/buffers.rb +1 -1
  6. data/lib/action_view/dependency_tracker/erb_tracker.rb +1 -1
  7. data/lib/action_view/dependency_tracker.rb +6 -1
  8. data/lib/action_view/gem_version.rb +3 -3
  9. data/lib/action_view/helpers/asset_tag_helper.rb +20 -4
  10. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  11. data/lib/action_view/helpers/capture_helper.rb +2 -2
  12. data/lib/action_view/helpers/controller_helper.rb +6 -2
  13. data/lib/action_view/helpers/date_helper.rb +25 -1
  14. data/lib/action_view/helpers/form_helper.rb +2 -2
  15. data/lib/action_view/helpers/form_options_helper.rb +16 -14
  16. data/lib/action_view/helpers/form_tag_helper.rb +17 -9
  17. data/lib/action_view/helpers/javascript_helper.rb +5 -1
  18. data/lib/action_view/helpers/number_helper.rb +14 -0
  19. data/lib/action_view/helpers/tag_helper.rb +31 -34
  20. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  21. data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
  22. data/lib/action_view/helpers/tags/select.rb +6 -1
  23. data/lib/action_view/helpers/tags/select_renderer.rb +2 -2
  24. data/lib/action_view/helpers/text_helper.rb +10 -3
  25. data/lib/action_view/helpers/translation_helper.rb +6 -1
  26. data/lib/action_view/helpers/url_helper.rb +37 -9
  27. data/lib/action_view/locale/en.yml +3 -0
  28. data/lib/action_view/railtie.rb +11 -0
  29. data/lib/action_view/record_identifier.rb +21 -0
  30. data/lib/action_view/renderer/partial_renderer.rb +16 -0
  31. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -1
  32. data/lib/action_view/template/error.rb +7 -3
  33. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  34. data/lib/action_view/template/handlers/erb.rb +36 -11
  35. data/lib/action_view/template/raw_file.rb +4 -0
  36. data/lib/action_view/test_case.rb +50 -53
  37. data/lib/action_view.rb +4 -0
  38. metadata +10 -10
@@ -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 tag_string(name, content = nil, options, escape: true, &block)
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
- def boolean_tag_option(key)
291
- %(#{key}="#{key}")
292
- end
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
- def tag_option(key, value, escape)
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('"', "&quot;") if value.include?('"')
307
283
 
308
- %(#{key}="#{value}")
309
- end
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('"', "&quot;") 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["value"] || value)
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
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class HiddenField < TextField # :nodoc:
7
7
  def render
8
- @options[:autocomplete] = "off"
8
+ @options.reverse_merge!(autocomplete: "off")
9
9
  super
10
10
  end
11
11
  end
@@ -37,7 +37,12 @@ module ActionView
37
37
  # [nil, []]
38
38
  # { nil => [] }
39
39
  def grouped_choices?
40
- !@choices.blank? && @choices.first.respond_to?(:second) && Array === @choices.first.second
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.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
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.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
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 = [first_part, separator, phrase, separator, second_part].join.strip
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
- keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
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
- inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value],
345
- autocomplete: "off")
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
- # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
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
- return false unless request.get? || request.head?
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
- tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token, autocomplete: "off")
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
- tag("input", type: "hidden", name: "_method", value: method.to_s, autocomplete: "off")
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
@@ -43,6 +43,9 @@
43
43
  hour: "Hour"
44
44
  minute: "Minute"
45
45
  second: "Seconds"
46
+ relative:
47
+ future: "in %{time}"
48
+ past: "%{time} ago"
46
49
 
47
50
  helpers:
48
51
  select:
@@ -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 layout_name && template.supports_streaming?
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
- <<~MESSAGE
264
- Encountered a syntax error while rendering template: check #{@offending_code_string}
265
- MESSAGE
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 Eruby that whether template will be compiled with `frozen_string_literal: true`
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, backtrace_location, source)
44
- # Tokenize the source line
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
- return nil if source_lines.size < backtrace_location.lineno
47
- tokens = ::ERB::Util.tokenize(source_lines[backtrace_location.lineno - 1])
48
- new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
49
- lineno_delta = spot[:first_lineno] - backtrace_location.lineno
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 error_column - compiled.pos + offset
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 |(name, str)|
156
- result = [name, str, source_offset] if name == :CODE || name == :TEXT
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
@@ -20,6 +20,10 @@ module ActionView # :nodoc:
20
20
  def render(*args)
21
21
  ::File.read(@filename)
22
22
  end
23
+
24
+ def supports_streaming?
25
+ false
26
+ end
23
27
  end
24
28
  end
25
29
  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, :rendered
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!