actionview 7.0.8.7 → 7.1.5.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +309 -321
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +34 -14
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +40 -43
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/deprecator.rb +7 -0
  12. data/lib/action_view/digestor.rb +1 -1
  13. data/lib/action_view/gem_version.rb +4 -4
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +136 -52
  16. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  17. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  18. data/lib/action_view/helpers/cache_helper.rb +7 -13
  19. data/lib/action_view/helpers/capture_helper.rb +30 -10
  20. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  21. data/lib/action_view/helpers/controller_helper.rb +6 -0
  22. data/lib/action_view/helpers/csp_helper.rb +2 -2
  23. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  24. data/lib/action_view/helpers/date_helper.rb +17 -19
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +54 -25
  27. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  28. data/lib/action_view/helpers/form_tag_helper.rb +49 -15
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +37 -330
  31. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  32. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  33. data/lib/action_view/helpers/sanitize_helper.rb +51 -21
  34. data/lib/action_view/helpers/tag_helper.rb +5 -27
  35. data/lib/action_view/helpers/tags/base.rb +11 -52
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  38. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  41. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  43. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/select.rb +3 -0
  46. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  47. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  48. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  49. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  51. data/lib/action_view/helpers/tags.rb +2 -0
  52. data/lib/action_view/helpers/text_helper.rb +156 -84
  53. data/lib/action_view/helpers/translation_helper.rb +3 -3
  54. data/lib/action_view/helpers/url_helper.rb +46 -17
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +8 -6
  57. data/lib/action_view/log_subscriber.rb +49 -32
  58. data/lib/action_view/lookup_context.rb +29 -13
  59. data/lib/action_view/path_registry.rb +57 -0
  60. data/lib/action_view/path_set.rb +13 -14
  61. data/lib/action_view/railtie.rb +26 -3
  62. data/lib/action_view/record_identifier.rb +15 -8
  63. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  64. data/lib/action_view/renderer/collection_renderer.rb +10 -2
  65. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  66. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  67. data/lib/action_view/renderer/renderer.rb +2 -0
  68. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  69. data/lib/action_view/renderer/template_renderer.rb +3 -2
  70. data/lib/action_view/rendering.rb +22 -4
  71. data/lib/action_view/ripper_ast_parser.rb +5 -5
  72. data/lib/action_view/template/error.rb +14 -1
  73. data/lib/action_view/template/handlers/builder.rb +4 -4
  74. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  75. data/lib/action_view/template/handlers/erb.rb +73 -1
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/html.rb +1 -1
  78. data/lib/action_view/template/raw_file.rb +1 -1
  79. data/lib/action_view/template/renderable.rb +1 -1
  80. data/lib/action_view/template/resolver.rb +10 -2
  81. data/lib/action_view/template/text.rb +1 -1
  82. data/lib/action_view/template/types.rb +25 -34
  83. data/lib/action_view/template.rb +249 -54
  84. data/lib/action_view/template_path.rb +2 -0
  85. data/lib/action_view/test_case.rb +176 -21
  86. data/lib/action_view/unbound_template.rb +17 -7
  87. data/lib/action_view/version.rb +1 -1
  88. data/lib/action_view/view_paths.rb +15 -24
  89. data/lib/action_view.rb +4 -1
  90. metadata +22 -23
  91. data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -4,12 +4,14 @@ require "action_view/rendering"
4
4
  require "active_support/core_ext/module/redefine_method"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Layouts
8
+ #
7
9
  # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
8
10
  # repeated setups. The inclusion pattern has pages that look like this:
9
11
  #
10
- # <%= render "shared/header" %>
12
+ # <%= render "application/header" %>
11
13
  # Hello World
12
- # <%= render "shared/footer" %>
14
+ # <%= render "application/footer" %>
13
15
  #
14
16
  # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
15
17
  # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
@@ -150,7 +152,7 @@ module ActionView
150
152
  # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
