Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 695 lines (563 sloc) 22.638 kb
9988848 @tenderlove fix require
tenderlove authored
1 require 'journey'
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
2 require 'forwardable'
e00867b @pixeltrix Raise ArgumentError if route name is invalid [#6517 state:resolved]
pixeltrix authored
3 require 'active_support/core_ext/object/blank'
8a91ccf @mislav add missing requires to Rescuable and RouteSet [#4415 state:committed]
mislav authored
4 require 'active_support/core_ext/object/to_query'
ec5d846 @drogus Properly reload routes defined in class definition
drogus authored
5 require 'active_support/core_ext/hash/slice'
9be7911 @smartinez87 Use #remove_possible_method instead here
smartinez87 authored
6 require 'active_support/core_ext/module/remove_method'
525fd3a @vatrai TODO fix explicitly loading exceptations, autoload removed
vatrai authored
7 require 'action_controller/metal/exceptions'
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
8
a74022e @josh Move Routing into AD
josh authored
9 module ActionDispatch
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
10 module Routing
e0513e3 @josh Routing whitespace cleanup
josh authored
11 class RouteSet #:nodoc:
ed988ee @josevalim Avoid inspecting the whole route set, closes #1525
josevalim authored
12 # Since the router holds references to many parts of the system
13 # like engines, controllers and the application itself, inspecting
14 # the route set can actually be really slow, therefore we default
15 # alias inspect to to_s.
16 alias inspect to_s
17
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
18 PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
19
c10bf82 @pixeltrix Remove routing implementation details from RDoc
pixeltrix authored
20 class Dispatcher #:nodoc:
e8ef12e @josevalim Make Railties tests green again.
josevalim authored
21 def initialize(options={})
22 @defaults = options[:defaults]
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
23 @glob_param = options.delete(:glob)
a6b3942 @wycats Optimize LookupContext
wycats authored
24 @controllers = {}
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
25 end
26
27 def call(env)
28 params = env[PARAMETERS_KEY]
61e9f20 @josh Use rackmounts recognize api and don't piggyback recognize_path on
josh authored
29 prepare_params!(params)
e8ef12e @josevalim Make Railties tests green again.
josevalim authored
30
31 # Just raise undefined constant errors if a controller was specified as default.
32 unless controller = controller(params, @defaults.key?(:controller))
33 return [404, {'X-Cascade' => 'pass'}, []]
34 end
35
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
36 dispatch(controller, params[:action], env)
61e9f20 @josh Use rackmounts recognize api and don't piggyback recognize_path on
josh authored
37 end
38
39 def prepare_params!(params)
203b88f @kennyj Fix GH #4720. Routing problem with nested namespace and already camel…
kennyj authored
40 normalize_controller!(params)
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
41 merge_default_action!(params)
42 split_glob_param!(params) if @glob_param
61e9f20 @josh Use rackmounts recognize api and don't piggyback recognize_path on
josh authored
43 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
44
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
45 # If this is a default_controller (i.e. a controller specified by the user)
46 # we should raise an error in case it's not found, because it usually means
702aecb @abyx Fix typo in Dispatcher#controller documentation
abyx authored
47 # a user error. However, if the controller was retrieved through a dynamic
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
48 # segment, as in :controller(/:action), we should simply return nil and
b802a0d @pixeltrix When a dynamic :controller segment is present in the path add a Regex…
pixeltrix authored
49 # delegate the control back to Rack cascade. Besides, if this is not a default
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
50 # controller, it means we should respect the @scope[:module] parameter.
51 def controller(params, default_controller=true)
a6b3942 @wycats Optimize LookupContext
wycats authored
52 if params && params.key?(:controller)
b802a0d @pixeltrix When a dynamic :controller segment is present in the path add a Regex…
pixeltrix authored
53 controller_param = params[:controller]
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
54 controller_reference(controller_param)
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
55 end
4d177d4 @josevalim Routes should not swallow all NameErrors [#3862 status:resolved].
josevalim authored
56 rescue NameError => e
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
57 raise ActionController::RoutingError, e.message, e.backtrace if default_controller
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
58 end
59
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
60 private
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
61
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
62 def controller_reference(controller_param)
7b6bfe8 @tenderlove refactor Reference to a ClassCache object, fix lazy lookup in Middlew…
tenderlove authored
63 controller_name = "#{controller_param.camelize}Controller"
64
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
65 unless controller = @controllers[controller_param]
66 controller = @controllers[controller_param] =
dd41387 @tenderlove use newer class cache api
tenderlove authored
67 ActiveSupport::Dependencies.reference(controller_name)
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
68 end
7b6bfe8 @tenderlove refactor Reference to a ClassCache object, fix lazy lookup in Middlew…
tenderlove authored
69 controller.get(controller_name)
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
70 end
71
72 def dispatch(controller, action, env)
73 controller.action(action).call(env)
74 end
75
203b88f @kennyj Fix GH #4720. Routing problem with nested namespace and already camel…
kennyj authored
76 def normalize_controller!(params)
77 params[:controller] = params[:controller].underscore if params.key?(:controller)
78 end
79
9e6e648 @josevalim Fix routes with :controller segment when namespaced [#5034 state:reso…
josevalim authored
80 def merge_default_action!(params)
81 params[:action] ||= 'index'
82 end
83
84 def split_glob_param!(params)
71acc27 @miloops Move uri parser to AS as URI.parser method to reuse it in AP and ARes.
miloops authored
85 params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) }
5d773f8 @miloops Remove warning "URI.unescape is obsolete" from actionpack.
miloops authored
86 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
87 end
88
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
89 # A NamedRouteCollection instance is a collection of named routes, and also
90 # maintains an anonymous module that can be used to install helpers for the
91 # named routes.
92 class NamedRouteCollection #:nodoc:
93 include Enumerable
13a7836 @josh Install url helpers on module instance so they can be accessed
josh authored
94 attr_reader :routes, :helpers, :module
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
95
96 def initialize
c0904e4 @tenderlove decouple initialize from clear!. Initialize ivars in initialize, clear
tenderlove authored
97 @routes = {}
98 @helpers = []
99 @module = Module.new
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
100 end
101
eebac02 @wycats Make named helpers unprotected without becoming actions [#4696 state:…
wycats authored
102 def helper_names
103 self.module.instance_methods.map(&:to_s)
104 end
105
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
106 def clear!
c0904e4 @tenderlove decouple initialize from clear!. Initialize ivars in initialize, clear
tenderlove authored
107 @routes.clear
108 @helpers.clear
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
109 end
110
111 def add(name, route)
112 routes[name.to_sym] = route
113 define_named_route_methods(name, route)
114 end
115
116 def get(name)
117 routes[name.to_sym]
118 end
119
120 alias []= add
121 alias [] get
122 alias clear clear!
123
124 def each
125 routes.each { |name, route| yield name, route }
126 self
127 end
128
129 def names
130 routes.keys
131 end
132
133 def length
134 routes.length
135 end
136
137 private
138 def url_helper_name(name, kind = :url)
139 :"#{name}_#{kind}"
140 end
141
142 def hash_access_name(name, kind = :url)
143 :"hash_for_#{name}_#{kind}"
144 end
145
146 def define_named_route_methods(name, route)
147 {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
148 hash = route.defaults.merge(:use_route => name).merge(opts)
149 define_hash_access route, name, kind, hash
150 define_url_helper route, name, kind, hash
151 end
152 end
153
154 def define_hash_access(route, name, kind, options)
155 selector = hash_access_name(name, kind)
e4099c2 @josevalim Allow named routes to be debugged.
josevalim authored
156
0035c54 @tenderlove don't use instance eval, just reference variables so we don't have to
tenderlove authored
157 @module.module_eval do
158 remove_possible_method selector
159
160 define_method(selector) do |*args|
161 inner_options = args.extract_options!
162 result = options.dup
b53efd2 @drogus Extended url_for to handle specifying which router should be used.
drogus authored
163
164 if args.any?
019eea4 Fix named routes modifying arguments
Pawel Pierzchala authored
165 result[:_positional_args] = args
0035c54 @tenderlove don't use instance eval, just reference variables so we don't have to
tenderlove authored
166 result[:_positional_keys] = route.segment_keys
b53efd2 @drogus Extended url_for to handle specifying which router should be used.
drogus authored
167 end
168
0035c54 @tenderlove don't use instance eval, just reference variables so we don't have to
tenderlove authored
169 result.merge(inner_options)
b53efd2 @drogus Extended url_for to handle specifying which router should be used.
drogus authored
170 end
0035c54 @tenderlove don't use instance eval, just reference variables so we don't have to
tenderlove authored
171
172 protected selector
173 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
174 helpers << selector
175 end
176
e4099c2 @josevalim Allow named routes to be debugged.
josevalim authored
177 # Create a url helper allowing ordered parameters to be associated
178 # with corresponding dynamic segments, so you can do:
179 #
180 # foo_url(bar, baz, bang)
181 #
182 # Instead of:
183 #
184 # foo_url(:bar => bar, :baz => baz, :bang => bang)
185 #
186 # Also allow options hash, so you can do:
187 #
188 # foo_url(bar, baz, bang, :sort_by => 'baz')
189 #
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
190 def define_url_helper(route, name, kind, options)
191 selector = url_helper_name(name, kind)
192 hash_access_method = hash_access_name(name, kind)
193
0e6b588 @lest don't pass unnecessary argument
lest authored
194 if optimize_helper?(route)
d7014bc @josevalim Optimize path helpers.
josevalim authored
195 @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
196 remove_possible_method :#{selector}
197 def #{selector}(*args)
cd5daba @josevalim Optimize url helpers.
josevalim authored
198 if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
d7014bc @josevalim Optimize path helpers.
josevalim authored
199 options = #{options.inspect}.merge!(url_options)
200 options[:path] = "#{optimized_helper(route)}"
201 ActionDispatch::Http::URL.url_for(options)
202 else
203 url_for(#{hash_access_method}(*args))
204 end
205 end
206 END_EVAL
207 else
208 @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
209 remove_possible_method :#{selector}
210 def #{selector}(*args)
211 url_for(#{hash_access_method}(*args))
212 end
213 END_EVAL
214 end
215
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
216 helpers << selector
217 end
d7014bc @josevalim Optimize path helpers.
josevalim authored
218
cd5daba @josevalim Optimize url helpers.
josevalim authored
219 # Clause check about when we need to generate an optimized helper.
0e6b588 @lest don't pass unnecessary argument
lest authored
220 def optimize_helper?(route) #:nodoc:
cd5daba @josevalim Optimize url helpers.
josevalim authored
221 route.ast.grep(Journey::Nodes::Star).empty? && route.requirements.except(:controller, :action).empty?
d7014bc @josevalim Optimize path helpers.
josevalim authored
222 end
223
224 # Generates the interpolation to be used in the optimized helper.
225 def optimized_helper(route)
226 string_route = route.ast.to_s
227
228 while string_route.gsub!(/\([^\)]*\)/, "")
229 true
230 end
231
232 route.required_parts.each_with_index do |part, i|
233 string_route.gsub!(part.inspect, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
234 end
235
236 string_route
237 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
238 end
239
a08bee7 @tenderlove all routes can be stored in the Journey Routes object
tenderlove authored
240 attr_accessor :formatter, :set, :named_routes, :default_scope, :router
e0bdc4f @josevalim Ensure namespaced controllers in engines work.
josevalim authored
241 attr_accessor :disable_clear_and_finalize, :resources_path_names
19ccd46 @pixeltrix Remove invalid conditions from route [#4989 state:resolved]
pixeltrix authored
242 attr_accessor :default_url_options, :request_class, :valid_conditions
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
243
a08bee7 @tenderlove all routes can be stored in the Journey Routes object
tenderlove authored
244 alias :routes :set
245
be968ec @josh Respect resources_path_names and :path_names options in new dsl
josh authored
246 def self.default_resources_path_names
247 { :new => 'new', :edit => 'edit' }
248 end
249
ab8bf9e @wycats * Change the object used in routing constraints to be an instance of
wycats authored
250 def initialize(request_class = ActionDispatch::Request)
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
251 self.named_routes = NamedRouteCollection.new
e0bdc4f @josevalim Ensure namespaced controllers in engines work.
josevalim authored
252 self.resources_path_names = self.class.default_resources_path_names.dup
1cc2a61 @josh Allow default_url_options to be set on route set
josh authored
253 self.default_url_options = {}
6dfa8d8 @josevalim Tidy up valid conditions in router a bit.
josevalim authored
254
ab8bf9e @wycats * Change the object used in routing constraints to be an instance of
wycats authored
255 self.request_class = request_class
ad1a891 @tenderlove unfactor the Route class to private factory methods
tenderlove authored
256 @valid_conditions = {}
257
258 request_class.public_instance_methods.each { |m|
259 @valid_conditions[m.to_sym] = true
260 }
261 @valid_conditions[:controller] = true
262 @valid_conditions[:action] = true
263
6dfa8d8 @josevalim Tidy up valid conditions in router a bit.
josevalim authored
264 self.valid_conditions.delete(:id)
a5db148 @josh Prepare Route#generate and Route#recognize early. Also refactor segme…
josh authored
265
b7ccfa9 @tenderlove clear! does not need to be called from initialize
tenderlove authored
266 @append = []
267 @prepend = []
5f8e48c @josh Move route reloading into railties
josh authored
268 @disable_clear_and_finalize = false
b7ccfa9 @tenderlove clear! does not need to be called from initialize
tenderlove authored
269 @finalized = false
59b9fe9 @tenderlove reuse the route collection and formatter by clearing them
tenderlove authored
270
271 @set = Journey::Routes.new
272 @router = Journey::Router.new(@set, {
273 :parameters_key => PARAMETERS_KEY,
274 :request_class => request_class})
275 @formatter = Journey::Formatter.new @set
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
276 end
277
9a17416 Tweak out url_for uses :script_name and add some tests
Carl Lerche authored
278 def draw(&block)
5f8e48c @josh Move route reloading into railties
josh authored
279 clear! unless @disable_clear_and_finalize
7418a44 @carllerche Add RouteSet#append
carllerche authored
280 eval_block(block)
281 finalize! unless @disable_clear_and_finalize
282 nil
283 end
284
285 def append(&block)
286 @append << block
287 end
ec5434c @josh Check block arity passed to routes draw so you don't need to use
josh authored
288
80bf68a @josevalim prepend the assets route instead of appending, closes #436
josevalim authored
289 def prepend(&block)
290 @prepend << block
291 end
292
7418a44 @carllerche Add RouteSet#append
carllerche authored
293 def eval_block(block)
2755294 @joshk raise an error if the old router draw method is used, along with a me…
joshk authored
294 if block.arity == 1
295 raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
4250cca @johndouthat Remove reference to rails_legacy_mapper, which isn't compatible with …
johndouthat authored
296 "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
2755294 @joshk raise an error if the old router draw method is used, along with a me…
joshk authored
297 end
ec5434c @josh Check block arity passed to routes draw so you don't need to use
josh authored
298 mapper = Mapper.new(self)
b3eb26a @drogus Removed deprecated RouteSet API, still many tests fail
drogus authored
299 if default_scope
300 mapper.with_default_scope(default_scope, &block)
ec5434c @josh Check block arity passed to routes draw so you don't need to use
josh authored
301 else
b3eb26a @drogus Removed deprecated RouteSet API, still many tests fail
drogus authored
302 mapper.instance_exec(&block)
ec5434c @josh Check block arity passed to routes draw so you don't need to use
josh authored
303 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
304 end
305
5f8e48c @josh Move route reloading into railties
josh authored
306 def finalize!
3abf5ad Make RouteSet#finalize! a NOOP if it's been called already. Call fina…
Carl Lerche authored
307 return if @finalized
7418a44 @carllerche Add RouteSet#append
carllerche authored
308 @append.each { |blk| eval_block(blk) }
3abf5ad Make RouteSet#finalize! a NOOP if it's been called already. Call fina…
Carl Lerche authored
309 @finalized = true
5f8e48c @josh Move route reloading into railties
josh authored
310 end
311
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
312 def clear!
3abf5ad Make RouteSet#finalize! a NOOP if it's been called already. Call fina…
Carl Lerche authored
313 @finalized = false
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
314 named_routes.clear
59b9fe9 @tenderlove reuse the route collection and formatter by clearing them
tenderlove authored
315 set.clear
316 formatter.clear
80bf68a @josevalim prepend the assets route instead of appending, closes #436
josevalim authored
317 @prepend.each { |blk| eval_block(blk) }
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
318 end
319
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
320 module MountedHelpers #:nodoc:
321 extend ActiveSupport::Concern
322 include UrlFor
6c95e0f @drogus Add mounted_helpers to routes
drogus authored
323 end
324
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
325 # Contains all the mounted helpers accross different
326 # engines and the `main_app` helper for the application.
327 # You can include this in your classes if you want to
328 # access routes for other engines.
820c0fe @drogus Explicitly define main_app proxy
drogus authored
329 def mounted_helpers
6c95e0f @drogus Add mounted_helpers to routes
drogus authored
330 MountedHelpers
331 end
332
9913193 @drogus For view_context we need to initialize RoutesProxy in context of cont…
drogus authored
333 def define_mounted_helper(name)
334 return if MountedHelpers.method_defined?(name)
335
6c95e0f @drogus Add mounted_helpers to routes
drogus authored
336 routes = self
337 MountedHelpers.class_eval do
338 define_method "_#{name}" do
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
339 RoutesProxy.new(routes, _routes_context)
6c95e0f @drogus Add mounted_helpers to routes
drogus authored
340 end
341 end
342
343 MountedHelpers.class_eval <<-RUBY
344 def #{name}
345 @#{name} ||= _#{name}
346 end
347 RUBY
348 end
349
98f77e0 Rename named_url_helpers to url_helpers and url_helpers to url_for
Carlhuda authored
350 def url_helpers
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
351 @url_helpers ||= begin
352 routes = self
353
354 Module.new do
355 extend ActiveSupport::Concern
356 include UrlFor
357
358 # Define url_for in the singleton level so one can do:
359 # Rails.application.routes.url_helpers.url_for(args)
360 @_routes = routes
361 class << self
cd5daba @josevalim Optimize url helpers.
josevalim authored
362 delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
363 end
226dfc2 WIP: Remove the global router
Carlhuda authored
364
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
365 # Make named_routes available in the module singleton
366 # as well, so one can do:
367 # Rails.application.routes.url_helpers.posts_path
368 extend routes.named_routes.module
226dfc2 WIP: Remove the global router
Carlhuda authored
369
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
370 # Any class that includes this module will get all
371 # named routes...
372 include routes.named_routes.module
72c290c @tenderlove avoid extra method calls by just defining the delegate
tenderlove authored
373
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
374 # plus a singleton class method called _routes ...
375 included do
376 singleton_class.send(:redefine_method, :_routes) { routes }
377 end
13a7836 @josh Install url helpers on module instance so they can be accessed
josh authored
378
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
379 # And an instance method _routes. Note that
380 # UrlFor (included in this module) add extra
381 # conveniences for working with @_routes.
382 define_method(:_routes) { @_routes || routes }
226dfc2 WIP: Remove the global router
Carlhuda authored
383 end
49b6b49 @josevalim Clean up routes inclusion and add some comments for the next soul tha…
josevalim authored
384 end
226dfc2 WIP: Remove the global router
Carlhuda authored
385 end
386
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
387 def empty?
388 routes.empty?
389 end
390
f38e2e0 Add support for mount RackApp, :at => "/sprockets" with a shorthand o…
Carlhuda authored
391 def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
e00867b @pixeltrix Raise ArgumentError if route name is invalid [#6517 state:resolved]
pixeltrix authored
392 raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
ad1a891 @tenderlove unfactor the Route class to private factory methods
tenderlove authored
393
394 path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
395 conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
396
397 route = @set.add_route(app, path, conditions, defaults, name)
71d769e @andyjeffries Named Routes shouldn't override existing ones (currently route recogn…
andyjeffries authored
398 named_routes[name] = route if name && !named_routes[name]
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
399 route
400 end
401
ad1a891 @tenderlove unfactor the Route class to private factory methods
tenderlove authored
402 def build_path(path, requirements, separators, anchor)
403 strexp = Journey::Router::Strexp.new(
404 path,
405 requirements,
406 SEPARATORS,
407 anchor)
408
8d26f87 @tenderlove Added custom regexps to ASTs that have literal nodes on either side of
tenderlove authored
409 pattern = Journey::Path::Pattern.new(strexp)
410
411 builder = Journey::GTG::Builder.new pattern.spec
412
413 # Get all the symbol nodes followed by literals that are not the
414 # dummy node.
415 symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
416 builder.followpos(n).first.literal?
417 }
418
419 # Get all the symbol nodes preceded by literals.
420 symbols.concat pattern.spec.find_all(&:literal?).map { |n|
421 builder.followpos(n).first
422 }.find_all(&:symbol?)
423
424 symbols.each { |x|
425 x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
426 }
427
428 pattern
ad1a891 @tenderlove unfactor the Route class to private factory methods
tenderlove authored
429 end
430 private :build_path
431
432 def build_conditions(current_conditions, req_predicates, path_values)
433 conditions = current_conditions.dup
434
435 verbs = conditions[:request_method] || []
436
437 # Rack-Mount requires that :request_method be a regular expression.
438 # :request_method represents the HTTP verb that matches this route.
439 #
440 # Here we munge values before they get sent on to rack-mount.
441 unless verbs.empty?
442 conditions[:request_method] = %r[^#{verbs.join('|')}$]
443 end
444 conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) }
445
446 conditions
447 end
448 private :build_conditions
449
c10bf82 @pixeltrix Remove routing implementation details from RDoc
pixeltrix authored
450 class Generator #:nodoc:
a05a9ff @tenderlove stop using a hash for parameterizing
tenderlove authored
451 PARAMETERIZE = lambda do |name, value|
452 if name == :controller
453 value
454 elsif value.is_a?(Array)
ec37160 @jeremy Leave escaping up to Journey
jeremy authored
455 value.map { |v| v.to_param }.join('/')
456 elsif param = value.to_param
457 param
86bcccf @thedarkone No need to create a separate lambda for each call.
thedarkone authored
458 end
a05a9ff @tenderlove stop using a hash for parameterizing
tenderlove authored
459 end
86bcccf @thedarkone No need to create a separate lambda for each call.
thedarkone authored
460
28016d3 @drogus Use env['action_dispatch.routes'] to determine if we should generate …
drogus authored
461 attr_reader :options, :recall, :set, :named_route
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
462
463 def initialize(options, recall, set, extras = false)
464 @named_route = options.delete(:use_route)
465 @options = options.dup
466 @recall = recall.dup
467 @set = set
468 @extras = extras
469
470 normalize_options!
471 normalize_controller_action_id!
472 use_relative_controller!
473 controller.sub!(%r{^/}, '') if controller
474 handle_nil_action!
475 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
476
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
477 def controller
478 @controller ||= @options[:controller]
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
479 end
480
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
481 def current_controller
482 @recall[:controller]
483 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
484
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
485 def use_recall_for(key)
486 if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
54250a5 @pixeltrix Refactor recall parameter normalization [#5021 state:resolved]
pixeltrix authored
487 if named_route_exists?
488 @options[key] = @recall.delete(key) if segment_keys.include?(key)
489 else
490 @options[key] = @recall.delete(key)
491 end
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
492 end
493 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
494
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
495 def normalize_options!
496 # If an explicit :controller was given, always make :action explicit
497 # too, so that action expiry works as expected for things like
498 #
499 # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
500 #
501 # (the above is from the unit tests). In the above case, because the
502 # controller was explicitly given, but no action, the action is implied to
503 # be "index", not the recalled action of "show".
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
504
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
505 if options[:controller]
506 options[:action] ||= 'index'
507 options[:controller] = options[:controller].to_s
508 end
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
509
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
510 if options[:action]
511 options[:action] = options[:action].to_s
512 end
513 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
514
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
515 # This pulls :controller, :action, and :id out of the recall.
516 # The recall key is only used if there is no key in the options
517 # or if the key in the options is identical. If any of
518 # :controller, :action or :id is not found, don't pull any
519 # more keys from the recall.
520 def normalize_controller_action_id!
521 @recall[:action] ||= 'index' if current_controller
522
523 use_recall_for(:controller) or return
524 use_recall_for(:action) or return
525 use_recall_for(:id)
526 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
527
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
528 # if the current controller is "foo/bar/baz" and :controller => "baz/bat"
529 # is specified, the controller becomes "foo/baz/bat"
530 def use_relative_controller!
837231a @kennyj Fix url_for method's behavior when it is called with :controller opti…
kennyj authored
531 if !named_route && different_controller? && !controller.start_with?("/")
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
532 old_parts = current_controller.split('/')
533 size = controller.count("/") + 1
534 parts = old_parts[0...-size] << controller
535 @controller = @options[:controller] = parts.join("/")
536 end
537 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
538
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
539 # This handles the case of :action => nil being explicitly passed.
540 # It is identical to :action => "index"
541 def handle_nil_action!
542 if options.has_key?(:action) && options[:action].nil?
543 options[:action] = 'index'
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
544 end
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
545 recall[:action] = options.delete(:action) if options[:action] == 'index'
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
546 end
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
547
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
548 def generate
4ffe667 @tenderlove Instantiate each part of our routing system:
tenderlove authored
549 path, params = @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
550
f01184a @samleb Avoid potentially expensive inspect call in router. [#4491 state:reso…
samleb authored
551 raise_routing_error unless path
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
552
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
553 return [path, params.keys] if @extras
4b325fc @josh Update routing for rackmount 0.2 api changes
josh authored
554
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
555 [path, params]
f65b221 @tenderlove removing backwards compatibility module
tenderlove authored
556 rescue Journey::Router::RoutingError
f01184a @samleb Avoid potentially expensive inspect call in router. [#4491 state:reso…
samleb authored
557 raise_routing_error
84be6cf @josh Fork rack build nested query to support to_param
josh authored
558 end
559
f01184a @samleb Avoid potentially expensive inspect call in router. [#4491 state:reso…
samleb authored
560 def raise_routing_error
16ae08f @tenderlove use raise to create exceptions and to set the backtrace
tenderlove authored
561 raise ActionController::RoutingError, "No route matches #{options.inspect}"
f01184a @samleb Avoid potentially expensive inspect call in router. [#4491 state:reso…
samleb authored
562 end
84be6cf @josh Fork rack build nested query to support to_param
josh authored
563
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
564 def different_controller?
565 return false unless current_controller
566 controller.to_param != current_controller.to_param
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
567 end
91b52c7 @pixeltrix Normalize recall params when the route is not a standard route otherw…
pixeltrix authored
568
569 private
570 def named_route_exists?
571 named_route && set.named_routes[named_route]
572 end
573
574 def segment_keys
54250a5 @pixeltrix Refactor recall parameter normalization [#5021 state:resolved]
pixeltrix authored
575 set.named_routes[named_route].segment_keys
91b52c7 @pixeltrix Normalize recall params when the route is not a standard route otherw…
pixeltrix authored
576 end
9444ac9 @wycats Refactor the RouteSet so it uses a Generator object instead of one hu…
wycats authored
577 end
578
579 # Generate the path indicated by the arguments, and return an array of
580 # the keys that were not used to generate it.
581 def extra_keys(options, recall={})
582 generate_extras(options, recall).last
583 end
584
585 def generate_extras(options, recall={})
586 generate(options, recall, true)
587 end
588
589 def generate(options, recall = {}, extras = false)
91b52c7 @pixeltrix Normalize recall params when the route is not a standard route otherw…
pixeltrix authored
590 Generator.new(options, recall, self, extras).generate
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
591 end
592
2fe43b6 @joshk :subdomain, :domain and :tld_length options can now be used in url_fo…
joshk authored
593 RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
594 :trailing_slash, :anchor, :params, :only_path, :script_name]
28016d3 @drogus Use env['action_dispatch.routes'] to determine if we should generate …
drogus authored
595
d7014bc @josevalim Optimize path helpers.
josevalim authored
596 def mounted?
597 false
598 end
599
cd5daba @josevalim Optimize url helpers.
josevalim authored
600 def optimize_routes_generation?
601 !mounted? && default_url_options.empty?
602 end
603
28016d3 @drogus Use env['action_dispatch.routes'] to determine if we should generate …
drogus authored
604 def _generate_prefix(options = {})
605 nil
606 end
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
607
4d2470f @josh RouteSet#rewrite => url_for
josh authored
608 def url_for(options)
a1e795f @spastorino options could be of any kind of Hash (Hash, HashWithIndifferentAccess…
spastorino authored
609 options = (options || {}).reverse_merge!(default_url_options)
1cc2a61 @josh Allow default_url_options to be set on route set
josh authored
610
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
611 handle_positional_args(options)
612
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
613 user, password = extract_authentication(options)
614 path_segments = options.delete(:_path_segments)
d7014bc @josevalim Optimize path helpers.
josevalim authored
615 script_name = options.delete(:script_name).presence || _generate_prefix(options)
28016d3 @drogus Use env['action_dispatch.routes'] to determine if we should generate …
drogus authored
616
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
617 path_options = options.except(*RESERVED_OPTIONS)
618 path_options = yield(path_options) if block_given?
619
d7014bc @josevalim Optimize path helpers.
josevalim authored
620 path, params = generate(path_options, path_segments || {})
6efb849 @rmm5t Fixed force_ssl redirects to include original query params
rmm5t authored
621 params.merge!(options[:params] || {})
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
622
c41f08c @josevalim Move symbolize keys to the inner options as we can assume url_options…
josevalim authored
623 ActionDispatch::Http::URL.url_for(options.merge!({
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
624 :path => path,
d7014bc @josevalim Optimize path helpers.
josevalim authored
625 :script_name => script_name,
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
626 :params => params,
627 :user => user,
628 :password => password
629 }))
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
630 end
631
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
632 def call(env)
4ffe667 @tenderlove Instantiate each part of our routing system:
tenderlove authored
633 @router.call(env)
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
634 end
635
588225f @josh Remove fancy method not allowed resource exceptions since they are
josh authored
636 def recognize_path(path, environment = {})
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
637 method = (environment[:method] || "GET").to_s.upcase
f65b221 @tenderlove removing backwards compatibility module
tenderlove authored
638 path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
d7bf930 @mattfawcett Fix the assert_recognizes test method so that it works when there are
mattfawcett authored
639 extras = environment[:extras] || {}
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
640
a1df259 @josh Replace decaying routing internals w/ rack-mount
josh authored
641 begin
642 env = Rack::MockRequest.env_for(path, {:method => method})
643 rescue URI::InvalidURIError => e
a74022e @josh Move Routing into AD
josh authored
644 raise ActionController::RoutingError, e.message
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
645 end
646
7c9bf45 @pixeltrix Support routing constraints in functional tests
pixeltrix authored
647 req = @request_class.new(env)
4ffe667 @tenderlove Instantiate each part of our routing system:
tenderlove authored
648 @router.recognize(req) do |route, matches, params|
d7bf930 @mattfawcett Fix the assert_recognizes test method so that it works when there are
mattfawcett authored
649 params.merge!(extras)
b8af484 @wycats No need to unescape params twice if we came from Rack::Mount
wycats authored
650 params.each do |key, value|
651 if value.is_a?(String)
5ca86ac @lest deprecate String#encoding_aware? and remove its usage
lest authored
652 value = value.dup.force_encoding(Encoding::BINARY)
71acc27 @miloops Move uri parser to AS as URI.parser method to reuse it in AP and ARes.
miloops authored
653 params[key] = URI.parser.unescape(value)
b8af484 @wycats No need to unescape params twice if we came from Rack::Mount
wycats authored
654 end
655 end
9b654d4 @karevn Fix: when using subdomains and constraints, request params were not p…
karevn authored
656 old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
657 env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
61e9f20 @josh Use rackmounts recognize api and don't piggyback recognize_path on
josh authored
658 dispatcher = route.app
385be35 @pixeltrix Fix assert_recognizes with block constraints [#5805 state:resolved]
pixeltrix authored
659 while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
660 dispatcher = dispatcher.app
661 end
8079484 @josevalim Recognize should also work with route is wrapped in a constraint.
josevalim authored
662
e8ef12e @josevalim Make Railties tests green again.
josevalim authored
663 if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
61e9f20 @josh Use rackmounts recognize api and don't piggyback recognize_path on
josh authored
664 dispatcher.prepare_params!(params)
665 return params
666 end
667 end
668
715dd10 @josh Less annoying RoutingError message
josh authored
669 raise ActionController::RoutingError, "No route matches #{path.inspect}"
734e903 @josh Deprecate router generation "best match" sorting
josh authored
670 end
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
671
672 private
2fe43b6 @joshk :subdomain, :domain and :tld_length options can now be used in url_fo…
joshk authored
673
0bda6f1 @joshk The redirect routing method now allows for a hash of options which on…
joshk authored
674 def extract_authentication(options)
675 if options[:user] && options[:password]
676 [options.delete(:user), options.delete(:password)]
677 else
678 nil
2fe43b6 @joshk :subdomain, :domain and :tld_length options can now be used in url_fo…
joshk authored
679 end
680 end
681
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
682 def handle_positional_args(options)
683 return unless args = options.delete(:_positional_args)
684
685 keys = options.delete(:_positional_keys)
686 keys -= options.keys if args.size < keys.size - 1 # take format into account
687
688 # Tell url_for to skip default_url_options
59296ab @miloops Refactor routing methods.
miloops authored
689 options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
7db80f8 @josh Move AC::UrlRewriter onto route set
josh authored
690 end
691
3845de0 @NZKoz Restructure routing into several smaller files. References #10835 [ol…
NZKoz authored
692 end
693 end
e0513e3 @josh Routing whitespace cleanup
josh authored
694 end
Something went wrong with that request. Please try again.