actionview 4.2.11.3 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +136 -255
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/base.rb +14 -2
  7. data/lib/action_view/dependency_tracker.rb +46 -15
  8. data/lib/action_view/digestor.rb +13 -9
  9. data/lib/action_view/flows.rb +1 -1
  10. data/lib/action_view/gem_version.rb +4 -4
  11. data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
  12. data/lib/action_view/helpers/asset_url_helper.rb +51 -12
  13. data/lib/action_view/helpers/atom_feed_helper.rb +5 -4
  14. data/lib/action_view/helpers/cache_helper.rb +75 -20
  15. data/lib/action_view/helpers/capture_helper.rb +3 -2
  16. data/lib/action_view/helpers/controller_helper.rb +1 -0
  17. data/lib/action_view/helpers/date_helper.rb +39 -10
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +81 -35
  20. data/lib/action_view/helpers/form_options_helper.rb +74 -35
  21. data/lib/action_view/helpers/form_tag_helper.rb +46 -19
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +10 -12
  24. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  25. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  26. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  27. data/lib/action_view/helpers/tag_helper.rb +20 -13
  28. data/lib/action_view/helpers/tags/base.rb +33 -28
  29. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -30
  30. data/lib/action_view/helpers/tags/collection_helpers.rb +28 -0
  31. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  32. data/lib/action_view/helpers/tags/file_field.rb +15 -0
  33. data/lib/action_view/helpers/tags/label.rb +1 -1
  34. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  35. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  36. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  37. data/lib/action_view/helpers/tags/translator.rb +1 -1
  38. data/lib/action_view/helpers/text_helper.rb +25 -9
  39. data/lib/action_view/helpers/translation_helper.rb +56 -26
  40. data/lib/action_view/helpers/url_helper.rb +40 -65
  41. data/lib/action_view/layouts.rb +11 -10
  42. data/lib/action_view/lookup_context.rb +14 -40
  43. data/lib/action_view/model_naming.rb +1 -1
  44. data/lib/action_view/path_set.rb +15 -18
  45. data/lib/action_view/railtie.rb +20 -3
  46. data/lib/action_view/record_identifier.rb +44 -19
  47. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  48. data/lib/action_view/renderer/partial_renderer.rb +27 -26
  49. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +70 -0
  50. data/lib/action_view/renderer/renderer.rb +2 -6
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  52. data/lib/action_view/renderer/template_renderer.rb +12 -11
  53. data/lib/action_view/rendering.rb +8 -5
  54. data/lib/action_view/routing_url_for.rb +18 -6
  55. data/lib/action_view/template.rb +50 -13
  56. data/lib/action_view/template/error.rb +14 -7
  57. data/lib/action_view/template/handlers.rb +3 -3
  58. data/lib/action_view/template/handlers/erb.rb +25 -0
  59. data/lib/action_view/template/handlers/raw.rb +1 -1
  60. data/lib/action_view/template/resolver.rb +36 -58
  61. data/lib/action_view/template/types.rb +1 -1
  62. data/lib/action_view/test_case.rb +13 -8
  63. data/lib/action_view/testing/resolvers.rb +3 -4
  64. data/lib/action_view/view_paths.rb +6 -22
  65. metadata +17 -14
@@ -228,7 +228,7 @@ module ActionView
228
228
  # set by the <tt>layout</tt> method.
229
229
  #
230
230
  # ==== Returns
231
- # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
231
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
232
232
  def _conditional_layout?
233
233
  return unless super
234
234
 
@@ -262,7 +262,7 @@ module ActionView
262
262
  def layout(layout, conditions = {})
263
263
  include LayoutConditions unless conditions.empty?
264
264
 
265
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
265
+ conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
266
266
  self._layout_conditions = conditions
267
267
 
268
268
  self._layout = layout
@@ -277,7 +277,7 @@ module ActionView
277
277
  remove_possible_method(:_layout)
278
278
 
279
279
  prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
280
- default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
280
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
281
281
  name_clause = if name
282
282
  default_behavior
283
283
  else
@@ -316,7 +316,7 @@ module ActionView
316
316
  end
