DEV Community

Keeyan Nejad
Keeyan Nejad

Posted on

A Conservative Case for Concerns

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:

Article.create(
  title: csv_row['title'],
  content: csv_row['content'],
  author: Author.find_by(name: csv_row['author'])
)
Enter fullscreen mode Exit fullscreen mode

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:

Author.create(
  name: csv_row['name'],
  country: Country.find_by(name: csv_row['country'])
)
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

Then in the Article model I just add these lines:

include AssociableByName
associate_by_name :author
Enter fullscreen mode Exit fullscreen mode

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:

Article.create(
  title: csv_row['title'],
  content: csv_row['content'],
  author_name: csv_row['author']
)
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
keeyan profile image
Keeyan Nejad

I'm glad you liked it Jared :) But I have to say, I disagree with your statement there. I think the value of a concern is to extract the super-generic stuff so that they can be used in multiple places. If you have practically no code in your model and you extract it all to concerns simply to keep your model clean you will likely end up with Concerns that depend a lot on the actual model. So they can't be reused easily and it could be harder to work out how a model works since its functionality is spread across many files

 
keeyan profile image
Keeyan Nejad

Thanks for sharing that Jared :) I'll definitely give it a watch