
Rails Concerns: To Concern or Not to Concern - nikolalsvk
https://blog.appsignal.com/2020/09/16/rails-concers-to-concern-or-not-to-concern.html
======
lipanski
At the end of the day concerns are just Ruby modules and they are a core
feature of the language. They are an acceptable style of programming. It all
depends on the task, your team's size/maturity and the hard boundaries you'd
like to enforce.

The problem with concerns is that they can easily start leaking logic into
other concerns or models and you've only got your team's conventions and
common sense (which are all very soft boundaries) to steer you away from this.
As a matter of fact, same goes for Rails engines - they make it very easy to
call the parent app from within an engine and it's very tempting to do so at
the cost of leaking logic and breaking these boundaries.

If your team can agree and stick to a set of conventions in regards to
concerns, there's nothing wrong with using concerns the way Basecamp uses
them. If you prefer stricter boundaries, there are other patterns that you can
follow (like service objects or ActiveJob or events).

I personally use concerns when the behaviour tends to be very generic
("Paginates", "Cacheable", "SoftDeletes") and service objects for anything
that touches the business logic.

------
rubyist5eva
I've been doing Rails development for a decade. Concerns are probably the
biggest code smell/anti-pattern I've ever seen in any application. It's used
as a bandaid to "break up" classes which do too much (but you really don't,
you're just hiding the complexity), or it's way over done and everything is
magic and almost incomprehensible and unmaintainable. Overall, just a total
nightmare to deal with. Would not recommend, and I always try to steer people
away from using concers during code reviews.

~~~
Axsuul
What's the alternative to sharing logic across ActiveRecord models?

~~~
Fire-Dragon-DoL
Don't put the logic in activerecord models at all. Not even the data. Use
activerecord uniquely as a querying mechanism (read or write), don't use
relationships and don't put validations in there.

Create objects (aka behavioral objects, aka servoce objects) for the logic and
create entities (plain ruby objects) when you need to pass around the data.

Yes, you are essentially eliminating the entirety of activerecord.

After 10 years of rails, you realize that is the only safe way to use that
library.

By the way

User User::SignUp Are related. The behavior doesn't need to be in the same
object, the namespace takes care of that already.

~~~
e12e
> Use activerecord uniquely as a querying mechanism (read or write), don't use
> relationships and don't put validations in there.

I'm sorry, but without relationships - why even use AR for querying? And how?
Via connection.select/execute?

~~~
Fire-Dragon-DoL
It's hard to answer, in the sense that yes, you can use ActiveRecord
relationships for the purpose of _building a query_. The point is, you should
use them only for that. And it comes with the downsides of distancing you from
SQL, which is not negligible.

The problem is if you try to access relationships that you didn't load from
the object, that shouldn't be done. It's the autoloading from the database
that is the real problem.

If you have enough discipline and enough authority to ensure that never
happens within the software you are working on, you can.

It's really culture problem. As soon as you pass around a `user`, someone will
type `.posts` on it, or `.save`, suddenly your business logic depends on the
database shape, rather than on contracts. What if your database shape is
wrong? What if your database shape needs to change?

The goal of good architecture is to be resilient to change, or to even
_postpone the choice_ to a moment in time where you have more data to make the
right one.

The safest way is: query with activerecord, map to plain ruby objects and
discard the activerecord ones immediately.

This will also help you discover the entities you didn't know about, for
example, if you have a table of users identified by email, that's the "User"
table in Rails. However, let's say that you take only a subset of that table
(with select), such as "Full Name" and "Email": this could represent something
different. A Newsletter::Subscriber for example.

And the newsletter subscriber entity can be used in the
Newsletter::Weekly::Send object, as well as the Newsletter::Unsubscribe. If
you realize that this should not have been in the users table in the first
place (I have no idea myself), the cost of change is way lower than if you
passed an activerecord object around.

I hope this answers some of your questions. There is a lot to say on the
topic. I'm happy to chat more on slack or an online videoconference session

~~~
geospeck
That is very interesting approach! I’ve never worked on a project that doesn’t
use AR relationships e.g has_many... at all.

Do you have an example app or a blog post exposing this pattern? It would be
very interesting to see how this actually works. Thanks you very much for
bringing this up!

~~~
amw-zero
They aren’t saying to not use has_many. They’re saying to not use the
association method that gets put on the AR object from has_many. For example,
if a User has_many Posts, they’re saying to avoid calling user.posts.

That may seem weird, but the association still has value: for querying. You
can still write: User.joins(:posts) for example.

The problem with the association methods is that they aren’t really methods
because they always execute a query. A lot of longtime Rails developers get
tired of the database being involved in every step of the way in a request,
because it leads to a lack of separation of concerns.

------
pugio
Forgive the slight tangent but I'd like to talk about Rails in general:

Just last night I was migrating an app from Rails 4 to 6.1 - I have been using
rails since 0.9, but I think my long relationship is coming to an end.

What I loved about Rails was that it built a web framework with the same basic
idea as Ruby: optimize for developer happiness. Trying to use the webpack
integration (released with Rails 5.1 I think) is a nightmare. Suddenly my
clean Ruby world is polluted by JavaScript package hell, there are now two
parallel asset pipelines with subtly different behavior, I can no longer
reference JavaScript directly in my views, and the convention over
configuration Rails approach seems to have been superseded by a tool with the
most nightmarish proliferation of necessary configs that I've ever seen.

To top this all off, none of this is documented well in any of the official
Rails resources. The Rails JavaScript guide mostly focuses on their rails-UJS
tool (which is fine, though also lacking in actual API documentation) and
makes no mention of any of the webpacker stuff. The only docs I could find
were in half finished pull requests in forked repositories, and some brief
notes in markdown files.

Before starting with Rails 6, I was excited to try out the new Stimulus.js,
and hopefully the improvements derived from Hey.com. Now I want to tear out
every bit of JavaScript integration from the framework and manage those assets
entirely on my own.

~~~
jjgreen
From 4 to 6.1 _in an evening_ , colour me impressed.

~~~
xlii
That was my experience as well. Usually upgrading years old codebase to the
bleeding edge took way less than expected. Webpack on the other hand is a
different beast to handle and a challenge in itself.

------
stevebmark
Concerns are the best example of the flaws in Rails and Ruby. The fact that
DHH came up with a mixin applied at runtime as the solution to organizing
Rails apps is frustrating. Other programming ecosystems have rightly moved
away from mixins and do-everything-dynamically, along with moving away from
mixing data and methods that self-mutate that data.

Having worked on large real world Rails apps, concerns are indeed a poor
pattern in practice. They introduce hidden, untraceable dependencies, can
clobber each other silently, make it difficult or impossible to understand
where methods come from, and bloat classes into super coordinators, when
"models" should only be data. Combine that with Ruby's horrible preference for
metaprogramming and you get unmanageable code.

PS: If you work at a company as big as Github that can afford to literally
employ Ruby core maintainers, then you are welcome to reply with "but it works
for Github!"

~~~
cageface
I've been working on Ruby apps again lately after spending a few years in the
iOS world. I don't miss iOS at all but moving back to a slow, dynamically
typed language like Ruby after getting used to Swift is like trading in a
Tesla for a Model T. Ruby was great it its day but I think we can do better
now. Maintaining large Ruby codebases is no fun.

~~~
ultrarunner
No fun for you, maybe. And I can see where you’re coming from. I often find it
very fun, though, which has prevented me from porting an API to another
language like Go or (gasp) Swift. Ruby gives a lot of flexibility, and in my
line of work (especially with covid mitigations) there are lots of immediate
change requirements; flexibility is very important. That flexibility provides
the ability to shoot yourself in the foot (and I’ve surely done it), but it
also allows us to pull off events that have suddenly changed with little
warning. I like that.

~~~
theonething
> pull off events that have suddenly changed with little warning.

Excuse my ignorance. What are the events you reference here?

~~~
ultrarunner
I work for an event company, and in many cases much of it is orchestrated by
Ruby code, including a few rails apps.

~~~
theonething
aha. I wasn't sure if you were taking about events on the software side.

~~~
ultrarunner
Imagine the naming conventions on the software side :)

