Concerns in Rails: Everything You Need to Know

Concerns in Rails: Everything You Need to Know

May 20, 2023

Concerns are an important concept in Rails that can be confusing to understand for those new to Rails as well as seasoned practitioners. 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.

Concerns are not about reuse. They are about comprehension and cohesion - David Heinemeier Hansson

P.S. I originally published this post last year, when I was just starting to learn Rails. Since then, I've learned a few more things and hence decided to edit, revise, polish, and re-publish it. Also, a ton of new subscribers have joined the email list, and I thought you all might find it helpful.

Many Rails applications, including the framework itself, make heavy use of concerns. However, if you are new to Rails, concerns can be confusing to understand. When I started learning Rails last year, concerns were one of the topics that took a really long time for me to understand.

The Rails guide on concerns mixes in unrelated concepts such as validations, migrations, and views, making it overwhelming to understand the purpose of concerns to someone new to Rails. The Rails API documentation doesn’t help either. It starts with the assumption that the reader is already familiar with the problem concerns are trying to solve, and goes on to provide the solution, only adding to the confusion.

So I did some reading and found the web has many interesting nuggets of concern wisdom scattered around, like this blog post from DHH demonstrating the usage of concerns, or these answers from the A May of WTFs thread from the Core Rails members explaining concerns with a metaphor of writing.

This blog post attempts to summarize these scattered pieces of information to paint a coherent picture, showing you how concerns can help you write better code.

Here’s what I want you to get from this article:

  • What is a concern?
  • Why and when should you use concerns?
  • How to use a concern?
  • The rationale behind concerns

Sounds interesting? Let's begin...

What is a Concern?

concern is a module that you extract to split the implementation of a class or module in coherent chunks, instead of having one big class body. The API surface is the same one, they just help organize the code. A concern is a regular Ruby mixin, there’s nothing else, it is not a new concept. It is plain Ruby. - Xavier Noria

A Rails concern is just a plain Ruby module that extends the ActiveSupport::Concern module provided by Rails. Here’s a concern named Taggable.

module Taggable
extend ActiveSupport::Concern

included do
# any code that you want inside the class
# that includes this concern
end

class_methods do
# methods that you want to create as
# class methods on the including class
end
end

In Ruby, when a class includes another module, it gets all the methods defined in that module as if it had defined them. You can still do this with a concern, which I repeat, is just another Ruby module.

However, turning a Ruby module into a Rails concern allows us to do the following interesting things:

  1. Include class methods in addition to instance methods
  2. Define additional code such as validations, callbacks, or constants on the including class.

A concern does this by providing the following two blocks:

included

  • Any code inside this block is evaluated in the context of the including class. If Post class includes a concern, anything inside the included block will be evaluated as if it was written inside the Post class.
  • Typically, this block is used to define Rails macros like validations, associations, and scopes. Any methods you create here become instance methods of the including class.

class_methods

  • Any methods that you add here become class methods on the including class.
  • Alternatively, you can create a nested module named ClassMethods instead of the class_methods block to define the class methods. See the API docs for an example.

For more details on evaluating the code in the context of another class, please read the following article: class_eval vs. instance_eval in Ruby

Okay, by now, you must have a pretty good understanding of what a concern is. In the next section, we'll learn why and when should you use one, instead of a plain Ruby module.

Why and When Should You Use Concerns?

Using a concern lets you extract the common logic from different classes into a reusable module.

Consider two common models: Post and Comment, which represent a blog post and its comments, just like the one you're reading right now. A post has rich text and it can have multiple comments.

In addition to the code specific to these models, both classes contain the code that handles their visibility, specifically:

  1. visible_to attribute,
  2. is_visible instance method, and
  3. all_visible class method
# post.rb

class Post < ApplicationRecord
belongs_to :author
has_many :comments, dependent: :delete_all
has_rich_text :content

validates :title, presence: true, length: { minimum: 2 }
validate :has_valid_content

# common data
attr_accessor :visible_to

# common instance method
def is_visible?
visible_to.present?
end

def has_valid_content
# some code
end

# common class method
def self.all_visible
all.select { |item| item.is_visible? }
end
end

# comment.rb

class Comment < ApplicationRecord
belongs_to :post
validates :commenter, :body, presence: true

# common data
attr_accessor :visible_to

# common instance method
def is_visible?
visible_to.present?
end

# common class method
def self.all_visible
all.select { |item| item.is_visible? }
end
end

You must have noticed the code that checks the model's visibility is duplicated in both classes. You can imagine there could be other similiar models that need to control their visibility in the same way.

It would be nice if there was a way to abstract the visibility-related code.

Concerns let you do exactly that.

How to Use a Concern?

Let’s create a Visible concern to extract the visibility-related code from the Post and Comment models. We'll put the attribute and the instance method inside the included block and the class method inside the class_methods block.

