diff options
Diffstat (limited to 'actionview/lib/action_view/lookup_context.rb')
-rw-r--r-- | actionview/lib/action_view/lookup_context.rb | 137 |
1 files changed, 87 insertions, 50 deletions
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 626c4b8f5e..fd3d025cbf 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,7 +1,10 @@ -require 'concurrent/map' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/attribute_accessors' -require 'action_view/template/resolver' +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/deprecation" +require "action_view/template/resolver" module ActionView # = Action View Lookup Context @@ -13,18 +16,18 @@ module ActionView # only once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes, :rendered_format + deprecate :rendered_format + deprecate :rendered_format= - mattr_accessor :fallbacks - @@fallbacks = FallbackFileSystemResolver.instances + mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances - mattr_accessor :registered_details - self.registered_details = [] + mattr_accessor :registered_details, default: [] def self.register_detail(name, &block) - self.registered_details << name + registered_details << name Accessors::DEFAULT_PROCS[name] = block - Accessors.send :define_method, :"default_#{name}", &block + Accessors.define_method(:"default_#{name}", &block) Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{name} @details.fetch(:#{name}, []) @@ -57,27 +60,36 @@ module ActionView alias :eql? :equal? @details_keys = Concurrent::Map.new + @digest_cache = Concurrent::Map.new + + def self.digest_cache(details) + @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new + end - def self.get(details) + def self.details_cache_key(details) if details[:formats] details = details.dup details[:formats] &= Template::Types.symbols end - @details_keys[details] ||= new + @details_keys[details] ||= Object.new end def self.clear + ActionView::ViewPaths.all_view_paths.each do |path_set| + path_set.each(&:clear_cache) + end + ActionView::LookupContext.fallbacks.each(&:clear_cache) + @view_context_class = nil @details_keys.clear + @digest_cache.clear end def self.digest_caches - @details_keys.values.map(&:digest_cache) + @digest_cache.values end - attr_reader :digest_cache - - def initialize - @digest_cache = Concurrent::Map.new + def self.view_context_class(klass) + @view_context_class ||= klass.with_empty_template_cache end end @@ -88,7 +100,7 @@ module ActionView # Calculate the details key. Remove the handlers from calculation to improve performance # since the user cannot modify it explicitly. def details_key #:nodoc: - @details_key ||= DetailsKey.get(@details) if @cache + @details_key ||= DetailsKey.details_cache_key(@details) if @cache end # Temporary skip passing the details_key forward. @@ -99,10 +111,11 @@ module ActionView @cache = old_value end - protected + private - def _set_detail(key, value) - @details = @details.dup if @details_key + def _set_detail(key, value) # :doc: + @details = @details.dup if @digest_cache || @details_key + @digest_cache = nil @details_key = nil @details[key] = value end @@ -112,12 +125,6 @@ module ActionView module ViewPaths attr_reader :view_paths, :html_fallback_for_js - # Whenever setting view paths, makes a copy so that we can manipulate them in - # instance objects as we wish. - def view_paths=(paths) - @view_paths = ActionView::PathSet.new(Array(paths)) - end - def find(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options)) end @@ -144,32 +151,47 @@ module ActionView # Adds fallbacks to the view paths. Useful in cases when you are rendering # a :file. def with_fallbacks - added_resolvers = 0 - self.class.fallbacks.each do |resolver| - next if view_paths.include?(resolver) - view_paths.push(resolver) - added_resolvers += 1 + view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq) + + if block_given? + ActiveSupport::Deprecation.warn <<~eowarn.squish + Calling `with_fallbacks` with a block is deprecated. Call methods on + the lookup context returned by `with_fallbacks` instead. + eowarn + + begin + _view_paths = @view_paths + @view_paths = view_paths + yield + ensure + @view_paths = _view_paths + end + else + ActionView::LookupContext.new(view_paths, @details, @prefixes) end - yield - ensure - added_resolvers.times { view_paths.pop } end - protected + private + + # Whenever setting view paths, makes a copy so that we can manipulate them in + # instance objects as we wish. + def build_view_paths(paths) + ActionView::PathSet.new(Array(paths)) + end - def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc: + def args_for_lookup(name, prefixes, partial, keys, details_options) name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for(details_options) [name, prefixes, partial || false, details, details_key, keys] end # Compute details hash and key according to user options (e.g. passed from #render). - def detail_args_for(options) + def detail_args_for(options) # :doc: return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) if @cache - details_key = DetailsKey.get(user_details) + details_key = DetailsKey.details_cache_key(user_details) else details_key = nil end @@ -177,13 +199,13 @@ module ActionView [user_details, details_key] end - def args_for_any(name, prefixes, partial) # :nodoc: + def args_for_any(name, prefixes, partial) name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for_any [name, prefixes, partial || false, details, details_key] end - def detail_args_for_any # :nodoc: + def detail_args_for_any @detail_args_for_any ||= begin details = {} @@ -196,7 +218,7 @@ module ActionView end if @cache - [details, DetailsKey.get(details)] + [details, DetailsKey.details_cache_key(details)] else [details, nil] end @@ -206,15 +228,15 @@ module ActionView # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. - def normalize_name(name, prefixes) #:nodoc: + def normalize_name(name, prefixes) prefixes = prefixes.presence - parts = name.to_s.split('/'.freeze) + parts = name.to_s.split("/") parts.shift if parts.first.empty? - name = parts.pop + name = parts.pop return name, prefixes || [""] if parts.empty? - parts = parts.join('/'.freeze) + parts = parts.join("/") prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts] return name, prefixes @@ -227,16 +249,23 @@ module ActionView def initialize(view_paths, details = {}, prefixes = []) @details_key = nil + @digest_cache = nil @cache = true @prefixes = prefixes - @rendered_format = nil @details = initialize_details({}, details) - self.view_paths = view_paths + @view_paths = build_view_paths(view_paths) end def digest_cache - details_key.digest_cache + @digest_cache ||= DetailsKey.digest_cache(@details) + end + + def with_prepended_formats(formats) + details = @details.dup + details[:formats] = formats + + self.class.new(@view_paths, details, @prefixes) end def initialize_details(target, details) @@ -251,7 +280,15 @@ module ActionView # add :html as fallback to :js. def formats=(values) if values - values.concat(default_formats) if values.delete "*/*".freeze + values = values.dup + values.concat(default_formats) if values.delete "*/*" + values.uniq! + + invalid_values = (values - Template::Types.symbols) + unless invalid_values.empty? + raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}" + end + if values == [:js] values << :html @html_fallback_for_js = true |