Froze rails gems
[depot.git] / vendor / rails / actionpack / lib / action_controller / routing / builder.rb
1 module ActionController
2 module Routing
3 class RouteBuilder #:nodoc:
4 attr_reader :separators, :optional_separators
5 attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
6
7 def initialize
8 @separators = Routing::SEPARATORS
9 @optional_separators = %w( / )
10
11 @separator_regexp = /[#{Regexp.escape(separators.join)}]/
12 @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
13 @interval_regexp = /(.*?)(#{separator_regexp}|$)/
14 end
15
16 # Accepts a "route path" (a string defining a route), and returns the array
17 # of segments that corresponds to it. Note that the segment array is only
18 # partially initialized--the defaults and requirements, for instance, need
19 # to be set separately, via the +assign_route_options+ method, and the
20 # <tt>optional?</tt> method for each segment will not be reliable until after
21 # +assign_route_options+ is called, as well.
22 def segments_for_route_path(path)
23 rest, segments = path, []
24
25 until rest.empty?
26 segment, rest = segment_for(rest)
27 segments << segment
28 end
29 segments
30 end
31
32 # A factory method that returns a new segment instance appropriate for the
33 # format of the given string.
34 def segment_for(string)
35 segment =
36 case string
37 when /\A:(\w+)/
38 key = $1.to_sym
39 key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
40 when /\A\*(\w+)/
41 PathSegment.new($1.to_sym, :optional => true)
42 when /\A\?(.*?)\?/
43 StaticSegment.new($1, :optional => true)
44 when nonseparator_regexp
45 StaticSegment.new($1)
46 when separator_regexp
47 DividerSegment.new($&, :optional => optional_separators.include?($&))
48 end
49 [segment, $~.post_match]
50 end
51
52 # Split the given hash of options into requirement and default hashes. The
53 # segments are passed alongside in order to distinguish between default values
54 # and requirements.
55 def divide_route_options(segments, options)
56 options = options.except(:path_prefix, :name_prefix)
57
58 if options[:namespace]
59 options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
60 end
61
62 requirements = (options.delete(:requirements) || {}).dup
63 defaults = (options.delete(:defaults) || {}).dup
64 conditions = (options.delete(:conditions) || {}).dup
65
66 validate_route_conditions(conditions)
67
68 path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
69 options.each do |key, value|
70 hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
71 hash[key] = value
72 end
73
74 [defaults, requirements, conditions]
75 end
76
77 # Takes a hash of defaults and a hash of requirements, and assigns them to
78 # the segments. Any unused requirements (which do not correspond to a segment)
79 # are returned as a hash.
80 def assign_route_options(segments, defaults, requirements)
81 route_requirements = {} # Requirements that do not belong to a segment
82
83 segment_named = Proc.new do |key|
84 segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
85 end
86
87 requirements.each do |key, requirement|
88 segment = segment_named[key]
89 if segment
90 raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
91 if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
92 raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
93 end
94 if requirement.multiline?
95 raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
96 end
97 segment.regexp = requirement
98 else
99 route_requirements[key] = requirement
100 end
101 end
102
103 defaults.each do |key, default|
104 segment = segment_named[key]
105 raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
106 segment.is_optional = true
107 segment.default = default.to_param if default
108 end
109
110 assign_default_route_options(segments)
111 ensure_required_segments(segments)
112 route_requirements
113 end
114
115 # Assign default options, such as 'index' as a default for <tt>:action</tt>. This
116 # method must be run *after* user supplied requirements and defaults have
117 # been applied to the segments.
118 def assign_default_route_options(segments)
119 segments.each do |segment|
120 next unless segment.is_a? DynamicSegment
121 case segment.key
122 when :action
123 if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
124 segment.default ||= 'index'
125 segment.is_optional = true
126 end
127 when :id
128 if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
129 segment.is_optional = true
130 end
131 end
132 end
133 end
134
135 # Makes sure that there are no optional segments that precede a required
136 # segment. If any are found that precede a required segment, they are
137 # made required.
138 def ensure_required_segments(segments)
139 allow_optional = true
140 segments.reverse_each do |segment|
141 allow_optional &&= segment.optional?
142 if !allow_optional && segment.optional?
143 unless segment.optionality_implied?
144 warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
145 end
146 segment.is_optional = false
147 elsif allow_optional && segment.respond_to?(:default) && segment.default
148 # if a segment has a default, then it is optional
149 segment.is_optional = true
150 end
151 end
152 end
153
154 # Construct and return a route with the given path and options.
155 def build(path, options)
156 # Wrap the path with slashes
157 path = "/#{path}" unless path[0] == ?/
158 path = "#{path}/" unless path[-1] == ?/
159
160 path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
161
162 segments = segments_for_route_path(path)
163 defaults, requirements, conditions = divide_route_options(segments, options)
164 requirements = assign_route_options(segments, defaults, requirements)
165
166 # TODO: Segments should be frozen on initialize
167 segments.each { |segment| segment.freeze }
168
169 route = Route.new(segments, requirements, conditions)
170
171 if !route.significant_keys.include?(:controller)
172 raise ArgumentError, "Illegal route: the :controller must be specified!"
173 end
174
175 route.freeze
176 end
177
178 private
179 def validate_route_conditions(conditions)
180 if method = conditions[:method]
181 [method].flatten.each do |m|
182 if m == :head
183 raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
184 end
185
186 unless HTTP_METHODS.include?(m.to_sym)
187 raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
188 end
189 end
190 end
191 end
192 end
193 end
194 end