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:
# config/routes.rb
# Define a concern
concern :commentable do
resources :comments
end
# Use that concern
resources :posts, concerns: :commentable
resources :photos, concerns: :commentable
You are not limited to a single concern. A resource can use multiple concerns, too.
resources :messages, concerns: [:commentable, :attachable, ...]
You can also use these concerns in other routing blocks 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. Check out the following post if you want to learn more about Rails concerns.
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:
- The current mapper object
- 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
.
That's a wrap. I hope you found this article helpful and you learned something new. If you'd like to learn more about the Rails Router, check out my handbook below.
As always, 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 reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.