---
title: "Rails Routing -- a comprehensive look at routing"
description: "A deep dive into how Rails handles routing."
canonical_url: "https://www.bigbinary.com/blog/journey-into-rails-routing"
markdown_url: "https://www.bigbinary.com/blog/journey-into-rails-routing.md"
---

# Rails Routing -- a comprehensive look at routing

A deep dive into how Rails handles routing.

- Author: Neeraj Singh
- Published: January 29, 2013
- Categories: Rails

_Following code was tested with edge rails (rails4) ._

When a Rails application boots then it reads the `config/routes.rb` file. In
your routes you might have code like this

```ruby
Rails4demo::Application.routes.draw do
  root 'users#index'
  resources :users
  get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
  get '/logout' => 'sessions#destroy', :as => :logout
  get "/stories" => redirect("/photos")
end
```

In the above case there are five different routing statements. Rails needs to
store all those routes in a manner such that later when url is '/photos/5' then
it should be able to find the right route statement that should handle the
request.

In this article we are going to take a peek at how Rails handles the whole
routing business.

## Normalization in action

In order to compare various routing statements first all the routing statements
need to be normalized to a standard format so that one can easily compare one
route statement with another route statement.

Before we take a deep dive into how the normalization works lets first see some
normalizations in action.

## get call with defaults

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
end
```

After the normalization process the above routing statement is transformed into
five different variables. The values for all those five variables is shown
below.

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007fd05e0cf7e8
           @defaults={:format=>"jpg", :controller=>"photos", :action=>"show"},
           @glob_param=nil,
           @controller_class_names=#<ThreadSafe::Cache:0x007fd05e0cf7c0
           @backend={},
           @default_proc=nil>>
conditions: {:path_info=>"/photos/:id(.:format)", :required_defaults=>[:controller, :action], :request_method=>["GET"]}
requirements: {}
defaults: {:format=>"jpg", :controller=>"photos", :action=>"show"}
as: nil
anchor: true
```

`app` is the application that will be executed if conditions are met.
`conditions` are the conditions. Pay attention to `:path_info` in conditions.
This is used by Rails to determine the right route statement. `defaults` are
defaults and `requirements` are the constraints.

## GET call with as

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  get '/logout' => 'sessions#destroy', :as => :logout
end
```

After normalization above code gets following values

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f8ded87e740
           @defaults={:controller=>"sessions", :action=>"destroy"},
           @glob_param=nil,
           @controller_class_names=#<ThreadSafe::Cache:0x007f8ded87e718 @backend={},
           @default_proc=nil>>
conditions: {:path_info=>"/logout(.:format)", :required_defaults=>[:controller, :action], :request_method=>["GET"]}
requirements: {}
defaults: {:controller=>"sessions", :action=>"destroy"}
as: "logout"
anchor: true
```

Notice that in the above case `as` is populate with `logout` .

## root call

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  root 'users#index'
end
```

After normalization above code gets following values

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007fe91507f278
           @defaults={:controller=>"users", :action=>"index"},
           @glob_param=nil,
           @controller_class_names=#<ThreadSafe::Cache:0x007fe91507f250 @backend={},
           @default_proc=nil>>
conditions: {:path_info=>"/", :required_defaults=>[:controller, :action], :request_method=>["GET"]}
requirements: {}
defaults: {:controller=>"users", :action=>"index"}
as: "root"
anchor: true
```

Notice that in the above case `as` is populated. And the `path_info` is `/`
since this is the root url .

## GET call with constraints

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  #get 'pictures/:id' => 'pictures#show', :constraints => { :id => /[A-Z]\d{5}/ }
end
```

After normalization above code gets following values

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f8158e052c8
           @defaults={:controller=>"pictures", :action=>"show"},
           @glob_param=nil,
           @controller_class_names=#<ThreadSafe::Cache:0x007f8158e05278 @backend={},
           @default_proc=nil>>
conditions: {:path_info=>"/pictures/:id(.:format)", :required_defaults=>[:controller, :action], :request_method=>["GET"]}
requirements: {:id=>/[A-Z]\d{5}/}
defaults: {:controller=>"pictures", :action=>"show"}
as: nil
anchor: true
```

Notice that in the above case `requirements` is populated with constraints
mentioned in the route definition .

## get with a redirect

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  get "/stories" => redirect("/posts")
end
```

After normalization above code gets following values

```plaintext
app: redirect(301, /posts)
conditions: {:path_info=>"/stories(.:format)", :required_defaults=>[], :request_method=>["GET"]}
requirements: {}
defaults: {}
as: "stories"
anchor: true
```

Notice that in the above case `app` is a simple redirect .

## Resources

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  resources :users
end
```

