> TL;DR. Don’t use Rails concerns.


If you are an experienced Rails developer, you won’t need explanations about what a concern is. For those who are new to the framework, here is a short explanation:

The Concern is a tool provided by the ActiveSupport lib for including modules in classes, creating mixins.

Sounds great, right? Any class including our concern would be able to send emails. Unfortunately, not every example in a common Rails project is as clear as the one explained above.

In this post we’ll talk about some anti-patterns regarding Concerns, what problems can arise, and how we can solve them.

Concerns used wrong

Using Concerns to make the classes smaller

Very often, concerns are used to reduce the size of a class. This is even more common in projects using tools such as Rubocop in their CI process. In those situations, when a file exceeds a particular threshold, the quickest solution is to extract a concern. The logic is moved elsewhere and the number of lines of that class is reduced. But that reduction comes with a cost.

First of all, the refactor is purely cosmetic. Although we move the logic to a separate file, in runtime, the behaviour will still be in the original class. Modules are a form of inheritance in ruby, so the responsibilities will be still there.

Secondly, by using a concern we are losing explicitness. For instance, consider the following example

The method is quite intuitive because of its arguments, so we can assume it is implemented in the concern. But what about ? I could suspect it is provided by , but it could also be implemented in. Now imagine a class with 10 concerns, lots of method calls and not-so-meaningful names. Following the execution flow can be quite hard, and we may find ourselves doing searches to find out where's the method we are calling.

A good heuristic to find this antipattern is to look for how many classes implement a concern. If there’s only one, then we may be losing the good parts of concerns.

Bi-directional dependencies

Consider the following example:

The class depends on the concern. But, equally, the concern knows implementation details of (the and instance attributes). The problem with bi-directional dependencies can be found in any form of inheritance in which superclasses know implementation details of their subclasses.

Whenever possible, bi-directional dependencies should be avoided. The knowledge should flow one way only, and the communication should be explicitly declared via the public interface. Implicit knowledge doesn’t scale, and any future consumer of may forget to declare . So, a fixed version of the concern would be the following:

Triangular dependencies

The problem above can become bigger if the dependency goes further than bi-directional.

In the example above, depends on two concerns, one to print the contents and the other to upload them to a remote resource. But depends also on . The dependency is already defined in the class but resolved implicitly. This kind of situations are hard to deal with and should be avoided whenever possible.

Concerns used properly

It’s easy to see when something is broken. It’s not that easy to do the reverse and affirm that something has no flaws. I’d say good concerns are those without perceptible defects. We have described above some of those flaws, but you could consider your own code smells.

A good concern should be able to work in isolation, so it must be dependency-free. It should have a very concrete and limited responsibility. The kind of responsibilities for a concern should be framework or infrastructure related. That means that they shouldn’t contain business logic. Business logic is better modelled as abstractions (classes), rather than concerns. Value objects, services, repositories, aggregates or whatever artefact that fits better.

But even good concerns present software design problems. Concerns are a bit harder to test since you need more arrangement. But maybe the most important problem is that concerns promote the is a relation between our classes. With the is a relation, an object inherits behaviour directly, so more and more responsibilities are aggregated to the object as long as we keep adding concerns. True segregation of responsibilities come with has a relationship, either via composition or aggregation.

Finally, it’s hard to say what problem concerns solve. Every problem that concerns solve can be solved with composition or aggregation. Better than that, composition/aggregation solve the same problem but explicitly.

Explicit is better than implicit

The PEP-20, Zen of Python reads: Explicit is better than implicit. Implicit means you need previous knowledge. Explicitness is self-explanatory, so it makes our brain work less. No one wants to lose time looking for the place where a method has been defined.

Writing an aggregation has the same cost as writing a concern, and it makes things explicit. Using composition is a bit more costly, but when I’m looking for loose coupling or polymorphism, it’s what I prefer.