~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_controller/routing/route.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
module ActionController
2
 
  module Routing
3
 
    class Route #:nodoc:
4
 
      attr_accessor :segments, :requirements, :conditions, :optimise
5
 
 
6
 
      def initialize(segments = [], requirements = {}, conditions = {})
7
 
        @segments = segments
8
 
        @requirements = requirements
9
 
        @conditions = conditions
10
 
 
11
 
        if !significant_keys.include?(:action) && !requirements[:action]
12
 
          @requirements[:action] = "index"
13
 
          @significant_keys << :action
14
 
        end
15
 
 
16
 
        # Routes cannot use the current string interpolation method
17
 
        # if there are user-supplied <tt>:requirements</tt> as the interpolation
18
 
        # code won't raise RoutingErrors when generating
19
 
        has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
20
 
        if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
21
 
          @optimise = false
22
 
        else
23
 
          @optimise = true
24
 
        end
25
 
      end
26
 
 
27
 
      # Indicates whether the routes should be optimised with the string interpolation
28
 
      # version of the named routes methods.
29
 
      def optimise?
30
 
        @optimise && ActionController::Base::optimise_named_routes
31
 
      end
32
 
 
33
 
      def segment_keys
34
 
        segments.collect do |segment|
35
 
          segment.key if segment.respond_to? :key
36
 
        end.compact
37
 
      end
38
 
      
39
 
      def required_segment_keys
40
 
        required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
41
 
        required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
42
 
      end
43
 
 
44
 
      # Build a query string from the keys of the given hash. If +only_keys+
45
 
      # is given (as an array), only the keys indicated will be used to build
46
 
      # the query string. The query string will correctly build array parameter
47
 
      # values.
48
 
      def build_query_string(hash, only_keys = nil)
49
 
        elements = []
50
 
 
51
 
        (only_keys || hash.keys).each do |key|
52
 
          if value = hash[key]
53
 
            elements << value.to_query(key)
54
 
          end
55
 
        end
56
 
 
57
 
        elements.empty? ? '' : "?#{elements.sort * '&'}"
58
 
      end
59
 
 
60
 
      # A route's parameter shell contains parameter values that are not in the
61
 
      # route's path, but should be placed in the recognized hash.
62
 
      #
63
 
      # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
64
 
      #
65
 
      #   map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
66
 
      #
67
 
      def parameter_shell
68
 
        @parameter_shell ||= returning({}) do |shell|
69
 
          requirements.each do |key, requirement|
70
 
            shell[key] = requirement unless requirement.is_a? Regexp
71
 
          end
72
 
        end
73
 
      end
74
 
 
75
 
      # Return an array containing all the keys that are used in this route. This
76
 
      # includes keys that appear inside the path, and keys that have requirements
77
 
      # placed upon them.
78
 
      def significant_keys
79
 
        @significant_keys ||= returning([]) do |sk|
80
 
          segments.each { |segment| sk << segment.key if segment.respond_to? :key }
81
 
          sk.concat requirements.keys
82
 
          sk.uniq!
83
 
        end
84
 
      end
85
 
 
86
 
      # Return a hash of key/value pairs representing the keys in the route that
87
 
      # have defaults, or which are specified by non-regexp requirements.
88
 
      def defaults
89
 
        @defaults ||= returning({}) do |hash|
90
 
          segments.each do |segment|
91
 
            next unless segment.respond_to? :default
92
 
            hash[segment.key] = segment.default unless segment.default.nil?
93
 
          end
94
 
          requirements.each do |key,req|
95
 
            next if Regexp === req || req.nil?
96
 
            hash[key] = req
97
 
          end
98
 
        end
99
 
      end
100
 
 
101
 
      def matches_controller_and_action?(controller, action)
102
 
        prepare_matching!
103
 
        (@controller_requirement.nil? || @controller_requirement === controller) &&
104
 
        (@action_requirement.nil? || @action_requirement === action)
105
 
      end
106
 
 
107
 
      def to_s
108
 
        @to_s ||= begin
109
 
          segs = segments.inject("") { |str,s| str << s.to_s }
110
 
          "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
111
 
        end
112
 
      end
113
 
 
114
 
      # TODO: Route should be prepared and frozen on initialize
115
 
      def freeze
116
 
        unless frozen?
117
 
          write_generation!
118
 
          write_recognition!
119
 
          prepare_matching!
120
 
 
121
 
          parameter_shell
122
 
          significant_keys
123
 
          defaults
124
 
          to_s
125
 
        end
126
 
 
127
 
        super
128
 
      end
129
 
 
130
 
      def generate(options, hash, expire_on = {})
131
 
        path, hash = generate_raw(options, hash, expire_on)
132
 
        append_query_string(path, hash, extra_keys(options))
133
 
      end
134
 
 
135
 
      def generate_extras(options, hash, expire_on = {})
