How Rails Concerns Work and How to Use Them
Tags: rails, ruby
Published: 2021-12-20 07:16:34

_**Note: I've since updated this article with more details. You can read it on my blog at [Concerns in Rails: Everything You Need to Know](https://akshaykhot.com/how-rails-concerns-work-and-how-to-use-them/)**_

Many Rails applications, including the framework itself, make heavy use of concerns. However, if you are just getting into Rails, concerns can be confusing to understand. **Why do we need them, how do they work, and when do we use (or not use) them?** When I started learning Rails a few months ago, concerns were one of the topics that took a long time for me to understand. 

The Rails [API documentation](https://api.rubyonrails.org/classes/ActiveSupport/Concern.html) 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 research and found the web has many interesting nuggets of concern wisdom scattered around, like [this](https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns) blog post from DHH demonstrating the usage of concerns, or [these](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/10?u=software_writer) [answers](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/20?u=software_writer) from the [A May of WTFs thread](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619) from the Core Rails members [explaining](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/21?u=software_writer) concerns with a [metaphor of writing](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/22?u=software_writer).

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.

### Problem Concerns Solve

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

Consider the following two models: **Post** and **Comment**. In addition to the code specific to them, both models contain the code that handles their visibility, i.e. the `visible_to` attribute, `is_visible` instance method, and `count_all_visible` class method. There could be many other models that need to control their visibility in a similar way. It would be nice if there was a way to abstract this code.

```ruby
# 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

  attr_accessor :visible_to

  def is_visible?
    visible_to.present?
  end

  def has_valid_content
    # some code
  end

  def self.count_all_visible
    all.select { |item| item.is_visible? }
  end
end

# comment.rb

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

  attr_accessor :visible_to

  def is_visible?
    visible_to.present?
  end

  def self.count_all_visible
    all.select { |item| item.is_visible? }
  end
end
```

### What is a Concern?

> A lot of concerns are simply Ruby modules that are mixed in, with no additional stuff added. And then for a few cases, like when your concern includes both class and instance methods, or you want to call class methods on mixin, you can use extend ActiveSupport::Concern. - DHH

A Rails [Concern](https://api.rubyonrails.org/classes/ActiveSupport/Concern.html) is a module that extends the `ActiveSupport::Concern` module. Concerns allow us to include modules with methods (both instance and class) and constants into a class so that the including class can use them.

A concern provides two blocks:

1. *`included`*
   - The code inside the included block is evaluated in the context of the including class. For example, if `Post` includes a concern, anything inside the `included` block will be evaluated as if it was written inside `Post`. 
   - You can write class macros (validations, associations, scopes, etc.) here, and any methods become instance methods of the including class.
2. *`class_methods`*
   - The methods added inside this block become the class methods on the including class. 
   - Instead of the `class_methods` block, you can create a nested module named `ClassMethods`. 

Here's a concern named **Taggable**. 

```ruby
module Taggable
  extend ActiveSupport::Concern

  included do

  end

  class_methods do

  end
end
```

> 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](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/10?u=software_writer)

### How to Use a Concern?

Let's create a `Visible` concern to extract the visibility-related code from the `Post` and `Comment` models. In general, the `Visible` concern deals with an entity's visibility, concerning if it is visible or not. 

```ruby
# 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 count_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 wanted the `visibility` functionality, it would include the `Visible` concern. 

```ruby
class Post
  include Visible

end
```

Including this concern adds the `visible_to` attribute, `is_visible` instance method, and the `count_all_visible` class method to the `Post` class. 

The following test illustrates this. 

```ruby
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.count_all_visible
  end
end
```

### Why do we need Concerns?

You might wonder: *what's the point of all this cleverness?* 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. 

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

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. As you saw, the Accessor role starts life as literally just two methods! It doesn’t warrant being turned into it’s own class. It doesn’t have an independent center of gravity. - [A May of WTFs](https://discuss.rubyonrails.org/t/helping-devs-understand-concerns-faster/74619/22?u=software_writer)


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

**It's structured writing.**