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

Introduction

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:

module Emailable
include ActiveSupport::Concern
def deliver(email)
# send email here...
end
end
class Document
include Emailable
def archive
@archived = true
deliver({to: 'me@mydomain.com', subject: 'Document archived', body: @content})
end
end

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.

class Document
include Emailable, Storable, Erasable
def archive
@archived = true
deliver({to: 'me@mydomain.com', subject: 'Document archived', body: @content})
remove
end
end

Bi-directional dependencies

Consider the following example:

module Printable
include ActiveSupport::Concern
def print
raise UnknownFormatError unless ['pdf', 'doc'].include?(@format)
# do print @content
end
end
class Document
include Printable
def initialize(format, content)
@format = format
@content = content
end
def export
# ...
print
end
end
module Printable
include ActiveSupport::Concern
def print(format, content)
raise UnknownFormatError unless ['pdf', 'doc'].include?(format)
# do print content
end
end
class Document
include Printable
def initialize(format, content)
@format = format
@content = content
end
def export
# ...
print(@format, @content)
end
end

Triangular dependencies

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

module Connectable
include ActiveSupport::Concern
def connect_to(device)
# ...
end
end
module Printable
include ActiveSupport::Concern
def print(format, content)
connect_to(Printer::lookup)
# ...
end
end
class Document
include Connectable, Printable
def export
# ...
print(@format, @content)
end
def upload_to(remote_resource)
connect_to(remote_resource)
#...
end
end

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.

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.

# Aggregation
class Document
def archive
@archived = true
Mailer.deliver({to: 'me@mydomain.com', subject: 'Document archived', body: @content})
Store.remove
end
end
# Composition
class Document
def initialize(mailer, store)
@mailer = mailer
@store = store
end
def archive
@archived = true
@mailer.deliver({to: 'me@mydomain.com', subject: 'Document archived', body: @content})
@store.remove
end
end