Excerpt

Do you ever get the feeling that you know a topic is important and that you should know well, but you only understand it superficially? Ever since I got into Rails, I've had that feeling with the Rails router.
Transitioning from ASP.NET, I knew the concept of routing and got comfortable with the router after reading the docs. However, at the back of my mind, I've always had that nagging feeling that I don't REALLY understand the complete picture. I could make the basic routes and resources to get by for the simple CRUD tasks, but whenever I wanted to do something complicated, I found myself fumbling in dark.
Well, after over a year of fumbling and trying to skim the docs and StackOverflow to just learn enough to get by, I decided to dig deeper and REALLY understand how Routing works in Rails.
I've tried to summarize everything I learned in this one giant article. It's over 5,000 words, so don't expect to finish it in one sitting. However, if you can stick through it and read it till the end, I can pretty much guarantee that you'll have a much better understanding of how routing works in Rails. You'll also learn a few nice tips and tricks to clean up and simplify your existing routes right away.
### What You'll Learn:
1. What is Routing?
2. Routing in Rails
3. Digging Deeper: Router Internals
4. A Short Detour: the instance_exec Method
5. The match Method and its Options
6. HTTP Method Shorthands
7. Understanding the Segment Keys
8. The Magic of Named Routes
9. Resourceful Routes
10. Non-Resourceful Custom Routes
We'll start with the basics, by understanding the purpose of routing in web applications and the role of the Rails router. Then we'll open the hood (i.e. the Rails codebase) and take a peek behind the scenes to understand how Rails implements the routing mechanism.
Then, we'll learn about the match method, which forms the core of most of the routing shorthand methods in Rails, such as get, post, etc. It leads us into covering a few fundamental routing topics such as segment keys and named routes.
Once we've learned the fundamentals, we'll be ready to tackle the practical routing topics, which you'll use day-to-day in your Rails apps. They include the concept of resources and RESTful routes, which drastically reduce the amount of code you write by following convention-over-configuration. We'll wrap up by learning the syntactic sugar provided by router to make it easy to create more expressive routes.
As it goes with all the posts on this blog, I'm writing to clarify my understanding and act as a reference for the future me, but I really hope that you, the reader, find my writing useful in gaining a deeper understanding of the router.
There's a little metaprogramming involved along the way, but don't worry, it's actually very simple to understand. If you need a quick primer on metaprogramming, check out my notes on the Metaprogramming Ruby book.


Alright, enough talk. Let's get started. We've got a lot of ground to cover.
## What is Routing?
Before answering that, let me ask another question: What happens when someone visits your application? To keep things simple, I'll ignore the load balancers, web servers, proxy servers, etc. and just assume the request hits your Rails application server.
What happens when that request reaches your application?
The very first thing that needs to happen is Rails needs to figure out which part of your codebase it should route that request to, so you can return a meaningful response to the browser. And therein lies the answer to the question: What is Routing?
In simple terms,
> Routing is the process that determines what code needs to run based on the URL of the incoming request.
Router is one of the first components to run when an HTTP request hits your application. It inspects the incoming URL and directs it to the appropriate Ruby code. Typically, this code will be an action method inside a Rails controller.