151
153
  # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
152
154
  #
153
- # Setting the layout to +nil+ forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
155
+ # Setting the layout to +nil+ forces it to be looked up in the filesystem and falls back to the parent behavior if none exists.
154
156
  # Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent:
155
157
  #
156
158
  # class ApplicationController < ActionController::Base
@@ -162,7 +164,7 @@ module ActionView
162
164
  # end
163
165
  #
164
166
  # class CommentsController < ApplicationController
165
- # # Will search for "comments" layout and fallback "application" layout
167
+ # # Will search for "comments" layout and fall back to "application" layout
166
168
  # layout nil
167
169
  # end
168
170
  #
@@ -210,9 +212,9 @@ module ActionView
210
212
  class_attribute :_layout_conditions, instance_accessor: false, default: {}
211
213
 
212
214
  _write_layout_method
213
- end
214
215
 
215
- delegate :_layout_conditions, to: :class
216
+ delegate :_layout_conditions, to: :class
217
+ end
216
218
 
217
219
  module ClassMethods
218
220
  def inherited(klass) # :nodoc:
@@ -5,7 +5,7 @@ require "active_support/log_subscriber"
5
5
  module ActionView
6
6
  # = Action View Log Subscriber
7
7
  #
8
- # Provides functionality so that Rails can output logs from Action View.
8
+ # Provides functionality so that \Rails can output logs from Action View.
9
9
  class LogSubscriber < ActiveSupport::LogSubscriber
10
10
  VIEWS_PATTERN = /^app\/views\//
11
11
 
@@ -21,6 +21,7 @@ module ActionView
21
21
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
22
  end
23
23
  end
24
+ subscribe_log_level :render_template, :debug
24
25
 
25
26
  def render_partial(event)
26
27
  debug do
@@ -31,6 +32,7 @@ module ActionView
31
32
  message
32
33
  end
33
34
  end
35
+ subscribe_log_level :render_partial, :debug
34
36
 
35
37
  def render_layout(event)
36
38
  info do
@@ -38,6 +40,7 @@ module ActionView
38
40
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
39
41
  end
40
42
  end
43
+ subscribe_log_level :render_layout, :info
41
44
 
42
45
  def render_collection(event)
43
46
  identifier = event.payload[:identifier] || "templates"
@@ -49,29 +52,60 @@ module ActionView
49
52
  message
50
53
  end
51
54
  end
55
+ subscribe_log_level :render_collection, :debug
52
56
 
53
- def start(name, id, payload)
54
- log_rendering_start(payload, name)
57
+ module Utils # :nodoc:
58
+ def logger
59
+ ActionView::Base.logger
60
+ end
55
61
 
56
- super
57
- end
62
+ private
63
+ def from_rails_root(string)
64
+ string = string.sub(rails_root, "")
65
+ string.sub!(VIEWS_PATTERN, "")
66
+ string
67
+ end
58
68
 
59
- def logger
60
- ActionView::Base.logger
69
+ def rails_root # :doc:
70
+ @root ||= "#{Rails.root}/"
71
+ end
61
72
  end
62
73
 
63
- private
64
- EMPTY = ""
65
- def from_rails_root(string) # :doc:
66
- string = string.sub(rails_root, EMPTY)
67
- string.sub!(VIEWS_PATTERN, EMPTY)
68
- string
74
+ include Utils
75
+
76
+ class Start # :nodoc:
77
+ include Utils
78
+
79
+ def start(name, id, payload)
80
+ return unless logger
81
+ logger.debug do
82
+ qualifier =
83
+ if name == "render_template.action_view"
84
+ ""
85
+ elsif name == "render_layout.action_view"
86
+ "layout "
87
+ end
88
+
89
+ return unless qualifier
90
+
91
+ message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
92
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
93
+ message
94
+ end
95
+ end
96
+
97
+ def finish(name, id, payload)
98
+ end
69
99
  end