After normalization above code gets following values

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41a315c0
           @defaults={:action=>"index", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41a31598 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users(.:format)", :required_defaults=>[:action, :controller], :request_method=>["GET"]}
defaults: {:action=>"index", :controller=>"users"}
as: "users"

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41a4ef80
           @defaults={:action=>"create", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41a4ef58 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users(.:format)", :required_defaults=>[:action, :controller], :request_method=>["POST"]}
defaults: {:action=>"create", :controller=>"users"}
as: nil

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41b63790
           @defaults={:action=>"new", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41b63768 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/new(.:format)", :required_defaults=>[:action, :controller], :request_method=>["GET"]}
defaults: {:action=>"new", :controller=>"users"}
as: "new_user"

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41a10550
           @defaults={:action=>"edit", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41a10528 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/:id/edit(.:format)", :required_defaults=>[:action, :controller], :request_method=>["GET"]}
defaults: {:action=>"edit", :controller=>"users"}
as: "edit_user"

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41f31818
           @defaults={:action=>"show", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41f317f0 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/:id(.:format)", :required_defaults=>[:action, :controller], :request_method=>["GET"]}
defaults: {:action=>"show", :controller=>"users"}
as: "user"

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d44a9bb70
           @defaults={:action=>"update", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d44a9bb48 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/:id(.:format)", :required_defaults=>[:action, :controller], :request_method=>["PATCH"]}
defaults: {:action=>"update", :controller=>"users"}
as: nil

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d41b17480
           @defaults={:action=>"update", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d41b17458 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/:id(.:format)", :required_defaults=>[:action, :controller], :request_method=>["PUT"]}
defaults: {:action=>"update", :controller=>"users"}
as: nil

app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007f9d439ddf68
           @defaults={:action=>"destroy", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007f9d439ddf40 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/:id(.:format)", :required_defaults=>[:action, :controller], :request_method=>["DELETE"]}
defaults: {:action=>"destroy", :controller=>"users"}
as: nil
```

In this case I omitted `requirements` and `anchor` for brevity .

Notice that a single routing statement `resources :users` created eight
normalized routing statements. It means that `resources` statement is basically
a short cut for defining all those eight routing statements .

## Resources with only

Here we have following route

```ruby
Rails4demo::Application.routes.draw do
  resources :users, only: :new
end
```

After normalization above code gets following values

```plaintext
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007fdf55043e40
           @defaults={:action=>"new", :controller=>"users"}, @glob_param=nil, @controller_class_names=#<ThreadSafe::Cache:0x007fdf55043e18 @backend={}, @default_proc=nil>>
conditions: {:path_info=>"/users/new(.:format)", :required_defaults=>[:action, :controller], :request_method=>["GET"]}
defaults: {:action=>"new", :controller=>"users"}
as: "new_user"
```

Because of `only` keyword only one routing statement was produced in this case.

## Mapper

In Rails `ActionDispatch::Routing::Mapper` class is responsible for normalizing
all routing statements.

```ruby
module ActionDispatch
  module Routing
    class Mapper
      include Base
      include HttpHelpers
      include Redirection
      include Scoping
      include Concerns
      include Resources
    end
  end
end
```

Now let's look at what these included modules do

## Base

```ruby
module Base
  def root (options = {})
  end

  def match
  end

  def mount(app, options = {})
  end
```

As you can see `Base` handles `root`, `match` and `mount` calls.

## HttpHelpers

```ruby
module HttpHelpers
  def get(*args, &block)
  end

  def post(*args, &block)
  end

  def patch(*args, &block)
  end

  def put(*args, &block)
  end

  def delete(*args, &block)
  end
end
```

`HttpHelpers` handles `get`, `post`, `patch`, `put` and `delete` .

## Scoping

```ruby
module Scoping
  def scope(*args)
  end

  def namespace(path, options = {})
  end

  def constraints(constraints = {})
  end
end
```

## Resources

```ruby
module Resources
  def resource(*resources, &block)
  end

  def resources(*resources, &block)
  end

  def collection
  end

  def member
  end

  def shallow
  end
end
```

## Let's put all the routes together

So now let's look at all the routes definition together.

```ruby
Rails4demo::Application.routes.draw do
  root 'users#index'
  get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
  get '/logout' => 'sessions#destroy', :as => :logout
  get 'pictures/:id' => 'pictures#show', :constraints => { :id => /[A-Z]\d{5}/ }
  get "/stories" => redirect("/posts")
  resources :users
end
```

Above routes definition produces following information. I am going to show info
path info.

```plaintext
{ :path_info=>"/":path_info=>"/photos/:id(.:format)" }

{ :path_info=>"/logout(.:format)" }

{ :path_info=>"/pictures/:id(.:format) }

{ :path_info=>"/stories(.:format)" }

{ :path_info=>"/users(.:format), :request_method=>["GET"]}

{:path_info=>"/users(.:format)", :request_method=>["POST"]}

{:path_info=>"/users/new(.:format)", :request_method=>["GET"]}

{:path_info=>"/users/:id/edit(.:format)", :request_method=>["GET"]}

{:path_info=>"/users/:id(.:format)", :controller], :request_method=>["GET"]}

{:path_info=>"/users/:id(.:format)", :request_method=>["PATCH"]}

{:path_info=>"/users/:id(.:format)", :request_method=>["PUT"]}

{:path_info=>"/users/:id(.:format)", :request_method=>["DELETE"]}
```

## How to find the matching route definition

So now that we have normalized the routing definitions the task at hand is to
find the right route definition for the given url along with request_method.

For example if the requested page is `/pictures/A12345` then the matching
routing definition should be
`get 'pictures/:id' => 'pictures#show', :constraints => { :id => /[A-Z]\d{5}/ }`
.

In order to accomplish that I would do something like this.

I would convert all path info into a regular expression and I would push that
regular expression in an array. So in this case I would have 12 regular
expressions in the array and for the given url I would try to match one by one.

This strategy will work and this is how Rails worked all the way up to Rails 3.1
.

## Aaron Patterson loves computer science

[Aaron Patterson](http://twitter.com/tenderlove) noticed that finding the best
matching route definition for a given url is nothing else but pattern matching
task. And computer science solved this problem much more elegantly and this
happens to run faster also by building an AST and walking over it.

So he decided to make a mini language out of the route definitions . After all
the route definitions , we write , follow certain rules.

And thus [Journey](http://github.com/rails/journey) was born.

In the next blog we will see how to write grammar rules for routing definitions
, how to parse and then walk the ast to see the best match .

## Links

- [Human page](https://www.bigbinary.com/blog/journey-into-rails-routing)
