Move details to lookup_context and make resolvers use the cache key.
This commit is contained in:
parent
67a6725bf9
commit
bdf5096816
@ -101,19 +101,9 @@ def _normalize_options(options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
options[:template] ||= (options[:action] || action_name).to_s
|
options[:template] ||= (options[:action] || action_name).to_s
|
||||||
|
|
||||||
details = _normalize_details(options)
|
|
||||||
lookup_context.update_details(details)
|
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def _normalize_details(options)
|
|
||||||
details = {}
|
|
||||||
details[:formats] = Array(options[:format]) if options[:format]
|
|
||||||
details[:locale] = Array(options[:locale]) if options[:locale]
|
|
||||||
details
|
|
||||||
end
|
|
||||||
|
|
||||||
def _process_options(options)
|
def _process_options(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,14 +1,30 @@
|
|||||||
|
require 'active_support/core_ext/object/try'
|
||||||
|
|
||||||
module ActionView
|
module ActionView
|
||||||
# LookupContext is the object responsible to hold all information required to lookup
|
# 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
|
# 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
|
# 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.
|
# this key is generated just once during the request, it speeds up all cache accesses.
|
||||||
class LookupContext #:nodoc:
|
class LookupContext #:nodoc:
|
||||||
attr_reader :details, :view_paths
|
|
||||||
|
|
||||||
mattr_accessor :fallbacks
|
mattr_accessor :fallbacks
|
||||||
@@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]
|
@@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]
|
||||||
|
|
||||||
|
mattr_accessor :registered_details
|
||||||
|
self.registered_details = {}
|
||||||
|
|
||||||
|
def self.register_detail(name, options = {})
|
||||||
|
registered_details[name] = lambda do |value|
|
||||||
|
value = (value.blank? || options[:accessible] == false) ?
|
||||||
|
Array(yield) : Array(value)
|
||||||
|
value |= [nil] unless options[:allow_nil] == false
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
register_detail(:formats) { Mime::SET.symbols }
|
||||||
|
register_detail(:locale, :accessible => false) { [I18n.locale] }
|
||||||
|
register_detail(:handlers, :accessible => false) { Template::Handlers.extensions }
|
||||||
|
|
||||||
class DetailsKey #:nodoc:
|
class DetailsKey #:nodoc:
|
||||||
attr_reader :details
|
attr_reader :details
|
||||||
alias :eql? :equal?
|
alias :eql? :equal?
|
||||||
@ -22,75 +38,108 @@ def self.get(details)
|
|||||||
def initialize(details)
|
def initialize(details)
|
||||||
@details, @hash = details, details.hash
|
@details, @hash = details, details.hash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def outdated?(details)
|
||||||
|
@details != details
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(view_paths, details = {})
|
def initialize(view_paths, details = {})
|
||||||
@details, @details_key = details, nil
|
|
||||||
self.view_paths = view_paths
|
self.view_paths = view_paths
|
||||||
|
self.details = details
|
||||||
|
@details_key = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut to read formats from details.
|
module ViewPaths
|
||||||
def formats
|
attr_reader :view_paths
|
||||||
@details[:formats]
|
|
||||||
|
# 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::Base.process_view_paths(paths)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_template(name, prefix = nil, partial = false)
|
||||||
|
key = details_key
|
||||||
|
@view_paths.find(name, key.details, prefix, partial || false, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_exists?(name, prefix = nil, partial = false)
|
||||||
|
key = details_key
|
||||||
|
@view_paths.exists?(name, key.details, prefix, partial || false, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut to set formats in details.
|
module Details
|
||||||
def formats=(value)
|
def details
|
||||||
self.details = @details.merge(:formats => Array(value))
|
@details = normalize_details(@details)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Whenever setting view paths, makes a copy so we can manipulate then in
|
def details=(new_details)
|
||||||
# instance objects as we wish.
|
@details = new_details
|
||||||
def view_paths=(paths)
|
details
|
||||||
@view_paths = ActionView::Base.process_view_paths(paths)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Setter for details. Everything this method is invoked, we need to nullify
|
# TODO This is too expensive. Revisit this.
|
||||||
# the details key if it changed.
|
def details_key
|
||||||
def details=(details)
|
latest_details = self.details
|
||||||
@details = details
|
@details_key = nil if @details_key.try(:outdated?, latest_details)
|
||||||
@details_key = nil if @details_key && @details_key.details != details
|
@details_key ||= DetailsKey.get(latest_details)
|
||||||
end
|
end
|
||||||
|
|
||||||
def details_key
|
# Shortcut to read formats from details.
|
||||||
@details_key ||= DetailsKey.get(details) unless details.empty?
|
def formats
|
||||||
end
|
self.details[:formats]
|
||||||
|
end
|
||||||
|
|
||||||
# Update the details keys by merging the given hash into the current
|
# Shortcut to set formats in details.
|
||||||
# details hash. If a block is given, the details are modified just during
|
def formats=(value)
|
||||||
# the execution of the block and reverted to the previous value after.
|
self.details = @details.merge(:formats => value)
|
||||||
def update_details(new_details)
|
end
|
||||||
old_details = self.details
|
|
||||||
self.details = old_details.merge(new_details)
|
|
||||||
|
|
||||||
if block_given?
|
# Update the details keys by merging the given hash into the current
|
||||||
begin
|
# details hash. If a block is given, the details are modified just during
|
||||||
yield
|
# the execution of the block and reverted to the previous value after.
|
||||||
ensure
|
def update_details(new_details)
|
||||||
self.details = old_details
|
old_details = self.details
|
||||||
|
self.details = old_details.merge(new_details)
|
||||||
|
|
||||||
|
if block_given?
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
self.details = old_details
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Added fallbacks to the view paths. Useful in cases you are rendering a file.
|
protected
|
||||||
def with_fallbacks
|
|
||||||
added_resolvers = 0
|
def normalize_details(details)
|
||||||
self.class.fallbacks.each do |resolver|
|
details = details.dup
|
||||||
next if view_paths.include?(resolver)
|
# TODO: Refactor this concern out of the resolver
|
||||||
view_paths.push(resolver)
|
details.delete(:formats) if details[:formats] == [:"*/*"]
|
||||||
added_resolvers += 1
|
self.class.registered_details.each do |k, v|
|
||||||
|
details[k] = v.call(details[k])
|
||||||
|
end
|
||||||
|
details
|
||||||
end
|
end
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
added_resolvers.times { view_paths.pop }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_template(name, prefix = nil, partial = false)
|
include Details
|
||||||
@view_paths.find(name, details, prefix, partial || false, details_key)
|
include ViewPaths
|
||||||
end
|
|
||||||
|
|
||||||
def template_exists?(name, prefix = nil, partial = false)
|
|
||||||
@view_paths.exists?(name, details, prefix, partial || false, details_key)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -11,7 +11,7 @@ def #{method}(*args)
|
|||||||
|
|
||||||
def find(path, details = {}, prefix = nil, partial = false, key=nil)
|
def find(path, details = {}, prefix = nil, partial = false, key=nil)
|
||||||
each do |resolver|
|
each do |resolver|
|
||||||
if template = resolver.find(path, details, prefix, partial)
|
if template = resolver.find(path, details, prefix, partial, key)
|
||||||
return template
|
return template
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -21,7 +21,7 @@ def find(path, details = {}, prefix = nil, partial = false, key=nil)
|
|||||||
|
|
||||||
def exists?(path, details = {}, prefix = nil, partial = false, key=nil)
|
def exists?(path, details = {}, prefix = nil, partial = false, key=nil)
|
||||||
each do |resolver|
|
each do |resolver|
|
||||||
if resolver.find(path, details, prefix, partial)
|
if resolver.find(path, details, prefix, partial, key)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,24 +5,9 @@
|
|||||||
|
|
||||||
module ActionView
|
module ActionView
|
||||||
class Resolver
|
class Resolver
|
||||||
|
|
||||||
class_inheritable_accessor(:registered_details)
|
|
||||||
self.registered_details = {}
|
|
||||||
|
|
||||||
def self.register_detail(name, options = {})
|
|
||||||
registered_details[name] = lambda do |val|
|
|
||||||
val = Array.wrap(val || yield)
|
|
||||||
val |= [nil] unless options[:allow_nil] == false
|
|
||||||
val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
register_detail(:locale) { [I18n.locale] }
|
|
||||||
register_detail(:formats) { Mime::SET.symbols }
|
|
||||||
register_detail(:handlers) { Template::Handlers.extensions }
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@cached = {}
|
@cached = Hash.new { |h1,k1| h1[k1] =
|
||||||
|
Hash.new { |h2,k2| h2[k2] = Hash.new { |h3, k3| h3[k3] = {} } } }
|
||||||
end
|
end
|
||||||
|
|
||||||
def find(*args)
|
def find(*args)
|
||||||
@ -30,11 +15,10 @@ def find(*args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Normalizes the arguments and passes it on to find_template.
|
# Normalizes the arguments and passes it on to find_template.
|
||||||
def find_all(name, details = {}, prefix = nil, partial = nil)
|
def find_all(name, details = {}, prefix = nil, partial = nil, key=nil)
|
||||||
details = normalize_details(details)
|
|
||||||
name, prefix = normalize_name(name, prefix)
|
name, prefix = normalize_name(name, prefix)
|
||||||
|
|
||||||
cached([name, details, prefix, partial]) do
|
cached(key, prefix, name, partial) do
|
||||||
find_templates(name, details, prefix, partial)
|
find_templates(name, details, prefix, partial)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -52,16 +36,6 @@ def find_templates(name, details, prefix, partial)
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_details(details)
|
|
||||||
details = details.dup
|
|
||||||
# TODO: Refactor this concern out of the resolver
|
|
||||||
details.delete(:formats) if details[:formats] == [:"*/*"]
|
|
||||||
registered_details.each do |k, v|
|
|
||||||
details[k] = v.call(details[k])
|
|
||||||
end
|
|
||||||
details
|
|
||||||
end
|
|
||||||
|
|
||||||
# Support legacy foo.erb names even though we now ignore .erb
|
# Support legacy foo.erb names even though we now ignore .erb
|
||||||
# as well as incorrectly putting part of the path in the template
|
# as well as incorrectly putting part of the path in the template
|
||||||
# name instead of the prefix.
|
# name instead of the prefix.
|
||||||
@ -73,10 +47,14 @@ def normalize_name(name, prefix)
|
|||||||
return parts.pop, [prefix, *parts].compact.join("/")
|
return parts.pop, [prefix, *parts].compact.join("/")
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached(key)
|
def cached(key, prefix, name, partial)
|
||||||
return yield unless caching?
|
return yield unless key && caching?
|
||||||
return @cached[key] if @cached.key?(key)
|
scope = @cached[key][prefix][name]
|
||||||
@cached[key] = yield
|
if scope.key?(partial)
|
||||||
|
scope[partial]
|
||||||
|
else
|
||||||
|
scope[partial] = yield
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,11 +16,7 @@ def _prefix
|
|||||||
"renderer/string.erb" => "With String",
|
"renderer/string.erb" => "With String",
|
||||||
"renderer/symbol.erb" => "With Symbol",
|
"renderer/symbol.erb" => "With Symbol",
|
||||||
"string/with_path.erb" => "With String With Path",
|
"string/with_path.erb" => "With String With Path",
|
||||||
"some/file.erb" => "With File",
|
"some/file.erb" => "With File"
|
||||||
"with_format.html.erb" => "With html format",
|
|
||||||
"with_format.xml.erb" => "With xml format",
|
|
||||||
"with_locale.en.erb" => "With en locale",
|
|
||||||
"with_locale.pl.erb" => "With pl locale"
|
|
||||||
)]
|
)]
|
||||||
|
|
||||||
def template
|
def template
|
||||||
@ -54,22 +50,6 @@ def string_with_path
|
|||||||
def symbol
|
def symbol
|
||||||
render :symbol
|
render :symbol
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_html_format
|
|
||||||
render :template => "with_format", :format => :html
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_xml_format
|
|
||||||
render :template => "with_format", :format => :xml
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_en_locale
|
|
||||||
render :template => "with_locale"
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_pl_locale
|
|
||||||
render :template => "with_locale", :locale => :pl
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestRenderer < ActiveSupport::TestCase
|
class TestRenderer < ActiveSupport::TestCase
|
||||||
@ -117,26 +97,6 @@ def test_render_string_with_path
|
|||||||
@controller.process(:string_with_path)
|
@controller.process(:string_with_path)
|
||||||
assert_equal "With String With Path", @controller.response_body
|
assert_equal "With String With Path", @controller.response_body
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_with_html_format
|
|
||||||
@controller.process(:with_html_format)
|
|
||||||
assert_equal "With html format", @controller.response_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_with_xml_format
|
|
||||||
@controller.process(:with_xml_format)
|
|
||||||
assert_equal "With xml format", @controller.response_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_with_en_locale
|
|
||||||
@controller.process(:with_en_locale)
|
|
||||||
assert_equal "With en locale", @controller.response_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_with_pl_locale
|
|
||||||
@controller.process(:with_pl_locale)
|
|
||||||
assert_equal "With pl locale", @controller.response_body
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user