70
100
 
71
- def rails_root # :doc:
72
- @root ||= "#{Rails.root}/"
101
+ def self.attach_to(*)
102
+ ActiveSupport::Notifications.subscribe("render_template.action_view", ActionView::LogSubscriber::Start.new)
103
+ ActiveSupport::Notifications.subscribe("render_layout.action_view", ActionView::LogSubscriber::Start.new)
104
+
105
+ super
73
106
  end
74
107
 
108
+ private
75
109
  def render_count(payload) # :doc:
76
110
  if payload[:cache_hits]
77
111
  "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
@@ -88,23 +122,6 @@ module ActionView
88
122
  "[cache miss]"
89
123
  end
90
124
  end
91
-
92
- def log_rendering_start(payload, name)
93
- debug do
94
- qualifier =
95
- if name == "render_template.action_view"
96
- ""
97
- elsif name == "render_layout.action_view"
98
- "layout "
99
- end
100
-
101
- return unless qualifier
102
-
103
- message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
104
- message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
105
- message
106
- end
107
- end
108
125
  end
109
126
  end
110
127
 
@@ -13,7 +13,7 @@ module ActionView
13
13
  # view paths, used in the resolver cache lookup. Since this key is generated
14
14
  # only once during the request, it speeds up all cache accesses.
15
15
  class LookupContext # :nodoc:
16
- attr_accessor :prefixes, :rendered_format
16
+ attr_accessor :prefixes
17
17
 
18
18
  singleton_class.attr_accessor :registered_details
19
19
  self.registered_details = []
@@ -63,16 +63,20 @@ module ActionView
63
63
  end
64
64
 
65
65
  def self.details_cache_key(details)
66
- if details[:formats]
67
- details = details.dup
68
- details[:formats] &= Template::Types.symbols
66
+ @details_keys.fetch(details) do
67
+ if formats = details[:formats]
68
+ unless Template::Types.valid_symbols?(formats)
69
+ details = details.dup
70
+ details[:formats] &= Template::Types.symbols
71
+ end
72
+ end
73
+ @details_keys[details] ||= TemplateDetails::Requested.new(**details)
69
74
  end
70
- @details_keys[details] ||= TemplateDetails::Requested.new(**details)
71
75
  end
72
76
 
73
77
  def self.clear
74
- ActionView::ViewPaths.all_view_paths.each do |path_set|
75
- path_set.each(&:clear_cache)
78
+ ActionView::PathRegistry.all_resolvers.each do |resolver|
79
+ resolver.clear_cache
76
80
  end
77
81
  @view_context_class = nil
78
82
  @details_keys.clear
@@ -83,9 +87,9 @@ module ActionView
83
87
  @digest_cache.values
84
88
  end
85
89
 
86
- def self.view_context_class(klass)
90
+ def self.view_context_class
87
91
  @view_context_mutex.synchronize do
88
- @view_context_class ||= klass.with_empty_template_cache
92
+ @view_context_class ||= ActionView::Base.with_empty_template_cache
89
93
  end
90
94
  end
91
95
  end
@@ -148,11 +152,23 @@ module ActionView
148
152
  end
149
153
  alias :any_templates? :any?
150
154
 
155
+ def append_view_paths(paths)
156
+ @view_paths = build_view_paths(@view_paths.to_a + paths)
157
+ end
158
+
159
+ def prepend_view_paths(paths)
160
+ @view_paths = build_view_paths(paths + @view_paths.to_a)
161
+ end
162
+
151
163
  private
152
164
  # Whenever setting view paths, makes a copy so that we can manipulate them in
153
165
  # instance objects as we wish.
154
166
  def build_view_paths(paths)
155
- ActionView::PathSet.new(Array(paths))
167
+ if ActionView::PathSet === paths
168
+ paths
169
+ else
170
+ ActionView::PathSet.new(Array(paths))
171
+ end
156
172
  end
