actionview 7.1.5.2 → 8.1.2.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -436
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +29 -11
  5. data/lib/action_view/buffers.rb +1 -1
  6. data/lib/action_view/cache_expiry.rb +9 -3
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
  8. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  9. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +8 -2
  11. data/lib/action_view/digestor.rb +6 -2
  12. data/lib/action_view/gem_version.rb +3 -3
  13. data/lib/action_view/helpers/asset_tag_helper.rb +39 -8
  14. data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
  15. data/lib/action_view/helpers/cache_helper.rb +10 -2
  16. data/lib/action_view/helpers/capture_helper.rb +2 -2
  17. data/lib/action_view/helpers/controller_helper.rb +6 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +28 -4
  20. data/lib/action_view/helpers/form_helper.rb +277 -272
  21. data/lib/action_view/helpers/form_options_helper.rb +39 -35
  22. data/lib/action_view/helpers/form_tag_helper.rb +115 -72
  23. data/lib/action_view/helpers/javascript_helper.rb +5 -1
  24. data/lib/action_view/helpers/number_helper.rb +14 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +5 -6
  26. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  27. data/lib/action_view/helpers/sanitize_helper.rb +6 -0
  28. data/lib/action_view/helpers/tag_helper.rb +225 -48
  29. data/lib/action_view/helpers/tags/base.rb +11 -9
  30. data/lib/action_view/helpers/tags/check_box.rb +9 -3
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/file_field.rb +7 -2
  35. data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
  36. data/lib/action_view/helpers/tags/label.rb +3 -10
  37. data/lib/action_view/helpers/tags/radio_button.rb +1 -1
  38. data/lib/action_view/helpers/tags/select.rb +6 -1
  39. data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
  40. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  41. data/lib/action_view/helpers/tags/text_field.rb +1 -1
  42. data/lib/action_view/helpers/text_helper.rb +11 -4
  43. data/lib/action_view/helpers/translation_helper.rb +6 -1
  44. data/lib/action_view/helpers/url_helper.rb +42 -90
  45. data/lib/action_view/layouts.rb +9 -11
  46. data/lib/action_view/locale/en.yml +3 -0
  47. data/lib/action_view/log_subscriber.rb +9 -8
  48. data/lib/action_view/railtie.rb +12 -2
  49. data/lib/action_view/record_identifier.rb +22 -1
  50. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  51. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
  52. data/lib/action_view/render_parser.rb +21 -169
  53. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  54. data/lib/action_view/renderer/partial_renderer.rb +18 -2
  55. data/lib/action_view/renderer/renderer.rb +32 -38
  56. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
  57. data/lib/action_view/renderer/template_renderer.rb +3 -3
  58. data/lib/action_view/rendering.rb +6 -7
  59. data/lib/action_view/structured_event_subscriber.rb +97 -0
  60. data/lib/action_view/template/error.rb +18 -3
  61. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  62. data/lib/action_view/template/handlers/erb.rb +77 -44
  63. data/lib/action_view/template/raw_file.rb +4 -0
  64. data/lib/action_view/template/renderable.rb +7 -1
  65. data/lib/action_view/template/resolver.rb +0 -3
  66. data/lib/action_view/template.rb +39 -12
  67. data/lib/action_view/test_case.rb +57 -62
  68. data/lib/action_view.rb +4 -0
  69. metadata +17 -14
  70. data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
@@ -166,7 +166,7 @@ module ActionView
166
166
  # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
167
167
  # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
168
168
  #
169
- # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
169
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match)) }
170
170
  # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
171
171
  #
172
172
  # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
@@ -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
 
@@ -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 ||= {}
@@ -402,8 +341,9 @@ module ActionView
402
341
  inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
403
342
  if params
404
343
  to_form_params(params).each do |param|
405
- inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value],
406
- 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)
407
347
  end
408
348
  end
409
349
  html = content_tag("form", inner_tags, form_options)
@@ -561,8 +501,6 @@ module ActionView
561
501
  content_tag("a", name || email_address, html_options, &block)
562
502
  end
563
503
 
564
- RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
565
-
566
504
  # True if the current request URI was generated by the given +options+.
567
505
  #
568
506
  # ==== Examples
@@ -601,32 +539,55 @@ module ActionView
601
539
  # current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
602
540
  # # => true
603
541
  #
604
- # 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.
605
544
  #
606
545
  # current_page?(controller: 'product', action: 'index')
607
546
  # # => false
608
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
+ #
609
557
  # We can also pass in the symbol arguments instead of strings.
610
558
  #
611
- 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)
612
560
  unless request
613
561
  raise "You cannot use helpers that need to determine the current " \
614
562
  "page unless your view context provides a Request object " \
615
563
  "in a #request method"
616
564
  end
617
565
 
618
- 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
619
582
 
