The Only Correct Way to Use default_scopes in Rails
March 19 2021
Inserts strong declaration in title to get them inflammatory clicks...
There are plenty of articles out there that will tell you to never use default_scopes... and lots of other articles that clone the same content and call it original 🙄 but the consensus stands: default_scopes are bad. There's good reason for this mindset — notably two parts that will cause massive headaches for you in the future if you use a
- Trying to retrieve records cut out by the scope is notoriously hard
- Default scopes impose defaults onto new model instances
I'm not saying those aren't true. Those are definitely true. Using default scopes in the wrong way will present pain.
BUT, I believe there is exactly one decent use case for default scopes in Rails applications that folks don't recognize. The case where the app isn't aware of them. That is, when there is no application- or business-logic utilizing the scope for functionality.
I'm going to use soft-deleting as my example case here since it's a well-known feature and can be used in a number of different ways. The key distinction here is whether or not the application is leveraging soft-deletes for application or business-logic functionality, so let's illustrate that in two ways. In Situation 1, consider a Blog application that uses soft-deletes as a feature to allow authors to have a "Blog Post Trash" that holds the posts until they confirm they really want to delete them. In this case, soft-deleting is being used for application-level functionality: the soft-delete is the mechanism that directly powers the "is it in the trash?" query. Contrast that with Situation 2 and perhaps a Library application that keeps a record of who-checked-out-what-books. In this situation users can delete records from their history, but as far as the user (and the application) is concerned, once it's deleted it's deleted. The only reason soft-deletes exist in this situation is for auditing or data-retention purposes outside of the application.
The key difference here is whether or not the application is aware of, and attempts to make functional use of, the records on the "other side" of the default scope. In the former, the soft-delete is very much part of the user-functionality. Using a
default_scope in that case would be a bad, painful idea. However in the latter, the application has no idea there are records that are soft deleted — it believes that once
#destroy is called, that record no longer exists. The soft-deleted records are purely for administrative intervention, data-retention, and/or developer needs — the Rails app itself isn't attempting to use soft-deleted records in any way.
If the soft-deleted records are not leveraged for programmatic restores/revives anywhere or used for functional purposes within the app, and serves purely as a data retention mechanism to only be leveraged by Rails developers in a production console (or manual database access, etc.), then a default scope is okay.
Now, it's worth pointing out that what I'm suggesting as being "okay with default scopes" isn't actually used by lots of folks (as far as I know) — it's much, much more common to have application-functionality-based soft-deleting. For the same reasons that Situation 1's 'trash bin' is helpful to end-users. But my last note here is actually about naming. If you're working with application-functionality-level soft-deletes or other "maybe this could be a default scope?" type situations, first, don't use a default scope. Second, name your field better! If you have a user-level feature for your User's blog posts to be in a trash bin, name the attribute/column
:user_trashed_at. If the goal is to hide content from users, name your field
user_hid_at etc. etc. Application-functionality-level features should be named well with regard to the application's domain. That's not soft-deleting, that's a user-feature. Name it as such 😜