The Rails Router
As you can see, the router is the first point of contact for a request inside your Rails application.
After inspecting the URL, the router decides to either:
1. forward the request to a controller action
2. redirect the request to another URL inside or outside the application
3. filter and reject the request based on pre-defined constraints
If the router can't find a route that matches the incoming request, it throws an error.
You can think of the router as a black box with a clear input and an output. Upon receiving the incoming HTTP request URL as an input, the router outputs the controller action where that request must be dispatched.
The Rails Router
However, the router serves a dual purpose in Rails. In addition to routing the incoming request to a controller action, it can also dynamically generate the URLs for you, so you don't need to hard-code string URLs throughout your app. Not only this approach is more maintainable, it also reads well and makes your codebase more expressive.
We'll explore the named routes in a later section of this post.
## Routing in Rails
To configure the router, you add specific rules (called routes) in the config/routes.rb file. These routes tell the router how to map incoming URLs to specific controller actions.
When a request arrives, the router finds the route which matches the URL pattern. It does this by going through the routes in the order they're defined in the routes.rb file. As soon as it finds a matching route, the search ends.
For example, the following route instructs Rails to direct any POST request with the URL posts/publish to the publish method on the PostsController class.
```plain text
match "posts/publish/:id", controller: "posts", action: "publish", as: "publish", via: :post
```
> Note: If you were expecting to see a shorthand route like
```plain text
get posts/publish => posts#publish
```
, I'm intentionally taking the difficult and longer route (no pun intended!) with the
```plain text
match
```
method, as it will give you a deeper and better understanding of routing.
This single route contains multiple components that we'll examine in this post.
- the incoming URL path to match: posts/publish
- which controller and action this request should be routed to, i.e. the publish action on PostsController class
- the name for this route: publish, it generates URL helpers named publish_url and publish_path that you can use in your code.
- HTTP verb this route corresponds to :post
- segment key :id which acts as a variable placeholder for the id of the post.
As you can see, the router offers a rich domain-specific language (DSL) to express a lot of information with a concise syntax. And this is still a long-form version that you'll rarely use!!
As you'll soon learn, Rails uses convention-over-configuration to even shorten the above route without affecting its expressiveness.
For example, the above route could be simplified to:
```plain text
post "posts/publish/:id" => "posts#publish", as: "publish"
```
And it keeps getting better.
So that was a brief introduction to the routing process and the Rails router. Now let's inspect the internals of the router to understand what's going on behind the scenes. Trust me, this will be really useful as you learn more advanced features of the router.
## Digging Deeper: Router Internals
Let's revisit the config/routes.rb file we saw earlier.
When you create a new Rails app, it automatically creates a /routes.rb file in the config directory. All it contains is a single call to the draw method on the object returned by the Rails.application.routes method. Additionally, it takes a block containing the routes for your application.
```plain text
Rails.application.routes.draw do
get "posts/publish": "posts#publish", as: "publish"
end
```
To understand how the above method works, we need to take a short detour and understand the instance_exec method, which is defined on the BasicObject class.
### Understanding How instance_exec Works
The instance_exec method takes a block and runs that block in the context of the object on which it's called. Inside the block, it sets self to that object, so you can access its instance variables and methods.
```plain text
class Company
def initialize(name, product)
@name = name
@product = product
end
def info
"#{@name}: #{@product}"
end
end
microsoft = Company.new 'Microsoft', 'Excel'
microsoft.instance_exec do
puts info # access instance method
puts @name # access instance variable
end
# Output:
# Microsoft: Excel
# Microsoft
```
You might wonder what's the purpose of using instance_exec when you can just call the method directly on microsoft. You are right in this example, but, as we'll soon see, the instance_exec method allows you to pass a block and run it later in the context of an object, which is a really useful technique for creating special-purpose DSLs.
Not sure what I mean? let's revisit the routes.rb file.
```plain text
Rails.application.routes.draw do
end
```
The Rails.application is an instance of the Application class which inherits from the Engine class. The Engine class has a routes method which returns an instance of the RouteSet class.
```plain text
# railties/lib/rails/engine.rb
def routes(&block)
@routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config)
# ...
@routes
end
```
It means that the block passed to the draw method in the routes.rb file is ultimately received by the draw method on the RouteSet class. This method passes that block to the eval_block method, as seen below.
```plain text
# actionpack/lib/action_dispatch/routing/route_set.rb
def draw(&block)
# ...
eval_block(block)
# ...
end
def eval_block(block)
mapper = Mapper.new(self)
if default_scope
mapper.with_default_scope(default_scope, &block)
else
mapper.instance_exec(&block)
end
end
```
> If this is already started to get overwhelming and you feel like giving up, DON'T. Clone the Rails repo, open it in the editor, and trace the call stack with me. It's a fun exercise and you'll learn a lot along the way.
As you can see, the eval_block method first creates an instance of the ActionDispatch::Routing::Mapper class and executes the block within the context of that instance.
What it means, is that any code we write inside the block passed to the Rails.application.routes.draw method will be evaluated as if it was written inside the Mapper class.
For example, the two pieces of code below are similar. However, the first version just reads better. It feels like a programming language specifically designed for the routing domain.
```plain text
Rails.application.routes.draw do
root 'application#index'
get 'posts/publish', to: 'posts#publish'
end
# is similar to
routes = ActionDispatch::Routing::RouteSet.new
mapper = Mapper.new(routes)
mapper.root 'application#index'
mapper.get 'posts/publish', to: 'posts#publish'
```
This also means that whenever you see a method in your routes file, you know where to look for its definition. It's the ActionDispatch::Routing::Mapper class. For example, the commonly used get method is defined in the Mapper::HttpHelpers module.
This gives us a solid base to explore the Rails Routing API. We'll start our investigation with the match method.
## The match Method
To understand the Rails Router API, it's essential to learn the match method, which forms the core of the Router DSL. All helper methods like get and post use match under the hood. Additionally, most of the shorthand syntax such as scopes and constraints use the options provided by match behind the scenes.
Once you really understand the match method and its options, the rest of the routing methods and shorthands become very easy to understand.
Here's the basic API of the match method, which is defined inside the Mapper class.
```plain text
match(path, options)
```
The first parameter path tells the router what URL pattern you wish to match. It can be either a String or a Hash.
- The string version specifies the URL pattern, e.g. /posts/publish/:id
- The hash matches the URL pattern to a controller#action pair, like '/posts/1' => 'posts#show'.
The second parameter options is a hash containing additional data required by the router to decide where it should redirect this request. You can also pass any custom data, which gets passed to the params hash accessible in the controllers.
In its simplest and most explicit form, you supply a URL pattern along with the name of the controller and the action. If you don't want to pass them separately, a string in the form of controller#action is also allowed with the :to option.
```plain text
match 'photos/:id', controller: 'photos', action: 'show', via: :get
match 'photos/:id', to: 'photos#show', via: :get
match 'photos/:id' => 'photos#show', via: :get
```
As we'll learn later, you'll often use shortcuts like get and post instead of directly using the match method. However, match comes in handy when you want to match a route for multiple HTTP verbs.
```plain text
match 'photos/:id', to: 'photos#show', via: [:get, :post]
```
Also note: the :via option is mandatory for security-related reasons. If you don't pass it, the router raises an ArgumentError.
### Options Available to Match
The options hash helps the router identify the action to which it should pass the incoming request. Here are some of the important options that you can provide to the match method.
> Any custom data that doesn't match the existing option gets passed as-is to the
```plain text
params
```
controller
The name of the controller you want to route this request to.
action
The name of the action you want to route this request to. The controller and action keys are used together.
to
If you don't want to pass the controller and action as separate options, the :to option lets you pass the controller#action as a string.
> Note: You're not restricted to using only controller actions for incoming requests. The
```plain text
:to
```
```plain text
call
```
```plain text
match 'path', to: 'controller#action', via: :get
match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
match 'path', to: RackApp, via: :get
```
If you're curious to learn more about Rack, check this out:

