Understanding the Match Method
In this section, we will explore how the match
method works 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.
The most basic way to define routes in Rails is to use the match
method, which forms the core of the Router DSL. To 'really' understand the Rails Router API, it's essential to learn this method. 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.
For example, the following route instructs Rails to direct any POST
request with the URL /posts/publish
to the publish
action method on the PostsController
class.
match "posts/publish/:id", controller: "posts", action: "publish", as: "publish", via: :post
This single route contains multiple components that we'll examine in this post as well as in the future chapters.
- the incoming URL
path
to match:posts/publish/:id
- which controller and action this request should be routed to, i.e. the
publish
action onPostsController
class - the name for this route:
publish
, it generates URL helpers namedpublish_url
andpublish_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:
post "posts/publish/:id", to: "posts#publish", as: "publish"
And it keeps getting better.
Match Internals
In its essence, the match
method matches a URL pattern to one or more routes. Here's the basic API of the match
method, which is defined inside the Mapper
class.
# actionpack/lib/action_dispatch/routing/mapper.rb
def match(path, options = nil)
end
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 option, with the hash version was recently deprecated. To learn more, check out this pull request.
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.
Here are some usage examples of the match
method.
match 'photos/:id', to: 'photos#show', via: :get
match 'photos/:id', controller: 'photos', action: 'show', via: :get
match 'photos/:id' => 'photos#show', via: :get # deprecated!
All the three routes above do the same thing, dispatch the HTTP GET request at /photos/:id
to the show
action method on the PhotosController
class.
In its simplest and most explicit form, you supply a string URL pattern along with the name of the controller and the action via options.
match "photos/:id", controller: "photos", action: "show", via: :get
If you don't want to pass them separately, a string in the form of controller#action
is also allowed with the :to
option.
match "photos/:id", to: "photos#show", via: :get
Finally, the hash version simplifies this even further (but is now deprecated!):
match "photos/:id" => "photos#show", via: :get
Matching Multiple HTTP Verbs
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.
match "photos/:id", to: "photos#handle", via: [:get, :post]
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 Method
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.
Keep in mind: Any custom data that doesn't match the existing option gets passed as-is to the params
hash, which you can access in the controllers, using the params
hash.
:controller
and :action
The controller
and action
keys are used together. They specify the name of the controller and the action you want to route this request to.
match "home", controller: "application", action: "home", via: :get
:to
If you don't want to pass the controller and action separately, the :to
option lets you pass the controller#action
as a string.
match "home", to: "application#home", via: :get
Note: You're not restricted to using only controller actions for incoming requests. The :to
option also allows you to point to a Rack endpoint, i.e. any object that responds to a call
method. This is really useful for quickly testing routes without creating new controllers, actions, and views.
match "path", to: -> (env) { [200, {}, ["Success!"]] }, via: :get
match "path", to: RackApp, via: :get
The Rails router can dispatch an HTTP request to a Rack endpoint, either in your application or within a gem. This is useful when you want to provide a well-isolated web UI or front-end to the users of your gem. To learn more about forwarding a request to a Rack application, check out the following post: How to Route an Incoming URL to a Rack Application in Rails.
:via
Lists the HTTP verb for this route. You have to pass at least one HTTP verb, for security-related reasons.
match 'path', to: 'controller#action', via: :get
match 'path', to: 'controller#action', via: [:get, :post]
match 'path', to: 'controller#action', via: :all
Alternatively, you can use the shorthand helper methods such as get
, post
, and so on which implicitly provide the HTTP verb.
module
Use this if you want namespaced controllers. The following route directs the request with /posts
URL to Blog::PostsController#index
action.
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
.
match 'posts', to: 'posts#index', as: 'blog', via: :get
We'll learn more about named routes in a future lesson.
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?
.
We'll learn more about route constraints in a future lesson.
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'.
match '/courses/publish(/:author)', to: 'courses#publish', defaults: { author: 'Akshay' }, via: :get
For a complete list of options, check out the API docs for the match
method. Most of the routing methods you'll use are just shorthands using various combinations of the match
method's options.
param
In the resourceful routes (which we'll learn later), :id
is the name of the dynamic segment used to generate the routes. The param
option overrides this default resource identifier. You can access the value of this dynamic segment using param[<:param>]
.
resources :posts, param: :slug
The posts
resource will contain :slug
as the dynamic segment, e.g. posts/:slug
.
To construct the URL, override the ActiveRecord::Base#to_param
method.
class Post < ApplicationRecord
def to_param
title.parameterize
end
end
post = Post.find_by(title: "Ruby on Rails")
post_path(post) # "/posts/ruby-on-rails"
Next, we'll learn about the shortcuts Rails provides, so you don't need to use the common options such as :via
, :module
, etc. 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! As you may already know, Rails provides simplified shorthand methods like get
and post
which implicitly assume the HTTP verb, inferred from the method name. For example, the following three routes mean the same thing.
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
andput
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, dynamic segment keys.
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. You can also subscribe to my blog to receive future posts via email.