Article summary
After a long hiatus from Rails, I found myself working in a Rails codebase this week. Here at Atomic, our recent focus has been on the wins provided by our starter kit. I still love Ruby and Rails, but after digging through a well-intentioned codebase, I was reminded how much I dislike Rails magic callbacks.
In a mini-fit of rage, I went hunting on the internet for people who agree with me…usually not a very helpful course of action. As part of my googling, I came across some recent developer videos by @DHH, the creator of Rails.
Example
module Recording::Mentions
extend ActiveSupport::Concern
included do
after_commit :eavesdrop_for_mentions, ...
end
private
def eavesdrop_for_mentions
#PerformLater ...
end
end
class MessageController
def create
@recording = @bucket.recordings.new ...
respond_to do |format|
#...
end
end
I’ve pulled in a snippet from the video that DHH claims as a victory for Rails. He claims that the fact that the message controller (and any other controller that works with Recordings) doesn’t know about the eavesdropping or the mentions is a good thing. As a Rails expert, with total and intimate knowledge of the entire code base, DHH may be right.
In the video, DHH claims that side effects are nice, and having them keeps things out of your way while you’re working on the main flow. He also pokes fun at the functional programming ideas of reducing side effects and pushing them to the edges of your system.
Wrong. Side effects and magic like this are how good developers get lost in their own code base.
Code that’s explicit and easy to read and reason about will always win out in my book. It allows new people to ramp in with less headaches and keeps everyone from being surprised by crazy, seemingly unrelated things that pop up.
Explicit Example
class MessageController
def create
@recording = MessageService.user_creates_message bucket: @bucket, ..
respond_to do |format|
#...
end
end
class MessageService
def user_creates_message(bucket:,params:)
recording = RecordingRepository.create params
MentionService.scan_for_and_send_notifications recording: recording, ...
end
end
You can see and follow what the code is doing here. It’s easier to test, easier to ramp into, and represents a similar amount of code. So the Rails magic is just there to make you feel like a wizard, but it actually hurts the quality of your app’s code.
The magic is so bad that other code in DHH’s example deals with it via the new suppress
method. This leads to spooky action-at-a-distance problems, and it violates single responsibility.
I love Rails, but the recommended way to work with callbacks is a terrible set of practices for building a real app.