317
317
 
318
318
  self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
319
- def _layout
319
+ def _layout(formats)
320
320
  if _conditional_layout?
321
321
  #{layout_definition}
322
322
  else
@@ -372,7 +372,7 @@ module ActionView
372
372
  end
373
373
 
374
374
  # This will be overwritten by _write_layout_method
375
- def _layout; end
375
+ def _layout(*); end
376
376
 
377
377
  # Determine the layout for a given name, taking into account the name type.
378
378
  #
@@ -382,8 +382,8 @@ module ActionView
382
382
  case name
383
383
  when String then _normalize_layout(name)
384
384
  when Proc then name
385
- when true then Proc.new { _default_layout(true) }
386
- when :default then Proc.new { _default_layout(false) }
385
+ when true then Proc.new { |formats| _default_layout(formats, true) }
386
+ when :default then Proc.new { |formats| _default_layout(formats, false) }
387
387
  when false, nil then nil
388
388
  else
389
389
  raise ArgumentError,
@@ -399,14 +399,15 @@ module ActionView
399
399
  # Optionally raises an exception if the layout could not be found.
400
400
  #
401
401
  # ==== Parameters
402
+ # * <tt>formats</tt> - The formats accepted to this layout
402
403
  # * <tt>require_layout</tt> - If set to true and layout is not found,
403
- # an ArgumentError exception is raised (defaults to false)
404
+ # an +ArgumentError+ exception is raised (defaults to false)
404
405
  #
405
406
  # ==== Returns
406
407
  # * <tt>template</tt> - The template object for the default layout (or nil)
407
- def _default_layout(require_layout = false)
408
+ def _default_layout(formats, require_layout = false)
408
409
  begin
409
- value = _layout if action_has_layout?
410
+ value = _layout(formats) if action_has_layout?
410
411
  rescue NameError => e
411
412
  raise e, "Could not render layout: #{e.message}"
412
413
  end
@@ -1,4 +1,4 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
2
  require 'active_support/core_ext/module/remove_method'
3
3
  require 'active_support/core_ext/module/attribute_accessors'
4
4
  require 'action_view/template/resolver'
@@ -6,10 +6,11 @@ require 'action_view/template/resolver'
6
6
  module ActionView
7
7
  # = Action View Lookup Context
8
8
  #
9
- # LookupContext is the object responsible to hold all information required to lookup
10
- # templates, i.e. view paths and details. The LookupContext is also responsible to
11
- # generate a key, given to view paths, used in the resolver cache lookup. Since
12
- # this key is generated just once during the request, it speeds up all cache accesses.
9
+ # <tt>LookupContext</tt> is the object responsible for holding all information
10
+ # required for looking up templates, i.e. view paths and details.
11
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
12
+ # view paths, used in the resolver cache lookup. Since this key is generated
13
+ # only once during the request, it speeds up all cache accesses.
13
14
  class LookupContext #:nodoc:
14
15
  attr_accessor :prefixes, :rendered_format
15
16
 
@@ -19,7 +20,7 @@ module ActionView
19
20
  mattr_accessor :registered_details
20
21
  self.registered_details = []
21
22
 
22
- def self.register_detail(name, options = {}, &block)
23
+ def self.register_detail(name, &block)
23
24
  self.registered_details << name
24
25
  initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
25
26
 
@@ -54,14 +55,14 @@ module ActionView
54
55
  end
55
56
  register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
56
57
  register_detail(:variants) { [] }
57
- register_detail(:handlers){ Template::Handlers.extensions }
58
+ register_detail(:handlers) { Template::Handlers.extensions }
58
59
 
59
60
  class DetailsKey #:nodoc:
60
61
  alias :eql? :equal?
61
62
  alias :object_hash :hash
62
63
 
63
64
  attr_reader :hash
64
- @details_keys = ThreadSafe::Cache.new
65
+ @details_keys = Concurrent::Map.new
65
66
 
66
67
  def self.get(details)
67
68
  if details[:formats]
@@ -122,15 +123,11 @@ module ActionView
122
123
  end
123
124
  alias :find_template :find
