aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/lookup_context.rb
diff options
context:
space:
mode:
authorPiotr Sarnacki <drogus@gmail.com>2013-05-04 15:09:22 +0200
committerŁukasz Strzałkowski <lukasz.strzalkowski@gmail.com>2013-06-20 17:23:15 +0200
commit0d6e8edc2a47a4b4c6824936632bfb83850db343 (patch)
tree8829bfb94756e48e9489c4e8d22bb41df251bc81 /actionview/lib/action_view/lookup_context.rb
parent78b0934dd1bb84e8f093fb8ef95ca99b297b51cd (diff)
downloadrails-0d6e8edc2a47a4b4c6824936632bfb83850db343.tar.gz
rails-0d6e8edc2a47a4b4c6824936632bfb83850db343.tar.bz2
rails-0d6e8edc2a47a4b4c6824936632bfb83850db343.zip
Move actionpack/lib/action_view* into actionview/lib
Diffstat (limited to 'actionview/lib/action_view/lookup_context.rb')
-rw-r--r--actionview/lib/action_view/lookup_context.rb241
1 files changed, 241 insertions, 0 deletions
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
new file mode 100644
index 0000000000..f9d5b97fe3
--- /dev/null
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -0,0 +1,241 @@
+require 'thread_safe'
+require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/module/attribute_accessors'
+
+module ActionView
+ # = Action View Lookup Context
+ #
+ # LookupContext is the object responsible to hold all information required to lookup
+ # templates, i.e. view paths and details. The LookupContext is also responsible to
+ # generate a key, given to view paths, used in the resolver cache lookup. Since
+ # this key is generated just once during the request, it speeds up all cache accesses.
+ class LookupContext #:nodoc:
+ attr_accessor :prefixes, :rendered_format
+
+ mattr_accessor :fallbacks
+ @@fallbacks = FallbackFileSystemResolver.instances
+
+ mattr_accessor :registered_details
+ self.registered_details = []
+
+ def self.register_detail(name, options = {}, &block)
+ self.registered_details << name
+ initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
+
+ Accessors.send :define_method, :"default_#{name}", &block
+ Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{name}
+ @details.fetch(:#{name}, [])
+ end
+
+ def #{name}=(value)
+ value = value.present? ? Array(value) : default_#{name}
+ _set_detail(:#{name}, value) if value != @details[:#{name}]
+ end
+
+ remove_possible_method :initialize_details
+ def initialize_details(details)
+ #{initialize.join("\n")}
+ end
+ METHOD
+ end
+
+ # Holds accessors for the registered details.
+ module Accessors #:nodoc:
+ end
+
+ register_detail(:locale) do
+ locales = [I18n.locale]
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
+ locales << I18n.default_locale
+ locales.uniq!
+ locales
+ end
+ register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
+ register_detail(:handlers){ Template::Handlers.extensions }
+
+ class DetailsKey #:nodoc:
+ alias :eql? :equal?
+ alias :object_hash :hash
+
+ attr_reader :hash
+ @details_keys = ThreadSafe::Cache.new
+
+ def self.get(details)
+ @details_keys[details] ||= new
+ end
+
+ def self.clear
+ @details_keys.clear
+ end
+
+ def initialize
+ @hash = object_hash
+ end
+ end
+
+ # Add caching behavior on top of Details.
+ module DetailsCache
+ attr_accessor :cache
+
+ # 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
+ end
+
+ # Temporary skip passing the details_key forward.
+ def disable_cache
+ old_value, @cache = @cache, false
+ yield
+ ensure
+ @cache = old_value
+ end
+
+ protected
+
+ def _set_detail(key, value)
+ @details = @details.dup if @details_key
+ @details_key = nil
+ @details[key] = value
+ end
+ end
+
+ # Helpers related to template lookup using the lookup context information.
+ module ViewPaths
+ attr_reader :view_paths, :html_fallback_for_js
+
+ # Whenever setting view paths, makes a copy so we can manipulate then 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
+ alias :find_template :find
+
+ def find_all(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
+ end
+
+ def exists?(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
+ end
+ alias :template_exists? :exists?
+
+ # Add fallbacks to the view paths. Useful in cases 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
+ end
+ yield
+ ensure
+ added_resolvers.times { view_paths.pop }
+ end
+
+ protected
+
+ def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
+ 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)
+ return @details, details_key if options.empty? # most common path.
+ user_details = @details.merge(options)
+ [user_details, DetailsKey.get(user_details)]
+ end
+
+ # 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:
+ prefixes = prefixes.presence
+ parts = name.to_s.split('/')
+ parts.shift if parts.first.empty?
+ name = parts.pop
+
+ return name, prefixes || [""] if parts.empty?
+
+ parts = parts.join('/')
+ prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
+
+ return name, prefixes
+ end
+ end
+
+ include Accessors
+ include DetailsCache
+ include ViewPaths
+
+ def initialize(view_paths, details = {}, prefixes = [])
+ @details, @details_key = {}, nil
+ @skip_default_locale = false
+ @cache = true
+ @prefixes = prefixes
+ @rendered_format = nil
+
+ self.view_paths = view_paths
+ initialize_details(details)
+ end
+
+ # Override formats= to expand ["*/*"] values and automatically
+ # add :html as fallback to :js.
+ def formats=(values)
+ if values
+ values.concat(default_formats) if values.delete "*/*"
+ if values == [:js]
+ values << :html
+ @html_fallback_for_js = true
+ end
+ end
+ super(values)
+ end
+
+ # Do not use the default locale on template lookup.
+ def skip_default_locale!
+ @skip_default_locale = true
+ self.locale = nil
+ end
+
+ # Override locale to return a symbol instead of array.
+ def locale
+ @details[:locale].first
+ end
+
+ # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
+ # to original_config, it means that it's has a copy of the original I18n configuration and it's
+ # acting as proxy, which we need to skip.
+ def locale=(value)
+ if value
+ config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
+ config.locale = value
+ end
+
+ super(@skip_default_locale ? I18n.locale : default_locale)
+ end
+
+ # A method which only uses the first format in the formats array for layout lookup.
+ def with_layout_format
+ if formats.size == 1
+ yield
+ else
+ old_formats = formats
+ _set_detail(:formats, formats[0,1])
+
+ begin
+ yield
+ ensure
+ _set_detail(:formats, old_formats)
+ end
+ end
+ end
+ end
+end