620
- options ||= options_as_kwargs
621
- check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
622
- 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)
623
584
 
624
585
  # We ignore any extra parameters in the request_uri if the
625
586
  # submitted URL doesn't have any either. This lets the function
626
587
  # work with things like ?order=asc
627
588
  # the behavior can be disabled with check_parameters: true
628
589
  request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
629
- request_uri = RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
590
+ request_uri = URI::RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
630
591
 
631
592
  if %r{^\w+://}.match?(url_string)
632
593
  request_uri = +"#{request.protocol}#{request.host_with_port}#{request_uri}"
@@ -638,19 +599,6 @@ module ActionView
638
599
  url_string == request_uri
639
600
  end
640
601
 
641
- if RUBY_VERSION.start_with?("2.7")
642
- using Module.new {
643
- refine UrlHelper do
644
- alias :_current_page? :current_page?
645
- end
646
- }
647
-
648
- def current_page?(*args) # :nodoc:
649
- options = args.pop
650
- options.is_a?(Hash) ? _current_page?(*args, **options) : _current_page?(*args, options)
651
- end
652
- end
653
-
654
602
  # Creates an SMS anchor link tag to the specified +phone_number+. When the
655
603
  # link is clicked, the default SMS messaging app is opened ready to send a
656
604
  # message to the linked phone number. If the +body+ option is specified,
@@ -827,14 +775,18 @@ module ActionView
827
775
  else
828
776
  token
829
777
  end
830
- 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)
831
781
  else
832
782
  ""
833
783
  end
834
784
  end
835
785
 
836
786
  def method_tag(method)
837
- 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)
838
790
  end
839
791
 
840
792
  # Returns an array of hashes each containing :name and :value keys
@@ -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
@@ -286,7 +284,7 @@ module ActionView
286
284
  silence_redefinition_of_method(:_layout)
287
285
 
288
286
  prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
289
- default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
287
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, keys, { formats: formats }).first || super"
290
288
  name_clause = if name
291
289
  default_behavior
292
290
  else
@@ -327,7 +325,7 @@ module ActionView
327
325
 
328
326
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
329
327
  # frozen_string_literal: true
330
- def _layout(lookup_context, formats)
328
+ def _layout(lookup_context, formats, keys)
331
329
  if _conditional_layout?
332
330
  #{layout_definition}
333
331
  else
@@ -349,7 +347,7 @@ module ActionView
349
347
  end
350
348
  end
351
349
 
352
- def _normalize_options(options) # :nodoc:
350
+ def _process_render_template_options(options) # :nodoc:
353
351
  super
354
352
 
355
353
  if _include_layout?(options)
@@ -391,8 +389,8 @@ module ActionView
391
389
  case name
392
390
  when String then _normalize_layout(name)
393
391
  when Proc then name
394
- when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
395
- when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
392
+ when true then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, true) }
393
+ when :default then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, false) }
396
394
  when false, nil then nil
397
395
  else
398
396
  raise ArgumentError,
@@ -414,9 +412,9 @@ module ActionView
414
412
  #
415
413
  # ==== Returns
416
414
  # * <tt>template</tt> - The template object for the default layout (or +nil+)
417
- def _default_layout(lookup_context, formats, require_layout = false)
415
+ def _default_layout(lookup_context, formats, keys, require_layout = false)
418
416
  begin
419
- value = _layout(lookup_context, formats) if action_has_layout?
417
+ value = _layout(lookup_context, formats, keys) if action_has_layout?
420
418
  rescue NameError => e
421
419
  raise e, "Could not render layout: #{e.message}"
422
420
  end
@@ -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
@@ -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:
@@ -3,10 +3,7 @@
3
3
  require "active_support/log_subscriber"
4
4
 
5
5
  module ActionView
6
- # = Action View Log Subscriber
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
@@ -18,7 +15,7 @@ module ActionView
18
15
  info do
19
16
  message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
17
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
18
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
22
19
  end
23
20
  end
24
21
  subscribe_log_level :render_template, :debug
@@ -27,7 +24,7 @@ module ActionView
27
24
  debug do
28
25
  message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
29
26
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
30
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
27
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
31
28
  message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
32
29
  message
33
30
  end
@@ -37,7 +34,7 @@ module ActionView
37
34
  def render_layout(event)
38
35
  info do
39
36
  message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
40
- message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
37
+ message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
41
38
  end
42
39
  end
43
40
  subscribe_log_level :render_layout, :info
@@ -48,7 +45,7 @@ module ActionView
48
45
  debug do
49
46
  message = +" Rendered collection of #{from_rails_root(identifier)}"
50
47
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
51
- message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
48
+ message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)"
52
49
  message
53
50
  end
54
51
  end
@@ -96,6 +93,10 @@ module ActionView
96
93
 