124
125
 
125
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
126
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
127
- end
128
-
129
126
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
130
127
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
131
128
  end
132
129
 
133
- def exists?(name, prefixes = [], partial = false, keys = [], options = {})
130
+ def exists?(name, prefixes = [], partial = false, keys = [], **options)
134
131
  @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
135
132
  end
136
133
  alias :template_exists? :exists?
@@ -176,13 +173,13 @@ module ActionView
176
173
  # name instead of the prefix.
177
174
  def normalize_name(name, prefixes) #:nodoc:
178
175
  prefixes = prefixes.presence
179
- parts = name.to_s.split('/')
176
+ parts = name.to_s.split('/'.freeze)
180
177
  parts.shift if parts.first.empty?
181
178
  name = parts.pop
182
179
 
183
180
  return name, prefixes || [""] if parts.empty?
184
181
 
185
- parts = parts.join('/')
182
+ parts = parts.join('/'.freeze)
186
183
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
187
184
 
188
185
  return name, prefixes
@@ -195,7 +192,6 @@ module ActionView
195
192
 
196
193
  def initialize(view_paths, details = {}, prefixes = [])
197
194
  @details, @details_key = {}, nil
198
- @skip_default_locale = false
199
195
  @cache = true
200
196
  @prefixes = prefixes
201
197
  @rendered_format = nil
@@ -208,7 +204,7 @@ module ActionView
208
204
  # add :html as fallback to :js.
209
205
  def formats=(values)
210
206
  if values
211
- values.concat(default_formats) if values.delete "*/*"
207
+ values.concat(default_formats) if values.delete "*/*".freeze
212
208
  if values == [:js]
213
209
  values << :html
214
210
  @html_fallback_for_js = true
@@ -217,12 +213,6 @@ module ActionView
217
213
  super(values)
218
214
  end
219
215
 
220
- # Do not use the default locale on template lookup.
221
- def skip_default_locale!
222
- @skip_default_locale = true
223
- self.locale = nil
224
- end
225
-
226
216
  # Override locale to return a symbol instead of array.
227
217
  def locale
228
218
  @details[:locale].first
@@ -237,23 +227,7 @@ module ActionView
237
227
  config.locale = value
238
228
  end
239
229
 
240
- super(@skip_default_locale ? I18n.locale : default_locale)
241
- end
242
-
243
- # Uses the first format in the formats array for layout lookup.
244
- def with_layout_format
245
- if formats.size == 1
246
- yield
247
- else
248
- old_formats = formats
249
- _set_detail(:formats, formats[0,1])
250
-
251
- begin
252
- yield
253
- ensure
254
- _set_detail(:formats, old_formats)
255
- end
256
- end
230
+ super(default_locale)
257
231
  end
258
232
  end
259
233
  end
@@ -1,5 +1,5 @@
1
1
  module ActionView
2
- module ModelNaming
2
+ module ModelNaming #:nodoc:
3
3
  # Converts the given object to an ActiveModel compliant one.
4
4
  def convert_to_model(object)
5
5
  object.respond_to?(:to_model) ? object.to_model : object
@@ -46,35 +46,32 @@ module ActionView #:nodoc:
46
46
  find_all(*args).first || raise(MissingTemplate.new(self, *args))
47
47
  end
48
48
 
49
- def find_file(path, prefixes = [], *args)
50
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
51
- end
52
-
53
49
  def find_all(path, prefixes = [], *args)
54
- _find_all path, prefixes, args, false
50
+ prefixes = [prefixes] if String === prefixes
51
+ prefixes.each do |prefix|
52
+ paths.each do |resolver|
53
+ templates = resolver.find_all(path, prefix, *args)
54
+ return templates unless templates.empty?
55
+ end
56
+ end
57
+ []
55
58
  end
56
59
 
57
60
  def exists?(path, prefixes, *args)
58
61
  find_all(path, prefixes, *args).any?
59
62
  end
60
63
 
