lookup_context.rb 4.4 KB
Newer Older
1
require 'active_support/core_ext/object/try'
2
require 'active_support/core_ext/object/blank'
3

4 5 6 7 8 9
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:
10 11 12
    mattr_accessor :fallbacks
    @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]

13
    mattr_accessor :registered_details
J
José Valim 已提交
14
    self.registered_details = []
15 16

    def self.register_detail(name, options = {})
J
José Valim 已提交
17 18 19
      self.registered_details << name

      Setters.send :define_method, :"#{name}=" do |value|
20
        value = Array(value.presence || yield)
21
        value |= [nil] unless options[:allow_nil] == false
J
José Valim 已提交
22 23 24 25 26

        unless value == @details[name]
          @details_key, @details = nil, @details.merge(name => value)
          @details.freeze
        end
27 28 29
      end
    end

J
José Valim 已提交
30 31 32 33
    # Holds raw setters for the registered details.
    module Setters #:nodoc:
    end

34
    register_detail(:formats) { Mime::SET.symbols }
35
    register_detail(:locale)  { [I18n.locale] }
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
    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 = {})
J
José Valim 已提交
53
      @details, @details_key = {}, nil
54
      self.view_paths = view_paths
55
      self.details = details
56 57
    end

58 59
    module ViewPaths
      attr_reader :view_paths
60

61 62 63 64 65
      # 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
66

J
José Valim 已提交
67
      def find(name, prefix = nil, partial = false)
J
José Valim 已提交
68
        @view_paths.find(name, prefix, partial, details, details_key)
69
      end
70

71
      def find_all(name, prefix = nil, partial = false)
J
José Valim 已提交
72
        @view_paths.find_all(name, prefix, partial, details, details_key)
73 74
      end

J
José Valim 已提交
75
      def exists?(name, prefix = nil, partial = false)
J
José Valim 已提交
76
        @view_paths.exists?(name, prefix, partial, details, details_key)
77
      end
78

J
José Valim 已提交
79
      # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
80 81 82 83 84 85
      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
86
        end
87 88 89
        yield
      ensure
        added_resolvers.times { view_paths.pop }
90 91 92
      end
    end

93
    module Details
94
      attr_reader :details
95

J
José Valim 已提交
96 97
      def details=(given_details)
        registered_details.each { |key| send(:"#{key}=", given_details[key]) }
98
      end
99

100
      def details_key
101
        @details_key ||= DetailsKey.get(@details)
102 103 104 105
      end

      # Shortcut to read formats from details.
      def formats
106
        @details[:formats].compact
107 108
      end

J
José Valim 已提交
109 110 111 112
      # Overload formats= to reject [:"*/*"] values.
      def formats=(value, freeze=true)
        value = nil if value == [:"*/*"]
        super(value)
113 114
      end

115 116 117 118 119
      # Shortcut to read locale.
      def locale
        I18n.locale
      end

J
José Valim 已提交
120 121 122
      # 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.
123
      def locale=(value)
J
José Valim 已提交
124 125 126 127
        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)
128 129
      end

130 131 132 133
      # 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)
134
        old_details  = @details
135 136 137 138 139 140
        self.details = old_details.merge(new_details)

        if block_given?
          begin
            yield
          ensure
J
José Valim 已提交
141
            @details = old_details
142 143 144
          end
        end
      end
145
    end
146

J
José Valim 已提交
147
    include Setters
148 149
    include Details
    include ViewPaths
150 151
  end
end