------
thedanbob
I tend to only use concerns in extremely cut-and-dry cases. And even then, I
sometimes get annoyed with them when hunting for that one method which is
tucked away in a concern somewhere. But sometimes they are the best solution
for making sure a bit of shared behavior doesn't get out of sync between
models.

------
irjustin
I'm an old dog. If I'm honest, Rails is the only thing I know (... I _barely_
know a lot of things).

Counter do DHH's and core Rails' pattern, I'm thin models and Service Objects
( _GASP_ the horror!) to handle business logic. Models become DAO-y.

I had a lot of mental trouble handling different use cases for the same
object. Like if an Admin vs User updates a post. The notification chains are
completely different. So now my service objects look like: Post::UpdateByAdmin
< Post::Base vs Post::UpdateByUser < Post::Base.

So, in general, my concerns are pretty thin. Like the post only handle data
grabs. Even then, I'm guilty of doing what the post says from time to time
especially when I couldn't see far enough into the future.

------
FpUser
I've never really had to program using Ruby but just reading this Concerns
concept makes me shudder. Am glad that I do not have to deal with the code
structured in such way.

~~~
tobyhinloopen
I worked on Ruby fulltime in a large team and it’s as horrible as you imagine.

One big ball of connected modules that depend on everything else.

It’s not Ruby’s or Rails’ fault I guess, but it’s easy to screw up and do it
badly.

You can make some pretty neat stuff with it but it takes great care and a lot
of discussions and disciplined code reviews