61
- private
62
-
63
- def _find_all(path, prefixes, args, outside_app)
64
- prefixes = [prefixes] if String === prefixes
65
- prefixes.each do |prefix|
66
- paths.each do |resolver|
67
- if outside_app
68
- templates = resolver.find_all_anywhere(path, prefix, *args)
69
- else
70
- templates = resolver.find_all(path, prefix, *args)
71
- end
72
- return templates unless templates.empty?
73
- end
64
+ def find_all_with_query(query) # :nodoc:
65
+ paths.each do |resolver|
66
+ templates = resolver.find_all_with_query(query)
67
+ return templates unless templates.empty?
74
68
  end
69
+
75
70
  []
76
71
  end
77
72
 
73
+ private
74
+
78
75
  def typecast(paths)
79
76
  paths.map do |path|
80
77
  case path
@@ -6,6 +6,7 @@ module ActionView
6
6
  class Railtie < Rails::Railtie # :nodoc:
7
7
  config.action_view = ActiveSupport::OrderedOptions.new
8
8
  config.action_view.embed_authenticity_token_in_remote_forms = false
9
+ config.action_view.debug_missing_translation = true
9
10
 
10
11
  config.eager_load_namespaces << ActionView
11
12
 
@@ -36,14 +37,30 @@ module ActionView
36
37
  end
37
38
  end
38
39
 
40
+ initializer "action_view.collection_caching" do |app|
41
+ ActiveSupport.on_load(:action_controller) do
42
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
43
+ end
44
+ end
45
+
46
+ initializer "action_view.per_request_digest_cache" do |app|
47
+ ActiveSupport.on_load(:action_view) do
48
+ if app.config.consider_all_requests_local
49
+ app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
50
+ end
51
+ end
52
+ end
53
+
39
54
  initializer "action_view.setup_action_pack" do |app|
40
55
  ActiveSupport.on_load(:action_controller) do
41
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
56
+ ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
42
57
  end
43
58
  end
44
59
 
45
- rake_tasks do
46
- load "action_view/tasks/dependencies.rake"
60
+ rake_tasks do |app|
61
+ unless app.config.api_only
62
+ load "action_view/tasks/dependencies.rake"
63
+ end
47
64
  end
48
65
  end
49
66
  end
@@ -2,29 +2,54 @@ require 'active_support/core_ext/module'
2
2
  require 'action_view/model_naming'
3
3
 
4
4
  module ActionView
5
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
6
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
7
- # a higher logical level.
5
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
6
+ # to associate records with DOM elements.
8
7
  #
9
- # # routes
10
- # resources :posts
8
+ # Consider for example the following code that displays the body of a post:
11
9
  #
12
- # # view
13
- # <%= div_for(post) do %> <div id="post_45" class="post">
14
- # <%= post.body %> What a wonderful world!
15
- # <% end %> </div>
10
+ # <%= div_for(post) do %>
11
+ # <%= post.body %>
12
+ # <% end %>
16
13
  #
17
- # # controller
18
- # def update
19
- # post = Post.find(params[:id])
20
- # post.update(params[:post])
14
+ # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML
15
+ # is:
21
16
  #
22
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
23
- # end
17
+ # <div id="new_post" class="post">
18
+ # </div>
19
+ #
20
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
21
+ # is:
22
+ #
23
+ # <div id="post_42" class="post">
24
+ # What a wonderful world!
25
+ # </div>
26
+ #
27
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
28
+ # automatically generated, following naming conventions encapsulated by the
29
+ # RecordIdentifier methods #dom_id and #dom_class:
30
+ #
31
+ # dom_id(Post.new) # => "new_post"
32
+ # dom_class(Post.new) # => "post"
33
+ # dom_id(Post.find 42) # => "post_42"
34
+ # dom_class(Post.find 42) # => "post"
24
35
  #
25
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
26
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
27
- # same naming convention and allows you to write less code if you follow it.
36
+ # Note that these methods do not strictly require +Post+ to be a subclass of
37
+ # ActiveRecord::Base.
38
+ # Any +Post+ class will work as long as its instances respond to +to_key+
39
+ # and +model_name+, given that +model_name+ responds to +param_key+.
40
+ # For instance:
41
+ #
42
+ # class Post
43
+ # attr_accessor :to_key
44
+ #
45
+ # def model_name
46
+ # OpenStruct.new param_key: 'post'
47
+ # end
48
+ #
49
+ # def self.find(id)
50
+ # new.tap { |post| post.to_key = [id] }
51
+ # end
52
+ # end
28
53
  module RecordIdentifier
