aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb
blob: 903c79fdc61cea737cf6fe1952ed95285de4337c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
module Rack::Mount
  module CodeGeneration #:nodoc:
    def _expired_recognize(env) #:nodoc:
      raise 'route set not finalized'
    end

    def rehash
      super
      optimize_recognize!
    end

    private
      def expire!
        if @optimized_recognize_defined
          remove_metaclass_method :recognize

          class << self
            alias_method :recognize, :_expired_recognize
          end

          @optimized_recognize_defined = false
        end

        super
      end

      def optimize_container_iterator(container)
        body = []

        container.each_with_index { |route, i|
          body << "route = self[#{i}]"
          body << 'matches = {}'
          body << 'params = route.defaults.dup'

          conditions = []
          route.conditions.each do |method, condition|
            b = []
            if condition.is_a?(Regexp)
              b << "if m = obj.#{method}.match(#{condition.inspect})"
              b << "matches[:#{method}] = m"
              if (named_captures = route.named_captures[method]) && named_captures.any?
                b << 'captures = m.captures'
                b << 'p = nil'
                b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
              end
            else
              b << "if m = obj.#{method} == route.conditions[:#{method}]"
            end
            b << 'true'
            b << 'end'
            conditions << "(#{b.join('; ')})"
          end

          body << <<-RUBY
            if #{conditions.join(' && ')}
              yield route, matches, params
            end
          RUBY
        }

        container.instance_eval(<<-RUBY, __FILE__, __LINE__)
          def optimized_each(obj)
            #{body.join("\n")}
            nil
          end
        RUBY
      end

      def optimize_recognize!
        keys = @recognition_keys.map { |key|
          if key.respond_to?(:call_source)
            key.call_source(:cache, :obj)
          else
            "obj.#{key}"
          end
        }.join(', ')

        @optimized_recognize_defined = true

        remove_metaclass_method :recognize

        instance_eval(<<-RUBY, __FILE__, __LINE__)
          def recognize(obj)
            cache = {}
            container = @recognition_graph[#{keys}]
            optimize_container_iterator(container) unless container.respond_to?(:optimized_each)

            if block_given?
              container.optimized_each(obj) do |route, matches, params|
                yield route, matches, params
              end
            else
              container.optimized_each(obj) do |route, matches, params|
                return route, matches, params
              end
            end

            nil
          end
        RUBY
      end

      # method_defined? can't distinguish between instance
      # and meta methods. So we have to rescue if the method
      # has not been defined in the metaclass yet.
      def remove_metaclass_method(symbol)
        metaclass = class << self; self; end
        metaclass.send(:remove_method, symbol)
      rescue NameError => e
        nil
      end
  end
end