A Taggable Concern in Rails

Concerns in Rails: Everything You Need to Know

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.

9 min read

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:

Sounds interesting? Let's begin...

What is a Concern?

A 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
This post explains the difference between class_eval and instance_eval methods in Ruby. If you keep getting confused between them when reading or writing Ruby code, it should clarify things a little. Consider the Greeting class, which forms the basis of other examples. class_eval class_eval eval…

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
A common critique of Rails is that it encourages a poor separation of concerns. That when things get serious, you need an alternative that brings the missing pieces. We disagree.

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

The Definitive Guide to Rack for Rails Developers
The word Rack actually refers to two things: a protocol and a gem. This article explains pretty much everything you need to know about Rack as a Rails developer. We will start by understanding the problem Rack solves and move to more advanced concepts like middleware and the Rack DSL.
Understanding Authenticity Tokens in Rails
This post explores CSRF vulnerability and how Rails mitigates it using authenticity tokens. We will will learn why they’re needed, how they’re generated, how Rails uses them to verify the requests, and how to disable them for specific requests.
Let’s Learn Ruby on Rails + Hotwire by Building a To-Do List
In this article, we’ll learn Ruby on Rails and Hotwire by building a to-do list from scratch. It shows how to build single-page web applications using traditional web architecture (server-rendered HTML), without the complexity of modern JavaScript frameworks like React.

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.