157
173
 
158
174
  # Compute details hash and key according to user options (e.g. passed from #render).
@@ -250,12 +266,12 @@ module ActionView
250
266
  values.concat(default_formats) if values.delete "*/*"
251
267
  values.uniq!
252
268
 
253
- invalid_values = (values - Template::Types.symbols)
254
- unless invalid_values.empty?
269
+ unless Template::Types.valid_symbols?(values)
270
+ invalid_values = values - Template::Types.symbols
255
271
  raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
256
272
  end
257
273
 
258
- if values == [:js]
274
+ if (values.length == 1) && (values[0] == :js)
259
275
  values << :html
260
276
  @html_fallback_for_js = true
261
277
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView # :nodoc:
4
+ module PathRegistry # :nodoc:
5
+ @view_paths_by_class = {}
6
+ @file_system_resolvers = {}
7
+ @file_system_resolver_mutex = Mutex.new
8
+ @file_system_resolver_hooks = []
9
+
10
+ class << self
11
+ attr_reader :file_system_resolver_hooks
12
+ end
13
+
14
+ def self.get_view_paths(klass)
15
+ @view_paths_by_class[klass] || get_view_paths(klass.superclass)
16
+ end
17
+
18
+ def self.set_view_paths(klass, paths)
19
+ @view_paths_by_class[klass] = paths
20
+ end
21
+
22
+ def self.cast_file_system_resolvers(paths)
23
+ paths = Array(paths)
24
+
25
+ @file_system_resolver_mutex.synchronize do
26
+ built_resolver = false
27
+ paths = paths.map do |path|
28
+ case path
29
+ when String, Pathname
30
+ path = File.expand_path(path)
31
+ @file_system_resolvers[path] ||=
32
+ begin
33
+ built_resolver = true
34
+ FileSystemResolver.new(path)
35
+ end
36
+ else
37
+ path
38
+ end
39
+ end
40
+
41
+ file_system_resolver_hooks.each(&:call) if built_resolver
42
+ end
43
+
44
+ paths
45
+ end
46
+
47
+ def self.all_resolvers
48
+ resolvers = [all_file_system_resolvers]
49
+ resolvers.concat @view_paths_by_class.values.map(&:to_a)
50
+ resolvers.flatten.uniq
51
+ end
52
+
53
+ def self.all_file_system_resolvers
54
+ @file_system_resolvers.values
55
+ end
56
+ end
57
+ end
@@ -13,14 +13,14 @@ module ActionView # :nodoc:
13
13
 
14
14
  attr_reader :paths
15
15
 
16
- delegate :[], :include?, :pop, :size, :each, to: :paths
16
+ delegate :[], :include?, :size, :each, to: :paths
17
17
 
18
18
  def initialize(paths = [])
19
- @paths = typecast paths
19
+ @paths = typecast(paths).freeze
20
20
  end
21
21
 
22
22
  def initialize_copy(other)
23
- @paths = other.paths.dup
23
+ @paths = other.paths.dup.freeze
24
24
  self
25
25
  end
26
26
 
@@ -32,18 +32,11 @@ module ActionView # :nodoc:
32
32
  PathSet.new paths.compact
33
33
  end
34
34
 
35
- def +(array)
35
+ def +(other)
36
+ array = Array === other ? other : other.paths
36
37
  PathSet.new(paths + array)
37
38
  end
38
39
 
39
- %w(<< concat push insert unshift).each do |method|
40
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
41
- def #{method}(*args)
42
- paths.#{method}(*typecast(args))
43
- end
44
- METHOD
45
- end
46
-
47
40
  def find(path, prefixes, partial, details, details_key, locals)
48
41
  find_all(path, prefixes, partial, details, details_key, locals).first ||
49
42
  raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
@@ -75,9 +68,15 @@ module ActionView # :nodoc:
75
68
  paths.map do |path|
76
69
  case path