29
54
  extend self
30
55
  extend ModelNaming
@@ -78,7 +103,7 @@ module ActionView
78
103
  # make sure yourself that your dom ids are valid, in case you overwrite this method.
79
104
  def record_key_for_dom_id(record)
80
105
  key = convert_to_model(record).to_key
81
- key ? key.join('_') : key
106
+ key ? key.join(JOIN) : key
82
107
  end
83
108
  end
84
109
  end
@@ -15,7 +15,7 @@ module ActionView
15
15
  # that new object is called in turn. This abstracts the setup and rendering
16
16
  # into a separate classes for partials and templates.
17
17
  class AbstractRenderer #:nodoc:
18
- delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
18
+ delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
19
19
 
20
20
  def initialize(lookup_context)
21
21
  @lookup_context = lookup_context
@@ -1,4 +1,5 @@
1
- require 'thread_safe'
1
+ require 'action_view/renderer/partial_renderer/collection_caching'
2
+ require 'concurrent/map'
2
3
 
3
4
  module ActionView
4
5
  class PartialIteration
@@ -73,7 +74,7 @@ module ActionView
73
74
  #
74
75
  # <%= render partial: "account", locals: { user: @buyer } %>
75
76
  #
76
- # == Rendering a collection of partials
77
+ # == \Rendering a collection of partials
77
78
  #
78
79
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
79
80
  # render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -105,7 +106,7 @@ module ActionView
105
106
  # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
106
107
  # just keep domain objects, like Active Records, in there.
107
108
  #
108
- # == Rendering shared partials
109
+ # == \Rendering shared partials
109
110
  #
110
111
  # Two controllers can share a set of partials and render them like this:
111
112
  #
@@ -113,7 +114,7 @@ module ActionView
113
114
  #
114
115
  # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
115
116
  #
116
- # == Rendering objects that respond to `to_partial_path`
117
+ # == \Rendering objects that respond to `to_partial_path`
117
118
  #
118
119
  # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
119
120
  # and pick the proper path by checking `to_partial_path` method.
@@ -127,7 +128,7 @@ module ActionView
127
128
  # # <%= render partial: "posts/post", collection: @posts %>
128
129
  # <%= render partial: @posts %>
129
130
  #
130
- # == Rendering the default case
131
+ # == \Rendering the default case
131
132
  #
132
133
  # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
133
134
  # defaults of render to render partials. Examples:
@@ -147,29 +148,29 @@ module ActionView
147
148
  # # <%= render partial: "posts/post", collection: @posts %>
148
149
  # <%= render @posts %>
149
150
  #
150
- # == Rendering partials with layouts
151
+ # == \Rendering partials with layouts
151
152
  #
152
153
  # Partials can have their own layouts applied to them. These layouts are different than the ones that are
153
154
  # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
154
155
  # of users:
155
156
  #
156
- # <%# app/views/users/index.html.erb &>
157
+ # <%# app/views/users/index.html.erb %>
157
158
  # Here's the administrator:
158
159
  # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
159
160
  #
160
161
  # Here's the editor:
161
162
  # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
162
163
  #
163
- # <%# app/views/users/_user.html.erb &>
164
+ # <%# app/views/users/_user.html.erb %>
164
165
  # Name: <%= user.name %>
165
166
  #
166
- # <%# app/views/users/_administrator.html.erb &>
167
+ # <%# app/views/users/_administrator.html.erb %>
167
168
  # <div id="administrator">
168
169
  # Budget: $<%= user.budget %>
169
170
  # <%= yield %>
170
171
  # </div>
171
172
  #
172
- # <%# app/views/users/_editor.html.erb &>
173
+ # <%# app/views/users/_editor.html.erb %>
173
174
  # <div id="editor">