💡 The Visible concern deals with an entity’s visibility, its primary concern is to check if that entity is visible or not.

# visible.rb

module Visible
extend ActiveSupport::Concern

# The code inside the included block is evaluated
# in the context of the class that includes the Visible concern.
# You can write class macros here, and
# any methods become instance methods of the including class.
included do
attr_accessor :visible_to

def is_visible?
visible_to.present?
end
end

# The methods added inside the class_methods block (or, ClassMethods module)
# become the class methods on the including class.
class_methods do
def all_visible
all.select { |item| item.is_visible? }
end
end
end

To use this concern, you include the module as usual.

For example, if the Post model wants the visibility functionality, it includes the Visible concern. Including this concern adds the visible_to attribute, is_visible instance method, and the all_visible class method to the Post class.

Let's see how the Visible concern simplifies both Post and Comment classes.

class Post < ApplicationRecord
include Visible

belongs_to :author
has_many :comments, dependent: :delete_all
has_rich_text :content

validates :title, presence: true, length: { minimum: 2 }
validate :has_valid_content

def has_valid_content
# some code
end
end

class Comment < ApplicationRecord
include Visible

belongs_to :post
validates :commenter, :body, presence: true
end

Let's run the following test to ensure none of the existing behavior in the Post class was changed.

require "test_helper"

class PostTest < ActiveSupport::TestCase

test "Post can include the visible concern" do
post = Post.new
assert_not post.is_visible?

post.visible_to = "reader"
assert_equal "reader", post.visible_to
assert post.is_visible?

assert_equal [], Post.all_visible
end
end

It passes with flying colors.

If you're following along with me, here's your challenge: write the passing test for the Comment model that uses the Visible concern.

The Rationale Behind Concerns

At this point you might be wondering: what’s the point of all this?

If we are trying to abstract some common behavior at a central location, can’t we simply create a new class, encapsulating the query in a stand-alone object? And you’d be right.

You can create a class containing the shared code, instantiate it, and use it.

visibility_manager = Visibilty.new

visibility_manager.is_visible?(post)

However, concerns are often just the right amount of abstraction, resulting in a friendlier API. All the methods you need are right there, on the primary object, and you don’t need to introduce an intermediate object like visibility_manager to access the shared code.

Which code is more readable?

post.is_visible?

# vs.

visibility_manager.is_visible?(post)

I admit, not everyone will agree here. In fact, since writing this post, a few people raised concerns (no pun intended!) that using concerns is a bad practice, composition is better than inheritance, concerns make the code difficult to read and test, they bloat your models, and so on and so on.

Personally, I found concerns very refreshing; coming from the C#/.NET world, I was used to creating Managers, Services, Commands, etc. all the time. Now I mostly prefer concerns, as they result in cleaner APIs on the models and I haven’t run into any headaches with testing etc.

Regarding readability, Rails makes heavy use of concerns. They are everywhere! My experience reading the Rails codebase has been a delight. I’ve been in the Rails ecosystem for just over a year, and find Rails source code an absolute pleasure to read.

At first, I thought the concerns (and modules) were confusing (so much hidden code!), but now I just like to think of them as neatly organized drawers. Agree, the room (model) has too many drawers, but at least everything is neatly organized, and once I find the concerned file, I know what’s going on.

Nowadays, if I come across a new feature, I just open the Rails source code and see how it's implemented. Personally, I find reading the source much more efficient than the guides and sometimes, the API. Finally, I haven't yet come across code that was difficult to read and test due to concerns.

Also, no one is preventing you from using composition and creating services and managers and the whole lot to separate your dependencies. I have nothing against them. If you want a much more nuanced post on concerns and various debates surrounding them, I recommend you read Jason Swett's excellent article: When used intelligently, Rails concerns are great.

So if you're still confused between the two approaches, here's what I recommend: write both versions side-by-side, take a look at them both, and choose whichever one you like. It's that simple!

Here’s how DHH addresses this concern (no pun intended) ;)

So that’s another way of putting this: It’s a writing style. Like using subheads to explain subservient ideas within a broader context. You could probably extract all those subheads out, and turn them into little essays of their own. But that’s often just not the right level of extraction. - A May of WTFs

That’s what concerns are: A way to group related methods together, while still living under a single class.

It’s structured writing.


That's a wrap. I hope you liked this article and you learned something new.

If you want to go even deeper on concerns, I highly recommend you read this excellent article from Jorge Manrubia, a developer at 37signals. It's one of the best programming articles I've read in a long time: Vanilla Rails is plenty

If you want to read similar deep dives on my blog, check out these articles:

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 look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.

Sign up for my newsletter

Let's learn to become better developers.

Comments (2)

R
Ricardo

Hi Akshay, Thank you for taking the time out to teach.. it's really appreciated..

A
Akshay Khot

Thanks, Ricardo. Glad you found it helpful!

Sign in to leave a comment.