Routing Concerns

Sometimes, you have common routes that you want to reuse inside other resources and routes. For example, imagine that your application has two resources, photos and posts.

# config/routes.rb

resources :posts
resources :photos

Next, you decide that you want to allow users to add comments under both posts and photos. That means you'll want to nest comments under both resources as follows:

# config/routes.rb

resources :posts do
  resources :comments
end

resources :photos do
  resources :comments
end

This is simple enough. But, you can imagine this can get repetitive if you have few more 'commentable' resources, i.e. resources that can be commented.

To avoid this repetition, Rails lets you to declare these common routes (concerns) to reuse inside other resources and routes. For this, you'll use the concern method as follows:

# config/routes.rb

concern :commentable do
  resources :comments
end

resources :posts, concerns: :commentable
resources :photos, concerns: :commentable

# more than one concerns are allowed
resources :messages, concerns: [:commentable, :attachable, ...]

You can also use these concerns anywhere by calling the concerns method.

# config/routes.rb

namespace :blog do
  concerns :commentable
end

The underlying idea behind routing concerns is exactly similar to the concept of concerns in Rails: to group commonly used, related code together in one place.

Concerns are an important concept in Rails that can be confusing to understand for those new to Rails as well as seasoned practitioners. If you want to learn more about concerns in Rails, check out the following post:

Concerns in Rails: Everything You Need to Know

This post explains what concerns are, how they work, and how & when you should use them to simplify your code, with practical, real-world examples.

How to Pass Options to a Concern

You can also pass options to a concern by using a block. This allows you to customize the behavior of the concern for different resources. For example, you might want to limit the actions available on a resource while still using the shared logic from the concern. Here's how you can do it:

concern :commentable do |options|
  resources :comments, options
end

resources :images, concerns: :commentable

resources :published_posts do
  concerns :commentable, only: [:show]
end

In the example, the commentable concern is applied to the published_posts resource, but with a restriction: only the show action is enabled. This is done by passing the only: [:show] option to the concern within the published_posts resource block, allowing comments to be accessed only when viewing a specific published post.

A Callable Concern Object

You can implement something more specific to your application with a callable object. In general, only resort to this option if you have somewhat complicated business logic that's out of place in your routes file.

The concern object must respond to the call method, which receives two parameters:

  1. The current mapper object

  2. A hash of options to be used by this concern object.

# purchasable.rb
class Purchasable
  def initialize(defaults = {})
    @defaults = defaults
  end

  def call(mapper, options = {})
    options = @defaults.merge(options)
    mapper.resources :purchases
    mapper.resources :receipts
    mapper.resources :returns if options[:returnable]
  end
end

# routes.rb
concern :purchasable, Purchasable.new(returnable: true)

resources :toys, concerns: :purchasable
resources :electronics, concerns: :purchasable
resources :pets do
  concerns :purchasable, returnable: false
end

You can use any routing helpers you want inside a concern. If using a callable object, they're accessible from the Mapper that's passed to call.

--

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.