174
175
  # Deadline: <%= user.deadline %>
175
176
  # <%= yield %>
@@ -232,7 +233,7 @@ module ActionView
232
233
  #
233
234
  # You can also apply a layout to a block within any template:
234
235
  #
235
- # <%# app/views/users/_chief.html.erb &>
236
+ # <%# app/views/users/_chief.html.erb %>
236
237
  # <%= render(layout: "administrator", locals: { user: chief }) do %>
237
238
  # Title: <%= chief.title %>
238
239
  # <% end %>
@@ -249,13 +250,13 @@ module ActionView
249
250
  # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
250
251
  # an array to layout and treat it as an enumerable.
251
252
  #
252
- # <%# app/views/users/_user.html.erb &>
253
+ # <%# app/views/users/_user.html.erb %>
253
254
  # <div class="user">
254
255
  # Budget: $<%= user.budget %>
255
256
  # <%= yield user %>
256
257
  # </div>
257
258
  #
258
- # <%# app/views/users/index.html.erb &>
259
+ # <%# app/views/users/index.html.erb %>
259
260
  # <%= render layout: @users do |user| %>
260
261
  # Title: <%= user.title %>
261
262
  # <% end %>
@@ -264,14 +265,14 @@ module ActionView
264
265
  #
265
266
  # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
266
267
  #
267
- # <%# app/views/users/_user.html.erb &>
268
+ # <%# app/views/users/_user.html.erb %>
268
269
  # <div class="user">
269
270
  # <%= yield user, :header %>
270
271
  # Budget: $<%= user.budget %>
271
272
  # <%= yield user, :footer %>
272
273
  # </div>
273
274
  #
274
- # <%# app/views/users/index.html.erb &>
275
+ # <%# app/views/users/index.html.erb %>
275
276
  # <%= render layout: @users do |user, section| %>
276
277
  # <%- case section when :header -%>
277
278
  # Title: <%= user.title %>
@@ -280,8 +281,10 @@ module ActionView
280
281
  # <%- end -%>
281
282
  # <% end %>
282
283
  class PartialRenderer < AbstractRenderer
283
- PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
284
- h[k] = ThreadSafe::Cache.new
284
+ include CollectionCaching
285
+
286
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
287
+ h[k] = Concurrent::Map.new
285
288
  end
286
289
 
287
290
  def initialize(*)
@@ -321,8 +324,9 @@ module ActionView
321
324
  spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
322
325
  end
323
326
 
324
- result = @template ? collection_with_template : collection_without_template
325
- result.join(spacer).html_safe
327
+ cache_collection_render do
328
+ @template ? collection_with_template : collection_without_template
329
+ end.join(spacer).html_safe
326
330
  end
327
331
 
328
332
  def render_partial
@@ -334,7 +338,7 @@ module ActionView
334
338
  end
335
339
 
336
340
  object = locals[as] if object.nil? # Respect object when object is false
337
- locals[as] = object
341
+ locals[as] = object if @has_object
338
342
 
339
343
  content = @template.render(view, locals) do |*name|
340
344
  view._layout_for(*name, &block)
@@ -344,8 +348,6 @@ module ActionView
344
348
  content
345
349
  end
346
350
 
347
- private
348
-
349
351
  # Sets up instance variables needed for rendering a partial. This method
350
352
  # finds the options and details and extracts them. The method also contains
351
353
  # logic that handles the type of object passed in as the partial.
@@ -518,8 +520,8 @@ module ActionView
518
520
 
519
521
  def retrieve_variable(path, as)
520
522
  variable = as || begin
521
- base = path[-1] == "/" ? "" : File.basename(path)
522
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
523
+ base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
524
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
523
525
  $1.to_sym
524
526
  end
525
527
  if @collection
@@ -530,8 +532,7 @@ module ActionView
530
532
  end
531
533
 
532
534
  IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
533
- "make sure your partial name starts with underscore, " +
534
- "and is followed by any combination of letters, numbers and underscores."
535
+ "make sure your partial name starts with underscore."
535
536
 
536
537
  OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
537
538
  "make sure it starts with lowercase letter, " +