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.
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.
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.