A Conservative Case for Concerns
Tags: rails, ruby, codequality
Published: 2020-10-06 19:55:18


There are many, many articles which condemn the use of concerns in a Rails project, and they all have valid points.
I mostly agree with the arguments, such as them causing Bi-directional dependencies and arbitrarily splitting up code into multiple files.
That said, I think a lot of these articles go too far by claiming you should never use concerns.

To be clear, if I saw a model that started with 20 concerns being included, I would be... Well, concerned.
That's why I've titles this a **conservative** case for concerns.
I think they should be used very sparingly, but not avoided like the Coronavirus.

Namely, I think when a concern does not depend on any specific methods or attributes of a model existing, doesn't contain code that is likely to evolve with business requirements, and could be used in a completely different project, then it's likely a valid use for a concern.

Recently, I was working on a project where we needed to import related CSV files.
In these files the associations were done based on the name of the record.
For example if we have a CSV file for articles, and each article belongs to an author, the CSV would have an author column which could have the name "Keeyan Nejad".
Then in the authors CSV we would have a name column which would include the names (assume for this example that all authors have a unique name).

When importing the CSV, the Author model would get a name from the name column.
Then I would import the articles, but when it would come time to associate an Article with an Author all I would have is the authors name, not the ID.
To solve this I would have to write something like this:

```ruby
Article.create(
  title: csv_row['title'],
  content: csv_row['content'],
  author: Author.find_by(name: csv_row['author'])
)
```

This isn't too bad, but then I it turns out that the Authors belong to a country, and instead of having a country ID we have the name of the country.

So to import the Authors I would have to do this:

```ruby
Author.create(
  name: csv_row['name'],
  country: Country.find_by(name: csv_row['country'])
)
```

I wanted to clean this up a bit and this is where I found a valid use for a Rails concern.
I wanted a way to associate a record by the name rather than by the ID column.

After a bit of playing around I came up with this code:

```ruby
module AssociableByName
  extend ActiveSupport::Concern

  included do
    def self.associate_by_name(model)
      define_setter_for_model(model)
    end

    def self.define_setter_for_model(model)
      define_method("#{model}_name=") do |reference|
        association_class = self.class.reflect_on_association(model).klass
        association = association_class.find_by(name: reference)
        raise "Could not find #{model} by name #{reference}" if association.nil?

        send("#{model}=", association)
      end
    end
  end
end
```

Then in the Article model I just add these lines:

```ruby
include AssociableByName
associate_by_name :author
```

What this code will do is create a new setter in the model called `author_name=` which will simply get the author ID from their name and create the association.

With that change, I can then update the importers to work more consistently:

```ruby
Article.create(
  title: csv_row['title'],
  content: csv_row['content'],
  author_name: csv_row['author']
)
```

And that's it!
Now the article will automatically associate with the Author by the name, rather than having to do the lookup, I also get the added benefit that it will throw an error if the association doesn't exist (which is consistent with `author_id` if the ID didn't exist)

What do you think? I'm happy to be proven wrong and learn why this code isn't a good Concern, so let me know if you have any thoughts!