via
Lists the HTTP verb for this route. You have to pass at least one HTTP verb.
```plain text
match 'path', to: 'controller#action', via: :get
match 'path', to: 'controller#action', via: [:get, :post]
match 'path', to: 'controller#action', via: :all
```
module
Use this if you want namespaced controllers. The following route directs the request with /posts URL to Blog::PostsController#index action.
```plain text
match '/posts', module: 'blog', controller: 'posts', action: 'index', via: :get
```
as
This option provides the name for the generated URL helpers. The following route creates the helpers named blog_path and blog_url.
```plain text
match 'posts', to: 'posts#index', as: 'blog', via: :get
```
constraints
If you want to put more restrictions on what URL patterns a route should match, provide this option with a hash of regular expressions. It can also take an object responding to matches?.
```plain text
match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
```
defaults
Sets the default value for a parameter. The following route will set the value of params[:author] to 'Akshay'.
```plain text
match '/courses/publish(/:author)', to: 'courses#publish', defaults: { author: 'Akshay' }, via: :get
```
Most of the routing methods you'll use are just shorthands using various combinations of the match method's options.
Next, we'll learn about the shortcuts Rails provides you so you don't need to use the common options such as :via all the time.
## HTTP Method Shorthands
So far, we've been using the match method, passing the HTTP verbs like :get and :post with the :via option. Can we do better? 🧐
YES! Rails provides simplified shorthand methods like get and post which implicitly assume the HTTP verb. For example, the following three routes mean the same thing.
```plain text
match 'posts', to: 'posts#index', via: :get
# ==
get 'posts', to: 'posts#index'
# OR
get 'posts' => 'posts#index'
```
There is a method for each HTTP verb.
- get
- post
- patch and put
- delete
- options
All of them accept the exact same options that we saw for the match method. For the remaining post, I'll use the above shorthands instead of the more explicit match method, unless needed.
Next, we'll learn another fundamental topic in routing, segment keys.
## Segment Keys
So far, we've seen route paths such as posts\:id. If you're new to routing, you might be wondering why there are symbols inside the URL.
These symbol-like terms are called segment keys, which make your URLs dynamic. What does that even mean? It means that they allow the router to match both photos/1, photos/2 and photos/n, where n can be anything.
> A segment key is just a variable prefixed with a colon, just like a symbol, inside a URL pattern. It lets you capture the value inside the URL.
Instead of hard-coding a URL which matches exactly one URL, segment keys allow you to match more than one URL. You can access the values of those keys in the params hash, e.g. params[:id].
```plain text
get '/posts/:slug', to: 'posts#show'
```
No, not this slug
💡
A Slug is the unique identifying part of a web address.
When you make a GET request to the URL matching the above route, e.g. /posts/understanding-router, Rails sets params[:slug] to understanding-router. You can access the slug value inside the controller.
```plain text
class PostsController < ApplicationController
def show
@post = Post.find_by(slug: params[:slug])
end
end
```
Note: If you use segment keys in a route, you have to provide their values when generating the URLs with the Rails helpers.
```plain text
<%= link_to 'Understanding Rails', controller: 'posts', action: 'show', slug: 'understanding-rails' %>
```
But what if some of my URLs don't need a segment key?
You can mark a segment key as optional by wrapping it in parentheses. The following route matches both /posts/understanding-rails and posts.
```plain text
get '/posts(/:slug)', to: 'posts#show'
```
That's nice, but how can I restrict the slugs to only be text and not numbers?
This is where the constraints option that we saw earlier, comes into the picture. You can add restrictions on the segment keys by providing a constraint.
The following route matches only those URLs where slug is a word consisting of letters between a to z.
```plain text
get '/posts/:slug', to: 'posts#show', constraints: { slug: /[a-z]+/ }
# or you can also use a shorthand
get '/posts/:slug', to: 'posts#show', slug: /[a-z]+/
```
But I also want to access the request object...
For more powerful constraints, you can pass a lambda to :constraints which receives the request object.
```plain text
get '/posts/:slug', to: 'posts#show', constraints: ->(req) { req.params[:slug].length < 10 }
```
But, my constraint logic is too complicated, and I hate putting it right there inside the route...
Rails allows you to extract the complex logic to a class that has a matches(request) method on it. Put that class inside the app/constraints directory, and Rails will pick it up without a hitch.
```plain text
get '/posts/:slug', to: 'posts#show', constraints: SlugLength.new
# app/constraints/slug_length.rb
class SlugLength
def matches?(request)
request.params[:slug].length < 5
end
end
```
Don't forget to return a boolean result from the method, depending on whether the URL matches the pattern.
Alright, that's enough about the segment keys. Next, we'll tackle the named routes, which give you sweet helper methods to generate the URLs on the fly.
## Named Routes
Consider the following route.
```plain text
get 'post/:id', to: 'posts/show'
```
When you make a request to post/10, the router invokes the show action on the PostsController class.
What if you want to link to a post? How would you go about creating the URL?
Well, the simplest solution is to interpolate it, passing the post id.
```plain text
link_to post.title, "/post/#{post.id}"
```
But hard-coding URLs into your application is not always a good idea. When your URL changes, you'll have to search the whole codebase to find and replace the old URLs. If you miss one, your code won't complain until the users click the link, only to be shown an error page.
To fix this, you could provide the controller and action name along with the post id, so Rails could infer the URL.
```plain text
link_to post.title, controller: 'posts', action: 'show', id: post.id
```
However, there's an even better way with the named routes, where you can name a route by providing the :as option.
When you name a route, Rails will automatically generate helper methods to generate URLs for that route. These methods are called name_url or name_path, where name is the value you gave to the :as option.
Let's rewrite the above route and give it a name.
```plain text
match '/post/:id', to: 'posts#show', as: 'article'
```
The router creates two methods named: article_path and article_url. The difference between the two methods is that article_path returns only the path portion of the URL, whereas article_url returns the complete URL, which includes the protocol and domain.
```plain text
article_path -> `/posts/4`
article_url -> `blog.com/posts/4` or `localhost:3000/posts/4`
```
Once you have named a route, you can use it as follows:
```plain text
link_to post.title, article_path(id: post.id)
```
It's less code to write, and it also avoids the problem of hard-coding the URL.
In fact, we can skip the id key, and just pass its value.
```plain text
link_to post.title, article_path(post.id)
```
We can go even further. Rails allows you to pass any object to the named helper methods as long as those objects have a to_param method on it.
```plain text
class Question
def to_param
'100'
end
end
article_path(Question.new) # '/post/100'
```
What's more, all the active record objects have a to_param method out of box, which returns the object's id (you can customize this).
```plain text
link_to post.title, article_path(post)
```
This version is more readable, concise, and also avoids the problem of hard-coded URLs. Pretty cool, right?
Now we're going to tackle one of the most practical concepts in routing that you'll use every day on your Rails apps: resources.
## Resourceful Routes
The concept of resources is very powerful in Rails. With a single call to a method named resources, Rails will generate seven different routes for you, saving you a lot of typing. But saving a few keystrokes is just the cherry on top. The biggest benefit of using resourceful routing is that it provides a nice organizational structure for your application. Let's learn how.
What's a Resource?
A resource, like an object, is one of those concepts that's difficult to put into words. You intuitively understand them, but you can't express them.
I'll stick to how Roy Fielding, the inventor of REST, defined them in his dissertation.
> A resource
R
M
R
(t)
t
Is your head spinning? 🤯 Mine too. Let's try the other definition he gives in the same section.
> A resource is the key abstraction of information. Any information that can be named can be a resource.
Much better, right? 💡 I think that definition captures the essence of resources really well.
Any concept in your application domain can be a resource: an article, a course, or an image. It doesn't have to be a physical entity, but can be an abstract concept such as a weather forecast or an SSH connection to connect to a remote server.
The main point is: you have total freedom in choosing whatever idea you are using in your application as a resource.
But Why do I need a Resource?
Good question. You might be wondering what this discussion of resources has to do with routing. Well, resources are fundamental to routing. They allow you to reduce seven different routes into a single method call.
If you've used any software application, you must have noticed that most apps have a few common patterns. Most of the time, you're:
1. Creating some data (publishing a post, uploading an image)
2. Reading that data (viewing the tweets, listening to podcasts)
3. Updating the data (changing source code on GitHub, editing the post)
4. Deleting the data (removing a folder from Dropbox, deleting your profile picture)
No matter how fancy your application is, I guarantee it will have some form of these four actions that your users can take. Collectively, they're called CRUD which stands for Create, Read, Update, and Delete.
💡
A resource is an object that you want users to be able to access via URI and perform CRUD operations.
For each of these actions, your application should have a route. Assuming you're building a course platform, your routes file might have the following routes.
```plain text
post 'courses' => 'courses#create'
get 'course' => 'course#show'
patch 'courses/:id' => 'courses#update'
delete 'courses/:id' => 'courses#destroy'
```
In addition to these four routes, you'll typically have three more:
- one to see all the courses
- two to fetch the pages that let the users create and modify the courses.
```plain text
get 'courses' => 'courses#index'
get 'courses/new' => 'courses#new'
get 'courses/:id/edit' => 'courses#edit'
```
So we have a total of seven routes that are common to most resources in most web applications. If you are building a blog, you'll have these seven routes for posts. If you're building Instagram, you'll have the same routes for images.
No matter the topic, you'll most likely have some subset of these seven routes for each resource.
Since this pattern is so common, Rails introduced the concept of resourceful routing, whereby calling one method named resources and passing the name of the resource, i.e. :post, :course, :image, etc. you get these seven routes for free. Rails will automatically generate them for you.
```plain text
resources :courses
```
In addition, resourceful routing also generates sensible names for these routes. For example, you'll have names like course_path, edit_course_path at your disposal without providing the :as option.
The following table summarizes what you get with a single call to resources. The values under the Prefix column represent the prefix of the route's name, e.g. new_course gives you new_course_path and new_course_url helpers, and so on.
```plain text
➜ bin/rails routes -g course
Prefix Verb URI Pattern Controller#Action
courses GET /courses(.:format) courses#index
POST /courses(.:format) courses#create
new_course GET /courses/new(.:format) courses#new
edit_course GET /courses/:id/edit(.:format) courses#edit
course GET /courses/:id(.:format) courses#show
PATCH /courses/:id(.:format) courses#update
PUT /courses/:id(.:format) courses#update
DELETE /courses/:id(.:format) courses#destroy
```
> Resourceful routing allows you to quickly declare all of the common routes for a given resource. A single call to
```plain text
resources
```
```plain text
index
```
```plain text
show
```
```plain text
new
```
```plain text
create
```
```plain text
edit
```
```plain text
update
```
```plain text
destroy
```
In addition to having multiple resources, there's also a singular form of resourceful routes. It represents a resource that only has one, single form. For example, a logged-in user's profile. You can use the resource method for this.
```plain text
resource :profile
```
It creates all the above routes, minus the one that shows all profiles. Since we only have one, there's no point in displaying all.
What if I don't have all seven routes?
Sometimes, you don't want all seven routes that resources method creates for you. In such cases, you can pass the only or except options to filter the ones you need or don't need.
```plain text
resources :courses, only: [:index, :show]
resources :courses, except: [:delete]
```
You can even nest multiple resources, but I'll leave that topic for a different post, since this one is already getting bigger than I anticipated.
## Non-Resourceful Custom Routes
A single call to resources in the routes.rb file declares the seven standard routes for your resource. What if you need additional routes?
Don't worry, Rails will let you create custom routes that don't fit one of the above seven routes with the member and collection routes. The member route only applies to a single resource, whereas collection will apply to a group of resources.
Member Routes
Let's assume that your users need to see a preview of the course before purchasing it, and you need to expose that route on the courses/:id/preview URL. How should you go about it?
Here's how you define the preview route on the CoursesController using the member block.
```plain text
resources :courses do
member do
get 'preview'
end
end
```
The router adds a new route that directs the request to the preview action on the CoursesController class. The remaining routes remain unchanged.
It also passes the course id in params[:id] and creates the preview_course_path and preview_course_url helpers.
```plain text
preview_course GET /courses/:id/preview(.:format) courses#preview
```
If you have a single member route, use the short-hand version by passing the :on option to the route, eliminating the block.
```plain text
resources :courses do
get 'preview', on: :member
end
```
Collection Routes
Let's say you want to search all courses and you want to expose it on the courses/search endpoint. You can add a new route for the collection of courses with the collection block.
```plain text
resources :courses do
collection do
get 'search'
end
end
```
This adds the following new route. It will also add a search_courses_path and search_courses_url helpers.
```plain text
search_courses GET /courses/search(.:format) courses#search
```
If you don't need multiple collection routes, just pass :on option to the route.
```plain text
resources :courses do
get 'search', on: :collection
end
```
This will add the same route as above.
To learn more about custom routes, check out this article:

As we saw, the Rails router is very powerful and highly flexible. Routing is also a two-way street: match URLs to controller actions, and generate URLs dynamically. Additionally, the router uses powerful conventions such as resourceful routing to automatically generate sensible routes for you.
Alright, I will stop here. There are a few other shortcuts and helper methods that we didn't cover in this article, but I've already written more than 5000 words non-stop, and this article is way, wayy, wayyy bigger than what I first thought it would be. If you're still reading, I sincerely hope that you learned at least one new thing about the Rails router that you didn't know before.
Expect a few posts in the upcoming weeks that will explain the remaining topics. But what we've covered so far should be more than enough for you to get started and be productive with using various routing features, both basic and advanced, in your applications.
Here're some additional resources, if you want to learn more about routing.
### Further Resources
- Rails Routing from the Outside In
- Rails API: The Mapper Class
- Routing in Laravel (to learn routing from a different framework's perspective)
- Roy Fielding's Dissertation
I hope you found this article useful and that you learned something new.
If you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.
Please subscribe to my blog if you'd like to receive future articles directly in your email. If you're already a subscriber, thank you.