77
70
  when Pathname, String
78
- FileSystemResolver.new path.to_s
79
- else
71
+ # This path should only be reached by "direct" users of
72
+ # ActionView::Base (not using the ViewPaths or Renderer modules).
73
+ # We can't cache/de-dup the file system resolver in this case as we
74
+ # don't know which compiled_method_container we'll be rendering to.
75
+ FileSystemResolver.new(path)
76
+ when Resolver
80
77
  path
78
+ else
79
+ raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver"
81
80
  end
82
81
  end
83
82
  end
@@ -13,6 +13,7 @@ module ActionView
13
13
  config.action_view.image_loading = nil
14
14
  config.action_view.image_decoding = nil
15
15
  config.action_view.apply_stylesheet_media_default = true
16
+ config.action_view.prepend_content_exfiltration_prevention = false
16
17
 
17
18
  config.eager_load_namespaces << ActionView
18
19
 
@@ -40,6 +41,17 @@ module ActionView
40
41
  end
41
42
  end
42
43
 
44
+ config.after_initialize do |app|
45
+ prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
46
+ ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
47
+ end
48
+
49
+ config.after_initialize do |app|
50
+ if klass = app.config.action_view.delete(:sanitizer_vendor)
51
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
52
+ end
53
+ end
54
+
43
55
  config.after_initialize do |app|
44
56
  button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
45
57
  unless button_to_generates_button_tag.nil?
@@ -67,6 +79,10 @@ module ActionView
67
79
  end
68
80
  end
69
81
 
82
+ initializer "action_view.deprecator", before: :load_environment_config do |app|
83
+ app.deprecators[:action_view] = ActionView.deprecator
84
+ end
85
+
70
86
  initializer "action_view.logger" do
71
87
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
72
88
  end
@@ -74,7 +90,7 @@ module ActionView
74
90
  initializer "action_view.caching" do |app|
75
91
  ActiveSupport.on_load(:action_view) do
76
92
  if app.config.action_view.cache_template_loading.nil?
77
- ActionView::Resolver.caching = app.config.cache_classes
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
78
94
  end
79
95
  end
80
96
  end
@@ -91,13 +107,20 @@ module ActionView
91
107
 
92
108
  config.after_initialize do |app|
93
109
  enable_caching = if app.config.action_view.cache_template_loading.nil?
94
- app.config.cache_classes
110
+ !app.config.reloading_enabled?
95
111
  else
96
112
  app.config.action_view.cache_template_loading
97
113
  end
98
114
 
99
115
  unless enable_caching
100
- app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ view_reloader.execute
120
+ app.reloader.to_run do
121
+ require_unload_lock!
122
+ view_reloader.execute
123
+ end
101
124
  end
102
125
  end
103
126
 
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
4
4
  require "action_view/model_naming"
5
5
 
6
6
  module ActionView
7
+ # = Action View \Record \Identifier
8
+ #
7
9
  # RecordIdentifier encapsulates methods used by various ActionView helpers
8
10
  # to associate records with DOM elements.
9
11
  #
@@ -31,6 +33,8 @@ module ActionView
31
33
  # automatically generated, following naming conventions encapsulated by the
32
34
  # RecordIdentifier methods #dom_id and #dom_class:
33
35
  #
36
+ # dom_id(Post) # => "new_post"
37
+ # dom_class(Post) # => "post"
34
38
  # dom_id(Post.new) # => "new_post"
35
39
  # dom_class(Post.new) # => "post"
36
40
  # dom_id(Post.find 42) # => "post_42"
@@ -79,18 +83,21 @@ module ActionView
79
83
  # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
80
84
  # If no id is found, prefix with "new_" instead.
81
85
  #
82
- # dom_id(Post.find(45)) # => "post_45"
83
- # dom_id(Post.new) # => "new_post"
86
+ # dom_id(Post.find(45)) # => "post_45"
87
+ # dom_id(Post) # => "new_post"
84
88
  #