97
94
  def finish(name, id, payload)
98
95
  end
96
+
97
+ def silenced?(_)
98
+ logger.nil? || !logger.debug?
99
+ end
99
100
  end
100
101
 
101
102
  def self.attach_to(*)
@@ -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
@@ -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
@@ -116,7 +127,6 @@ module ActionView
116
127
  view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
128
 
118
129
  app.reloaders << view_reloader
119
- view_reloader.execute
120
130
  app.reloader.to_run do
121
131
  require_unload_lock!
122
132
  view_reloader.execute
@@ -11,7 +11,7 @@ module ActionView
11
11
  #
12
12
  # Consider for example the following code that form of post:
13
13
  #
14
- # <%= form_for(post) do |f| %>
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.
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module RenderParser
5
+ class PrismRenderParser < Base # :nodoc:
6
+ def render_calls
7
+ queue = [Prism.parse(@code).value]
8
+ templates = []
9
+
10
+ while (node = queue.shift)
11
+ queue.concat(node.compact_child_nodes)
12
+ next unless node.is_a?(Prism::CallNode)
13
+
14
+ options = render_call_options(node)
15
+ next unless options
16
+
17
+ render_type = (options.keys & RENDER_TYPE_KEYS)[0]
18
+ template, object_template = render_call_template(options[render_type])
19
+ next unless template
20
+
21
+ if options.key?(:object) || options.key?(:collection) || object_template
22
+ next if options.key?(:object) && options.key?(:collection)
23
+ next unless options.key?(:partial)
24
+ end
25
+
26
+ if options[:spacer_template].is_a?(Prism::StringNode)
27
+ templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped)
28
+ end
29
+
30
+ templates << partial_to_virtual_path(render_type, template)
31
+
32
+ if render_type != :layout && options[:layout].is_a?(Prism::StringNode)
33
+ templates << partial_to_virtual_path(:layout, options[:layout].unescaped)
34
+ end
35
+ end
36
+
37
+ templates
38
+ end
39
+
40
+ private
41
+ # Accept a call node and return a hash of options for the render call.
42
+ # If it doesn't match the expected format, return nil.
43
+ def render_call_options(node)
44
+ # We are only looking for calls to render or render_to_string.
45
+ name = node.name.to_sym
46
+ return if name != :render && name != :render_to_string
47
+
48
+ # We are only looking for calls with arguments.
49
+ arguments = node.arguments
50
+ return unless arguments
51
+
52
+ arguments = arguments.arguments
53
+ length = arguments.length
54
+
55
+ # Get rid of any parentheses to get directly to the contents.
56
+ arguments.map! do |argument|
57
+ current = argument
58
+
59
+ while current.is_a?(Prism::ParenthesesNode) &&
60
+ current.body.is_a?(Prism::StatementsNode) &&
61
+ current.body.body.length == 1
62
+ current = current.body.body.first
63
+ end
64
+
65
+ current
66
+ end
67
+
68
+ # We are only looking for arguments that are either a string with an
69
+ # array of locals or a keyword hash with symbol keys.
70
+ options =
71
+ if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode)
72
+ { partial: arguments[0], locals: arguments[1] }
73
+ elsif length == 1 &&
74
+ arguments[0].is_a?(Prism::KeywordHashNode) &&
75
+ arguments[0].elements.all? do |element|
76
+ element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode)
77
+ end
78
+ arguments[0].elements.to_h do |element|
79
+ [element.key.unescaped.to_sym, element.value]
80
+ end
81
+ end
82
+
83
+ return unless options
84
+
85
+ # Here we validate that the options have the keys we expect.
86
+ keys = options.keys
87
+ return if !keys.intersect?(RENDER_TYPE_KEYS)
88
+ return if (keys - ALL_KNOWN_KEYS).any?
89
+
90
+ # Finally, we can return a valid set of options.
91
+ options
92
+ end
93
+
94
+ # Accept the node that is being passed in the position of the template
95
+ # and return the template name and whether or not it is an object
96
+ # template.
97
+ def render_call_template(node)
98
+ object_template = false
99
+ template =
100
+ case node.type
101
+ when :string_node
102
+ path = node.unescaped
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("")
115
+ else
116
+ dependency =
117
+ case node.type
118
+ when :class_variable_read_node
119
+ node.slice[2..]
120
+ when :instance_variable_read_node
121
+ node.slice[1..]
122
+ when :global_variable_read_node
123
+ node.slice[1..]
124
+ when :local_variable_read_node
125
+ node.slice
126
+ when :call_node
127
+ node.name.to_s
128
+ else
129
+ return
130
+ end
131
+
132
+ "#{dependency.pluralize}/#{dependency.singularize}"
133
+ end
134
+
135
+ [template, object_template]
136
+ end
137
+ end
138
+ end
139
+ end