136
 
        path, hash = generate_raw(options, hash, expire_on)
137
 
        [path, extra_keys(options)]
138
 
      end
139
 
 
140
 
      private
141
 
        def requirement_for(key)
142
 
          return requirements[key] if requirements.key? key
143
 
          segments.each do |segment|
144
 
            return segment.regexp if segment.respond_to?(:key) && segment.key == key
145
 
          end
146
 
          nil
147
 
        end
148
 
 
149
 
        # Write and compile a +generate+ method for this Route.
150
 
        def write_generation!
151
 
          # Build the main body of the generation
152
 
          body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
153
 
 
154
 
          # If we have conditions that must be tested first, nest the body inside an if
155
 
          body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
156
 
          args = "options, hash, expire_on = {}"
157
 
 
158
 
          # Nest the body inside of a def block, and then compile it.
159
 
          raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
160
 
          instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
161
 
 
162
 
          # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
163
 
          # are the same as the keys that were recalled from the previous request. Thus,
164
 
          # we can use the expire_on.keys to determine which keys ought to be used to build
165
 
          # the query string. (Never use keys from the recalled request when building the
166
 
          # query string.)
167
 
 
168
 
          raw_method
169
 
        end
170
 
 
171
 
        # Build several lines of code that extract values from the options hash. If any
172
 
        # of the values are missing or rejected then a return will be executed.
173
 
        def generation_extraction
174
 
          segments.collect do |segment|
175
 
            segment.extraction_code
176
 
          end.compact * "\n"
177
 
        end
178
 
 
179
 
        # Produce a condition expression that will check the requirements of this route
180
 
        # upon generation.
181
 
        def generation_requirements
182
 
          requirement_conditions = requirements.collect do |key, req|
183
 
            if req.is_a? Regexp
184
 
              value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
185
 
              "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
186
 
            else
187
 
              "hash[:#{key}] == #{req.inspect}"
188
 
            end
189
 
          end
190
 
          requirement_conditions * ' && ' unless requirement_conditions.empty?
191
 
        end
192
 
 
193
 
        def generation_structure
194
 
          segments.last.string_structure segments[0..-2]
195
 
        end
196
 
 
197
 
        # Write and compile a +recognize+ method for this Route.
198
 
        def write_recognition!
199
 
          # Create an if structure to extract the params from a match if it occurs.
200
 
          body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
201
 
          body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
202
 
 
203
 
          # Build the method declaration and compile it
204
 
          method_decl = "def recognize(path, env = {})\n#{body}\nend"
205
 
          instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
206
 
          method_decl
207
 
        end
208
 
 
209
 
        # Plugins may override this method to add other conditions, like checks on
210
 
        # host, subdomain, and so forth. Note that changes here only affect route
211
 
        # recognition, not generation.
212
 
        def recognition_conditions
213
 
          result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
214
 
          result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
215
 
          result
216
 
        end
217
 
 
218
 
        # Build the regular expression pattern that will match this route.
219
 
        def recognition_pattern(wrap = true)
220
 
          pattern = ''
221
 
          segments.reverse_each do |segment|
222
 
            pattern = segment.build_pattern pattern
223
 
          end
224
 
          wrap ? ("\\A" + pattern + "\\Z") : pattern
225
 
        end
226
 
 
227
 
        # Write the code to extract the parameters from a matched route.
228
 
        def recognition_extraction
229
 
          next_capture = 1
230
 
          extraction = segments.collect do |segment|
231
 
            x = segment.match_extraction(next_capture)
232
 
            next_capture += segment.number_of_captures
233
 
            x
234
 
          end
235
 
          extraction.compact
236
 
        end
237
 
 
238
 
        # Generate the query string with any extra keys in the hash and append
239
 
        # it to the given path, returning the new path.
240
 
        def append_query_string(path, hash, query_keys = nil)
241
 
          return nil unless path
242
 
          query_keys ||= extra_keys(hash)
243
 
          "#{path}#{build_query_string(hash, query_keys)}"
244
 
        end
245
 
 
246
 
        # Determine which keys in the given hash are "extra". Extra keys are
247
 
        # those that were not used to generate a particular route. The extra
248
 
        # keys also do not include those recalled from the prior request, nor
249
 
        # do they include any keys that were implied in the route (like a
250
 
        # <tt>:controller</tt> that is required, but not explicitly used in the
251
 
        # text of the route.)
252
 
        def extra_keys(hash, recall = {})
253
 
          (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
254
 
        end
255
 
 
256
 
        def prepare_matching!
257
 
          unless defined? @matching_prepared
258
 
            @controller_requirement = requirement_for(:controller)
259
 
            @action_requirement = requirement_for(:action)
260
 
            @matching_prepared = true
261
 
          end
262
 
        end
263
 
    end
264
 
  end
265
 
end