85
89
  # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
86
90
  #
87
91
  # dom_id(Post.find(45), :edit) # => "edit_post_45"
88
- # dom_id(Post.new, :custom) # => "custom_post"
89
- def dom_id(record, prefix = nil)
90
- if record_id = record_key_for_dom_id(record)
91
- "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
92
+ # dom_id(Post, :custom) # => "custom_post"
93
+ def dom_id(record_or_class, prefix = nil)
94
+ raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
95
+
96
+ record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
97
+ if record_id
98
+ "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
92
99
  else
93
- dom_class(record, prefix || NEW)
100
+ dom_class(record_or_class, prefix || NEW)
94
101
  end
95
102
  end
96
103
 
@@ -105,7 +112,7 @@ module ActionView
105
112
  # make sure yourself that your dom ids are valid, in case you override this method.
106
113
  def record_key_for_dom_id(record) # :doc:
107
114
  key = convert_to_model(record).to_key
108
- key ? key.join(JOIN) : key
115
+ key && key.all? ? key.join(JOIN) : nil
109
116
  end
110
117
  end
111
118
  end
@@ -31,7 +31,7 @@ module ActionView
31
31
 
32
32
  module ObjectRendering # :nodoc:
33
33
  PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
- h[k] = Concurrent::Map.new
34
+ h.compute_if_absent(k) { Concurrent::Map.new }
35
35
  end
36
36
 
37
37
  def initialize(lookup_context, options)
@@ -51,6 +51,10 @@ module ActionView
51
51
  def length
52
52
  @collection.respond_to?(:length) ? @collection.length : size
53
53
  end
54
+
55
+ def preload!
56
+ # no-op
57
+ end
54
58
  end
55
59
 
56
60
  class SameCollectionIterator < CollectionIterator # :nodoc:
@@ -84,9 +88,13 @@ module ActionView
84
88
 
85
89
  def each_with_info
86
90
  return super unless block_given?
87
- @relation.preload_associations(@collection)
91
+ preload!
88
92
  super
89
93
  end
94
+
95
+ def preload!
96
+ @relation.preload_associations(@collection)
97
+ end
90
98
  end
91
99
 
92
100
  class MixedCollectionIterator < CollectionIterator # :nodoc:
@@ -186,7 +194,7 @@ module ActionView
186
194
 
187
195
  _template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
188
196
 
189
- content = _template.render(view, locals)
197
+ content = _template.render(view, locals, implicit_locals: [counter, iteration])
190
198
  content = layout.render(view, locals) { content } if layout
191
199
  partial_iteration.iterate!
192
200
  build_rendered_template(content, _template)
@@ -59,6 +59,7 @@ module ActionView
59
59
  seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
60
60
 
61
61
  digest_path = view.digest_path_from_template(template)
62
+ collection.preload! if callable_cache_key?
62
63
 
63
64
  collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
64
65
  key = expanded_cache_key(seed.call(item), view, template, digest_path)
@@ -100,7 +101,7 @@ module ActionView
100
101
  # We want to cache buffers as raw strings. This both improve performance and
101
102
  # avoid creating forward compatibility issues with the internal representation
102
103
  # of these two types.
103
- if body.is_a?(ActiveSupport::SafeBuffer)
104
+ if body.is_a?(ActionView::OutputBuffer) || body.is_a?(ActiveSupport::SafeBuffer)
104
105
  body = body.to_str
105
106
  end
106
107
 
@@ -246,7 +246,8 @@ module ActionView
246
246
  ActiveSupport::Notifications.instrument(
247
247
  "render_partial.action_view",
248
248
  identifier: template.identifier,
249
- layout: layout && layout.virtual_path
249
+ layout: layout && layout.virtual_path,
250
+ locals: locals
250
251
  ) do |payload|
251
252
  content = template.render(view, locals, add_to_stack: !block) do |*name|
