Hello friends! I'd like to show you Ruby on Rails concepts I refreshed this week to Avoid Social Media Doom Scrolling.
Introduction
We’ve all been there. You’re at the end of a high-intensity coding session, or maybe you're in the middle of a Pomodoro break. Your brain is fried, and the easiest "hit" is a social media feed scroll. Suddenly, you're deep into doom-scrolling, watching phenomenal creators like Simon Squibb ask people, "What’s your dream?" (He is honestly my favourite creator right now, and his videos are raw and authentic and a whole vibe! Who’s your favorite creator lately?)
This week, I decided to fight the scroll. I closed the "Trending" tab and opened the Rails Documentation instead. My goal was to master ActiveRecord Association Dependencies.
If you're building a user-centric app, like the "Google-style" search engine I’m currently architecting, you quickly realize that choosing how a record "dies" is just as critical as how it lives. In a search engine that tracks saved queries, leads, and API keys, our strategy has to balance two things: User Privacy (deleting personal data) and Business Continuity (preserving lead data for analytics).
Here is how I structured my models to ensure my database doesn't become a "ghost town" of orphaned records.
1. The Clean Sweep: dependent: :destroy
In my app, when a user deletes their account, I want their Saved Searches to vanish. These are private and hold zero value once the owner is gone.
I chose :destroy because it’s the "polite" way to say goodbye. Unlike a cold database delete, :destroy instantiates each record and triggers its specific callbacks. If you have logic that says "Notify the search engine to de-index this query when deleted," this is the only way to ensure that "cleanup" logic actually executes.
class User < ApplicationRecord
has_many :saved_searches, dependent: :destroy
end
2. The Bulk Cleanup: dependent: :delete_all
Sometimes, you don’t need a polite goodbye; you need raw speed. Imagine a user has millions of Audit Logs. Calling :destroy would try to load every single log into Ruby memory, a surefire way to crash your server or hang your database.
:delete_all skips the Rails middleware entirely. It sends a single, sharp SQL command directly to the database. This, however, has a Trade-off: If your AuditLog has a before_destroy hook that cleans up a file on Amazon S3, it will not run. Use this only for "passive" data where speed is the priority over side effects.
class User < ApplicationRecord
# If we had millions of log entries, we didn't need to process
has_many :audit_logs, dependent: :delete_all
end
3. The "Legacy" Handover: dependent: :nullify
This was the trickiest one for me to grasp, but it’s vital for B2B apps. In our search engine scenario, if a User generates a Lead and then deletes their account, the business still needs that Lead record to calculate conversion rates.
If I delete the lead, I break the business analytics. By using nullify, the user_id on the Lead becomes NULL, but the record itself stays on the "shelf." The user’s privacy is respected (their name is gone), but the business value is preserved.
class User < ApplicationRecord
has_many :leads, dependent: :nullify
end
4. The Soft Block: dependent: :restrict_with_error
I want you to think of this as the "Safety Valve." Sometimes, a user cannot be allowed to leave yet. In my app, if a user has Active API Keys, letting them delete their account would leave external integrations in a broken state.
Instead of a hard crash, :restrict_with_error adds a message to the errors object. This allows me to tell the user in the UI: "You can't close your account while you have active API keys. Please disable them first." It’s a UX-friendly way to protect critical system resources.
class User < ApplicationRecord
# "You can't leave us while you still have an active API Key!"
has_many :api_keys, dependent: :restrict_with_error
end
5. The Hard Guard: dependent: :restrict_with_exception
This is the ultimate safety net for financial data. I use this for Billing Subscriptions. If the system attempts to delete a user who still has an active billing record, Rails will raise a hard ActiveRecord::DeleteRestrictionError.
This isn't for a "nice UI message." This is for your "Security-First" logic, a last line of defense to ensure your financial integrity is never compromised by a stray user.destroy call.
class User < ApplicationRecord
has_many :billing_subscriptions, dependent: :restrict_with_exception
end
Short Recap
When a user leaves, not all data should be treated the same. Privacy, performance, and business continuity pull in different directions. Pick :destroy when cleanup must run, :delete_all for raw speed, :nullify to keep business artifacts, and :restrict_with_* to block dangerous deletions.
Conclusion
Doom-scrolling gives you a dopamine hit that lasts five seconds. Solving a database integrity problem gives you a stable application that lasts years.
By strategically picking between :destroy, :nullify and :restrict, I’ve ensured that my system handles user churn like a pro. My advice? Next time you’re bored, don't check the "Trending" tab. Check your schema.rb. There is always a relationship that could be handled more gracefully.
Which dependent strategy do you find yourself reaching for most often? Are you a "Clean Sweep" developer who keeps a pristine database, or do you lean heavily on :nullify to protect business analytics? I’d love to hear how you handle the "afterlife" of your data in the comments!
A Quick Side Note: While I’m heads-down building this search engine, I’m also looking to pitch in on new projects. If you’re looking for a mid-level Rails engineer with solid JavaScript experience who cares about building defensive, scalable systems, I’d love to chat. You can find me on [LinkedIn/Twitter] or check out my other work here.
I hope this was useful information for you. See you in my next article.


Top comments (0)