Black Magic Rails: default_scope
In a community where best practices are always being redefined, black magic practices aren’t hard to come by. For our first discussion on controversial Rails practices we’re focusing on default_scope.
Default Scopes (Briefly) Explained
default_scope is a Rails method provided by ActiveRecord which allows you to specify conditions for all the finders associated with a particular model. It is commonly used to specify ordering directly in your ActiveRecord model.
Sounds like a fairly good tool, but don’t be fooled. Popular opinion dictates that default_scope is at the very least a chaotic good tool, bordering cunningly on the edge of the black magic stratosphere.
Discussion about the behavior of default_scope ensued following this initial conversation:
Which led to several of our developers deciding that default_scope shouldn’t be used carelessly:
The Council Convenes
Using default_scope, to my knowledge hasn’t been overtly controversial, so I wanted to dig deeper and see what the big deal was. Four of our developers went into the topic more in-depth:
Pete: A scope is a selector. Selectors are used for searching for things. They are not meant to populate stuff. The fact that a default scope sets an attribute when a new object is created is an unwelcome side-effect.
Brent: Yes, and I prefer to be explicit when querying. Especially when it comes to maintaining an app. If I’m looking at code that was written months (or years) ago, I shouldn’t have to remember a model’s default query conditions. I’ve also run into situations where I needed to override a default order, but I couldn’t do it, even with the “unscoped” scope. I’m not sure if that’s normal, but it was annoying enough that I just removed the default scope.
Michael: I’m in favor of default scopes in certain cases but very against the default attribute setting. Take, for example, a collection of moderated posts. I would like to make sure that any time I’m showing users unmoderated content, I have had to explicitly turn OFF a default scope of where(approved: true) or similar. However, setting the attributes incorrectly assumes that my creation state and my default scope are meant to coincide. The moderation example plays through again here, where I want to create posts with approved set to false but that won’t be the case if I use a default scope.
Ultimately I think setting attributes based on default scope is just plain confusing black magic. I’m not sure of the rules of when and how it would work, and if I can’t predict it I certainly don’t want it dictating the instantiation of my models. Plus it’s called “default_scope” not “default_scope_and_initialization_attributes”. The name implies nothing about attribute setting.
I actually believe, though, that the attribute setting is a side-effect of other scoping. For instance, if you have a has_many :users and do some_model.users.create the model_id would already be set. So basically it’s all very confusing, needs clarification and clear rules, and is probably the wrong choice most of the time.
Ping: I agree with your arguments on default_scope should not be used most of the time, since it is out of sight and creates extra queries if it is not needed all the time (when you’re thinking about using unscoped).
However, you can avoid the auto-populate by using where(‘approved = true’). So if you would like the default value to be set for your model, you can use hash in default_scope, otherwise string.
I guess it is black magic, but it is everywhere for Rails anyway.
Pete: I usually limit my use of default scopes to a default sort order. Of course, that’s VIEW logic showing up in the model, eh?
Michael: The fact that you can “get around” the setters by using SQL just proves that they shouldn’t be there. If Rails can’t figure out with complete certainty what the default scope is implying about the model, it shouldn’t half-ass it and initialize the things that it can figure out. It should leave that up to the developer.
In a lot of ways I think ActiveRecord would benefit from optional DataMapper-style property declarations. Then defaults could be set and other special properties like that without relying on bizarre hacks like default_scope.
When I brought the topic to the Twitterverse, @bphogan likened using default_scope to using a sword with a bladed handle and @samullen advised against using default_scope at all without knowing about unscoped.
So does using default_scope you a practitioner of Rails Black Magic? Not necessarily. People seem to be aware of the undesired effects of the method (resulting in it not being too harmful) and it does still make sense to use it in certain situations like specifying the order to display data to the user. In summary, I’m giving default_scope a chaotic neutral alignment.
Do you have more insight on using default_scope? Share your thoughts with us below in the comments or on Twitter! We’ll be tackling more Black Magic Rails topics in the coming weeks.