From 44ebab96da0ab47cc45c64a6efdd2cbb80f9d042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Mar 2010 15:19:03 +0100 Subject: Rename Template::Lookup to LookupContext. --- actionpack/lib/action_view/lookup_context.rb | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 actionpack/lib/action_view/lookup_context.rb (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb new file mode 100644 index 0000000000..82aebe1678 --- /dev/null +++ b/actionpack/lib/action_view/lookup_context.rb @@ -0,0 +1,80 @@ +module ActionView + # 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_reader :details, :view_paths + + class DetailsKey #:nodoc: + attr_reader :details + alias :eql? :equal? + + @details_keys = Hash.new + + def self.get(details) + @details_keys[details] ||= new(details) + end + + def initialize(details) + @details, @hash = details, details.hash + end + end + + def initialize(view_paths, details = {}) + @details, @details_key = details, nil + self.view_paths = view_paths + end + + # Shortcut to read formats from details. + def formats + @details[:formats] + end + + # Shortcut to set formats in details. + def formats=(value) + self.details = @details.merge(:formats => Array(value)) + end + + # 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 + + # Setter for details. Everything this method is invoked, we need to nullify + # the details key if it changed. + def details=(details) + @details = details + @details_key = nil if @details_key && @details_key.details != details + end + + def details_key + @details_key ||= DetailsKey.get(details) unless details.empty? + end + + # Update the details keys by merging the given hash into the current + # details hash. If a block is given, the details are modified just during + # the execution of the block and reverted to the previous value after. + def update_details(new_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 + + def find_template(name, prefix = nil, partial = false) + @view_paths.find(name, details, prefix, partial || false, details_key) + end + + def template_exists?(name, prefix = nil, partial = false) + @view_paths.exists?(name, details, prefix, partial || false, details_key) + end + end +end \ No newline at end of file -- cgit v1.2.3 From 68cda695da27f57cae682d160a13dab4dacb1ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Mar 2010 16:32:40 +0100 Subject: Speed up performance in resolvers by adding fallbacks just when required. --- actionpack/lib/action_view/lookup_context.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 82aebe1678..e259a78c5c 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -6,6 +6,9 @@ module ActionView class LookupContext #:nodoc: attr_reader :details, :view_paths + mattr_accessor :fallbacks + @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")] + class DetailsKey #:nodoc: attr_reader :details alias :eql? :equal? @@ -69,6 +72,19 @@ module ActionView end end + # Added 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 + def find_template(name, prefix = nil, partial = false) @view_paths.find(name, details, prefix, partial || false, details_key) end -- cgit v1.2.3 From bdf5096816d03f2bdaefd20a07a0fa562543549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Mar 2010 20:39:15 +0100 Subject: Move details to lookup_context and make resolvers use the cache key. --- actionpack/lib/action_view/lookup_context.rb | 157 ++++++++++++++++++--------- 1 file changed, 103 insertions(+), 54 deletions(-) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index e259a78c5c..231882185f 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,14 +1,30 @@ +require 'active_support/core_ext/object/try' + module ActionView # 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_reader :details, :view_paths - mattr_accessor :fallbacks @@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: attr_reader :details alias :eql? :equal? @@ -22,75 +38,108 @@ module ActionView def initialize(details) @details, @hash = details, details.hash end + + def outdated?(details) + @details != details + end end def initialize(view_paths, details = {}) - @details, @details_key = details, nil self.view_paths = view_paths + self.details = details + @details_key = nil end - # Shortcut to read formats from details. - def formats - @details[:formats] - end - - # Shortcut to set formats in details. - def formats=(value) - self.details = @details.merge(:formats => Array(value)) - end + module ViewPaths + attr_reader :view_paths - # 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 + # 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 - # Setter for details. Everything this method is invoked, we need to nullify - # the details key if it changed. - def details=(details) - @details = details - @details_key = nil if @details_key && @details_key.details != details - end + def find_template(name, prefix = nil, partial = false) + key = details_key + @view_paths.find(name, key.details, prefix, partial || false, key) + end - def details_key - @details_key ||= DetailsKey.get(details) unless details.empty? - end + def template_exists?(name, prefix = nil, partial = false) + key = details_key + @view_paths.exists?(name, key.details, prefix, partial || false, key) + end - # Update the details keys by merging the given hash into the current - # details hash. If a block is given, the details are modified just during - # the execution of the block and reverted to the previous value after. - def update_details(new_details) - old_details = self.details - self.details = old_details.merge(new_details) - - if block_given? - begin - yield - ensure - self.details = old_details + # 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 - # Added 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 + module Details + def details + @details = normalize_details(@details) end - yield - ensure - added_resolvers.times { view_paths.pop } - end - def find_template(name, prefix = nil, partial = false) - @view_paths.find(name, details, prefix, partial || false, details_key) - end + def details=(new_details) + @details = new_details + details + end - def template_exists?(name, prefix = nil, partial = false) - @view_paths.exists?(name, details, prefix, partial || false, details_key) + # TODO This is too expensive. Revisit this. + def details_key + latest_details = self.details + @details_key = nil if @details_key.try(:outdated?, latest_details) + @details_key ||= DetailsKey.get(latest_details) + end + + # Shortcut to read formats from details. + def formats + self.details[:formats] + end + + # Shortcut to set formats in details. + def formats=(value) + self.details = @details.merge(:formats => value) + end + + # Update the details keys by merging the given hash into the current + # details hash. If a block is given, the details are modified just during + # the execution of the block and reverted to the previous value after. + def update_details(new_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 + + protected + + def normalize_details(details) + details = details.dup + # TODO: Refactor this concern out of the resolver + details.delete(:formats) if details[:formats] == [:"*/*"] + self.class.registered_details.each do |k, v| + details[k] = v.call(details[k]) + end + details + end end + + include Details + include ViewPaths end end \ No newline at end of file -- cgit v1.2.3 From 36eb1a686c831d5a14998bb9ac7cc60efa363373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Mar 2010 20:57:33 +0100 Subject: Bring AM up to date with new rendering stack. --- actionpack/lib/action_view/lookup_context.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 231882185f..608f1ef818 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -64,6 +64,11 @@ module ActionView @view_paths.find(name, key.details, prefix, partial || false, key) end + def find_all(name, prefix = nil, partial = false) + key = details_key + @view_paths.find_all(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) -- cgit v1.2.3 From 8f082ff4217175f52234f2223658619a9c923afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Mar 2010 23:13:24 +0100 Subject: Clean LookupContext API. --- actionpack/lib/action_view/lookup_context.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 608f1ef818..4a4dc0d06c 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -59,19 +59,19 @@ module ActionView @view_paths = ActionView::Base.process_view_paths(paths) end - def find_template(name, prefix = nil, partial = false) + def find(name, prefix = nil, partial = false) key = details_key - @view_paths.find(name, key.details, prefix, partial || false, key) + @view_paths.find(name, prefix, partial || false, key.details, key) end def find_all(name, prefix = nil, partial = false) key = details_key - @view_paths.find_all(name, key.details, prefix, partial || false, key) + @view_paths.find_all(name, prefix, partial || false, key.details, key) end - def template_exists?(name, prefix = nil, partial = false) + def exists?(name, prefix = nil, partial = false) key = details_key - @view_paths.exists?(name, key.details, prefix, partial || false, key) + @view_paths.exists?(name, prefix, partial || false, key.details, key) end # Add fallbacks to the view paths. Useful in cases you are rendering a file. -- cgit v1.2.3 From 07cf49aadf3195db6ddefc58932efc88a6704a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 10 Mar 2010 22:11:48 +0100 Subject: Optimize and clean up how details key get expired. --- actionpack/lib/action_view/lookup_context.rb | 57 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 4a4dc0d06c..91885c7370 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/blank' module ActionView # LookupContext is the object responsible to hold all information required to lookup @@ -14,16 +15,14 @@ module ActionView def self.register_detail(name, options = {}) registered_details[name] = lambda do |value| - value = (value.blank? || options[:accessible] == false) ? - Array(yield) : Array(value) + value = Array(value.presence || yield) 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 } + register_detail(:locale) { [I18n.locale] } class DetailsKey #:nodoc: attr_reader :details @@ -38,16 +37,12 @@ module ActionView def initialize(details) @details, @hash = details, details.hash end - - def outdated?(details) - @details != details - end end def initialize(view_paths, details = {}) + @details_key = nil self.view_paths = view_paths self.details = details - @details_key = nil end module ViewPaths @@ -60,18 +55,15 @@ module ActionView end def find(name, prefix = nil, partial = false) - key = details_key - @view_paths.find(name, prefix, partial || false, key.details, key) + @view_paths.find(name, prefix, partial || false, details, details_key) end def find_all(name, prefix = nil, partial = false) - key = details_key - @view_paths.find_all(name, prefix, partial || false, key.details, key) + @view_paths.find_all(name, prefix, partial || false, details, details_key) end def exists?(name, prefix = nil, partial = false) - key = details_key - @view_paths.exists?(name, prefix, partial || false, key.details, key) + @view_paths.exists?(name, prefix, partial || false, details, details_key) end # Add fallbacks to the view paths. Useful in cases you are rendering a file. @@ -89,25 +81,20 @@ module ActionView end module Details - def details - @details = normalize_details(@details) - end + attr_reader :details - def details=(new_details) - @details = new_details - details + def details=(details) + @details = normalize_details(details) + @details_key = nil if @details_key && @details_key.details != @details end - # TODO This is too expensive. Revisit this. def details_key - latest_details = self.details - @details_key = nil if @details_key.try(:outdated?, latest_details) - @details_key ||= DetailsKey.get(latest_details) + @details_key ||= DetailsKey.get(@details) end # Shortcut to read formats from details. def formats - self.details[:formats] + @details[:formats].compact end # Shortcut to set formats in details. @@ -115,11 +102,25 @@ module ActionView self.details = @details.merge(:formats => value) end + # Shortcut to read locale. + def locale + I18n.locale + end + + # Shortcut to set locale in details and I18n. + def locale=(value) + I18n.locale = value + + unless I18n.config.respond_to?(:lookup_context) + self.details = @details.merge(:locale => value) + end + end + # Update the details keys by merging the given hash into the current # details hash. If a block is given, the details are modified just during # the execution of the block and reverted to the previous value after. def update_details(new_details) - old_details = self.details + old_details = @details self.details = old_details.merge(new_details) if block_given? @@ -140,7 +141,7 @@ module ActionView self.class.registered_details.each do |k, v| details[k] = v.call(details[k]) end - details + details.freeze end end -- cgit v1.2.3 From 6c027443b044276fd2a85b387b67a8dee940b320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 11 Mar 2010 12:45:05 +0100 Subject: Add tests for lookup context. --- actionpack/lib/action_view/lookup_context.rb | 66 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 91885c7370..0bb73b590d 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -11,16 +11,26 @@ module ActionView @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")] mattr_accessor :registered_details - self.registered_details = {} + self.registered_details = [] def self.register_detail(name, options = {}) - registered_details[name] = lambda do |value| + self.registered_details << name + + Setters.send :define_method, :"#{name}=" do |value| value = Array(value.presence || yield) value |= [nil] unless options[:allow_nil] == false - value + + unless value == @details[name] + @details_key, @details = nil, @details.merge(name => value) + @details.freeze + end end end + # Holds raw setters for the registered details. + module Setters #:nodoc: + end + register_detail(:formats) { Mime::SET.symbols } register_detail(:locale) { [I18n.locale] } @@ -40,7 +50,7 @@ module ActionView end def initialize(view_paths, details = {}) - @details_key = nil + @details, @details_key = {}, nil self.view_paths = view_paths self.details = details end @@ -55,18 +65,18 @@ module ActionView end def find(name, prefix = nil, partial = false) - @view_paths.find(name, prefix, partial || false, details, details_key) + @view_paths.find(name, prefix, partial, details, details_key) end def find_all(name, prefix = nil, partial = false) - @view_paths.find_all(name, prefix, partial || false, details, details_key) + @view_paths.find_all(name, prefix, partial, details, details_key) end def exists?(name, prefix = nil, partial = false) - @view_paths.exists?(name, prefix, partial || false, details, details_key) + @view_paths.exists?(name, prefix, partial, details, details_key) end - # Add fallbacks to the view paths. Useful in cases you are rendering a file. + # 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| @@ -83,9 +93,8 @@ module ActionView module Details attr_reader :details - def details=(details) - @details = normalize_details(details) - @details_key = nil if @details_key && @details_key.details != @details + def details=(given_details) + registered_details.each { |key| send(:"#{key}=", given_details[key]) } end def details_key @@ -97,9 +106,10 @@ module ActionView @details[:formats].compact end - # Shortcut to set formats in details. - def formats=(value) - self.details = @details.merge(:formats => value) + # Overload formats= to reject [:"*/*"] values. + def formats=(value, freeze=true) + value = nil if value == [:"*/*"] + super(value) end # Shortcut to read locale. @@ -107,13 +117,14 @@ module ActionView I18n.locale end - # Shortcut to set locale in details and I18n. + # Overload locale= to also set the I18n.locale. If the current I18n.config object responds + # to i18n_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) - I18n.locale = value - - unless I18n.config.respond_to?(:lookup_context) - self.details = @details.merge(:locale => value) - end + value = value.first if value.is_a?(Array) + config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config + config.locale = value if value + super(I18n.locale) end # Update the details keys by merging the given hash into the current @@ -127,24 +138,13 @@ module ActionView begin yield ensure - self.details = old_details + @details = old_details end end end - - protected - - def normalize_details(details) - details = details.dup - # TODO: Refactor this concern out of the resolver - details.delete(:formats) if details[:formats] == [:"*/*"] - self.class.registered_details.each do |k, v| - details[k] = v.call(details[k]) - end - details.freeze - end end + include Setters include Details include ViewPaths end -- cgit v1.2.3 From 839362fa07de3f7bdf1fc1a361ff456cd02efc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 12 Mar 2010 11:50:45 +0100 Subject: Make all AP tests pass for Ruby 1.9.1. --- actionpack/lib/action_view/lookup_context.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'actionpack/lib/action_view/lookup_context.rb') diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 0bb73b590d..27ee8b23c9 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -13,18 +13,21 @@ module ActionView mattr_accessor :registered_details self.registered_details = [] - def self.register_detail(name, options = {}) + def self.register_detail(name, options = {}, &block) self.registered_details << name - Setters.send :define_method, :"#{name}=" do |value| - value = Array(value.presence || yield) - value |= [nil] unless options[:allow_nil] == false + Setters.send :define_method, :"_#{name}_defaults", &block + Setters.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name}=(value) + value = Array(value.presence || _#{name}_defaults) + #{"value << nil unless value.include?(nil)" unless options[:allow_nil] == false} - unless value == @details[name] - @details_key, @details = nil, @details.merge(name => value) - @details.freeze + unless value == @details[:#{name}] + @details_key, @details = nil, @details.merge(:#{name} => value) + @details.freeze + end end - end + METHOD end # Holds raw setters for the registered details. -- cgit v1.2.3