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.

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 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.

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.