252
253
  view._layout_for(*name, &block)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
+ # = Action View \Renderer
5
+ #
4
6
  # This is the main entry point for rendering. It basically delegates
5
7
  # to other objects like TemplateRenderer and PartialRenderer which
6
8
  # actually renders the template.
@@ -46,7 +46,7 @@ module ActionView
46
46
  return [super.body] unless layout_name && template.supports_streaming?
47
47
 
48
48
  locals ||= {}
49
- layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
49
+ layout = find_layout(layout_name, locals.keys, [formats.first])
50
50
 
51
51
  Body.new do |buffer|
52
52
  delayed_render(buffer, template, layout, view, locals)
@@ -65,7 +65,8 @@ module ActionView
65
65
  ActiveSupport::Notifications.instrument(
66
66
  "render_template.action_view",
67
67
  identifier: template.identifier,
68
- layout: layout && layout.virtual_path
68
+ layout: layout && layout.virtual_path,
69
+ locals: locals
69
70
  ) do
70
71
  outer_config = I18n.config
71
72
  fiber = Fiber.new do
@@ -49,7 +49,7 @@ module ActionView
49
49
  @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
50
50
  end
51
51
  else
52
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
52
+ raise ArgumentError, "You invoked render but did not give any of :body, :file, :html, :inline, :partial, :plain, :renderable, or :template option."
53
53
  end
54
54
  end
55
55
 
@@ -60,7 +60,8 @@ module ActionView
60
60
  ActiveSupport::Notifications.instrument(
61
61
  "render_template.action_view",
62
62
  identifier: template.identifier,
63
- layout: layout && layout.virtual_path
63
+ layout: layout && layout.virtual_path,
64
+ locals: locals
64
65
  ) do
65
66
  template.render(view, locals) { |*name| view._layout_for(*name) }
66
67
  end
@@ -10,7 +10,8 @@ module ActionView
10
10
 
11
11
  def initialize(original_config, lookup_context)
12
12
  original_config = original_config.original_config if original_config.respond_to?(:original_config)
13
- @original_config, @lookup_context = original_config, lookup_context
13
+ @original_config = original_config
14
+ @lookup_context = lookup_context
14
15
  end
15
16
 
16
17
  def locale
@@ -48,7 +49,18 @@ module ActionView
48
49
  def _helpers
49
50
  end
50
51
 
52
+ def inherit_view_context_class?
53
+ superclass.respond_to?(:view_context_class) &&
54
+ supports_path? == superclass.supports_path? &&
55
+ _routes.equal?(superclass._routes) &&
56
+ _helpers.equal?(superclass._helpers)
57
+ end
58
+
51
59
  def build_view_context_class(klass, supports_path, routes, helpers)
60
+ if inherit_view_context_class?
61
+ return superclass.view_context_class
62
+ end
63
+
52
64
  Class.new(klass) do
53
65
  if routes
54
66
  include routes.url_helpers(supports_path)
@@ -61,8 +73,14 @@ module ActionView
61
73
  end
62
74
  end
63
75
 
76
+ def eager_load!
77
+ super
78
+ view_context_class
79
+ nil
80
+ end
81
+
64
82
  def view_context_class
65
- klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
83
+ klass = ActionView::LookupContext::DetailsKey.view_context_class
66
84
 
67
85
  @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
86
 
@@ -129,8 +147,8 @@ module ActionView
129
147
  lookup_context.formats = [format.to_sym] if format.to_sym
130
148
  end
131
149
 
132
- # Normalize args by converting render "foo" to render :action => "foo" and
133
- # render "foo/bar" to render :template => "foo/bar".
150
+ # Normalize args by converting render "foo" to render action: "foo" and
151
+ # render "foo/bar" to render template: "foo/bar".
134
152
  def _normalize_args(action = nil, options = {})
135
153
  options = super(action, options)
136
154
  case action