This feature is scheduled for: Rails v2.3/3.0
It’s pretty common to want SQL queries against a particular table to always be sorted the same way, and is one of the reasons why I added the ordered scope to the utility scopes gem. For instance, when dealing with collections of articles it is reasonable to expect that the default ordering be most recent first, i.e. created_at DESC. Well now you can specify default ordering, and other scopes, in edge rails directly in your ActiveRecord model.
Taking our Article example let’s specify the aforementioned default ordering:
1 2 3 |
class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end |
Now, when any find method or named_scope is executed the default ordering comes along for the ride:
Article.find(:all) #=> "SELECT * FROM `articles` ORDER BY created_at DESC" |
The same holds true for any named scopes you might have:
1 2 3 4 5 6 |
class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true } end Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC" |
There are some things to keep in mind, however. First is that scopes like :join, :offset, :limit and :order will get clobbered by the innermost rule. For example, here the default scope ordering loses out to the named_scope ordering.
1 2 3 4 5 6 7 8 9 |
class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' named_scope :published, :conditions => { :published => true }, :order => 'published_at DESC' end # published_at DESC clobbers default scope Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY published_at DESC" |
Also keep in mind that the default scoping is inherited, so child-classes of Article will have the same default scoping.
And for those occasions when you want to override or remove your default scope, just use with_exclusive_scope:
1 2 3 4 5 6 |
class Article < ActiveRecord::Base default_scope :order => 'created_at DESC' end # Ignore other scoping within this block Article.with_exclusive_scope { find(:all) } #=> "SELECT * FROM `articles` |
default_scope is a great way to specify reasonable query defaults and relieve yourself of having to create your own named_scopes for that purpose or specify such conditions at every invocation. Yes, I just said ‘relieve yourself’... giggity giggity
tags: ruby, rubyonrails

I could see this being used as a way to simulate destroying a model (like acts_as_paranoid). You could have a deleted_at column and set default_scope to only find records where deleted_at is null.
That seems useful at first but also kind of limiting. What happens when you want to show a list of deleted records? Is it possible to override the conditions of a default_scope or clear the scope? That can get messy fast.
In general, global scopes are convenient but also easy to abuse. For more information I encourage you to read this excellent article on Err the Blog. http://errtheblog.com/posts/39-withscope-with-scope
Good points, Ryan B.
+1 Ryan D. for Quagmire reference.
Ryan B: You can always override the default scope with a
with_exclusive_scope. Does that make you warm up to the concept, or are you still uncomfortable with the potential messiness?Fuckin’ aye! Been trying to implement this myself, and haven’t been too successful.
Don’t you mean :join rather than :joint in “First is scopes like…”?
Also, is there some mechanism for specifying that a query should not use the default scope other than just specifying the opposite?
Can you have more than one default?
2nd that, it could get messy pretty soon. When I see Article.find(:all) now, that doesn’t really tell me a lot. I’ll also have to check the model and see if there’s a default scope there.
One of the most useful additions to AR for some time.
Obviously, default order is only example. Method default_scope has the same syntax as named_scope (difference is only with lambdas, because there is no way to put arguments for find not breaking find syntax, if you really need this, just use named_scope). You can for example add basic :includes, that are always necessary for your model. Any use of named_scope or with_scope will override your attributes for find() because it uses same internals of AR. You shouldn’t use default_scope always, only when it give you possibility to clean your controller code IMHO. It’s better to have Model.find(:all) with defined default_scope than Model.base_scope.find(:all) everywhere in your contorller code.
This kind of feature is only usable if their is a way to remove the default scope at runtime without having to override manually all options.
Model.without_default_scope.a_custom_named_scope.find(...)
I’ve updated the post to include an example of using
with_exclusive_scopeto override the default scoping, since that seemed to be a concern for most.I’m wondering if it’s possible to rescue Exception (ex. ActiveRecord::StatementInvalid) from named_scope in a model file as I always use :select option for any find (never want to use select * or my db admin will be angry to my developer teams). I changed back to class method so that I can rescue Exception.
@ryan, Do you have a suggestion for me so that I can feel the beauty for this scope world too? or I’ll probably just rescue the exception in Controller which calls the named_scope?
I used to think a default scope was useful for filtering deleted records but after using acts_as_paranoid I have determined that it is the wrong approach.
The problem is that you most often don’t want to filter. Lets say you are making a standard CRUD controller with the object (we will call Category) having a default scope of :conditions => {:deleted_at => nil}. Seems useful right? Your index action just needs to be “Category.all”. Looks sweet huh?
But what happens if you want to show a deleted object. Your show method now needs to bypass this default scope somehow. What happens if you want to edit the deleted object (maybe to change it back to an active object?). Your edit and update actions now also need to bypass the default scope.
The most insidious case I found was when you have a drop-down when editing another related object. So we have something like <%= select :message, :category_id, Category.all.collect {|c| [c.name, c.id]} %>. At first you think it works great. “deleted” categories do not appear in the dropdown when creating a new message. But what happens when you edit an existing message which is currently in a deleted category. The option for the deleted category will not be in the dropdown causing it to fall back on the first value in the dropdown (the first category or nil depending on if you are using :include_blank). The user doesn’t notice this, they save the record (changing some other value) and now they have inadvertently changed the category!
So in this simple example you can see that our :index action was the ONLY place we actually wanted the default scope. On our :show, :edit, :update actions we need to override the default scope. And when displaying the object in other parts of an app (dropdown, plain output, etc.) we also will usually need to override the default scope.
This long message is a warning to STAY AWAY from default scope. It causes a lot of problems and it not a best practice. The examples in the blog post show probably the only useful use case which is a default order. Easy to override and provides a reasonable order if you forget somewhere in your app.
My 2 cents? I say get rid of this default_scope before it lands on a stable version of Rails. Instead replace it with default_order which is the only sane use case of this feature. Much like with_scope was turned into a protected method the framework should encourage good programming. Since :order is the only reasonable use case support only that. If someone REALLY wants to use the other scope arguments they can do it just like acts_as_paranoid has (i.e. a bit hackish but it works).
I understand your concerns. But from other side, i think every developer should be independent in making decisions ‘what tools from api’ he should use to achieve his targets. Default_scope is only a tool, you are responsible to use it or not use it. If you thnik it fit/or not fit your needs, then probably there is no other person that know better than you about that on the planet.
@Eric Enderson: I say Category.all.collect {|c| [c.name, c.id]} should not be in a view? That’s a thing that belongs to a model in the first place.
Besides does anyone force you to use it? :) Think, before posting needless comments.
@Pawel Kondzior – I agree that each developer can choose their tools. All I am saying is that Rails has traditionally been opinionated by making the bad choices (in Rails opinion) hard and the good choices easy. I think default_scope makes a bad choice (in my experience) easy. It was already possible (acts_as_paranoid has proven that), it just wasn’t easy. Obviously we can ignore this feature but I think the fact that as soon as the feature was posted the comments immediately went to the possibility of using :conditions in the default_scope point to the fact that this feature will lead towards lots of misuse.
@Piotr Usewicz – My dropdown example was just for illustrative purposes (I would define a helper). My point is that the dropdown (like most other places) will get complicated because of a default scope using :conditions. This is regardless of where you define that dropdown. Nobody is forcing me to use it but it’s availably will increase the complexity of many rails apps to come if it stays. These may be Rails apps I have to maintain so just wanted to put my two cents in about its dangers to discourage people from using :conditions in default_scope.
@Eric Anderson: I get your point, because I suffered it while using acts_as_paranoid. But I think you may be overreacting. At the time, to easy my pain with acts_as_paranoid, I submitted 2 patches to acts_as_paranoid that technoweenie kindly included upstream a couple of months ago.
So in current version of acts_as_paranoid you can easily condition the inclusion of deleted records or not with a helper method. For example:
app/controllers/application.rb
def can_see_deleted_items? current_user.is_admin? # or whatever criteria you want. end
app/controllers/your_controller.rb
Article.find(:all, :with_deleted => can_see_deleted_items?)
And you also have the same for :only_deleted so you can show “undelete” listings.
On a side note… we (the Uruguayan Ruby Users Group: www.ruguy.org) have been talking about the convenience of having default_scopes the past months, and agree that a new version of acts_as_paranoid could be much less hackish leveraging this.
@Eric: “But what happens when you edit an existing message which is currently in a deleted category”
This is simply a violation of referential integrity. Either the message should be deleted too, or the category should not be allowed to be deleted. It makes no sense for an active message to be in a deleted category. What are the semantics of such a case?
As for your other examples, if you want to be able to work with a deleted record the same as an undeleted record, I would question your definition of “deleted.” In any case, you will not want to show deleted records in most situations (that’s why they’re deleted), so you’ll have a scope somewhere that culls them out. default_scope simply DRYs that up. It makes more sense to have a special-case scope (using with_exclusive_scope) that shows deleted records, than to have a bunch of special-case scopes that don’t.
Finally, default_scope is nice and documenting. That helps teams understand code better.
@Mark Wilden – The semantics are when you de-activate a category you don’t want to be used anymore but historical data (existing messages) would still be in the category they were originally assigned. Now when you try to display that historical/archived message the category will not be found because the default scope will exclude it. So when you display a message you will need to have the category found without the default scope in case the message is in a de-activated category.
My only point is :conditions on default_scope can get messy quick and often you won’t realize the problems and consequences until later. I think an explicit scoping (through the use of named scope) is much more obvious. The named scope is keeping it DRY while not being confusing.
Using :conditions on default_scope is like using with_scope on an around_filter for a controller. For the same reasons with_scope was made protected I think default_scope with :conditions leads to complexity and confusion without much benefit. Using :limit on default_scope I can see having some of the same problems.
On the other hand default_scope with :order rocks as it provide a nice default sort which is easily over-ridable.
Not sure about using :joins and :include on default_scope. On one hand it can inadvertently add a larger load to your entire app if misused. On the flip side it might be necessary to provide a default sort on a related table.
Ah, see, I wouldn’t call that a deleted category, but something like inactive or unavailable. Now I see what you mean. In that case, I would definitely not use a default scope, because you don’t know when you would want to display a perfectly valid message that happened to have an unavailable category. For a “true” soft deletion, however, I’d say that in the great majority of your code, you would not want to display such a message, and hence a default scope would be DRYer, because you wouldn’t have to specify it everywhere. Plus, as I mentioned, any developer would be quickly alerted to its special significance.
On the other hand, I would probably not use a default sort order because the number of places where the order was significant would be limited, and I wouldn’t want to impose the database hit “just in case.”
Different strokes…:)
This is a good discussion, default_scope has its uses, but it’s important the developer understand what those special cases are. I think the convenience of it makes it far too easy to use it where it shouldn’t be.
I encourage everyone to first start off with a named scope and only use default scope if you end up using that named scope everywhere. Otherwise you’re likely to end up with messier, less clear code and possibly performance issues.
I’ve got a quick example of where this is AWESOME…
In my app I’ve got accounts based on subdomains. Many of the models (eg User, Page, Template) belong_to account. I set the current account on Thread.current and I’ve been doing this kind of ugly thing overriding the default AR:find (find_every, actually). The upside was that I would never forget to scope any finds on the account, and everything was a little bit cleaner.
Since I ALWAYS need those models scoped by account (on these models, accounts don’t share), the default_scope is perfect for this case, and cleaner than doing alias_method_chain on find_all.
@David Reese – I agree doing account separation can be a good use of default_scope. Curious why you went to all the trouble to override find and use alias_method_chain before this. Why not just use the protected with_scope in an around_filter on your controller. In general not a good practice but in the case of account separation it seems perfectly legitimate. Although I agree that default_scope is even more clear because it does the scoping at the model level instead of the controller level.
Very interesting feature. It can be use also where app limit access to some materials and often programmer forget about another conditions. But it is a problem with new programmer, they have to learn all special features for every apps.
Ok, my enthusiasm has tempered on this a bit. default_scope accepts the same arguments as with_scope, not the same arguments as named_scope. The difference is that with_scope accepts a plain hash, while named_scope can accept a proc. So stuff like this is out (probably a bad example):
default_scope(lambda { { :conditions => [‘created_at < ?’, 1.week.ago] } })
...as are more complicated lambdas like the ones I was planning to use. Ah, I’m sure it’s useful somewhere.
Article.with_exclusive_scope { find(:all) }I’d rather seeArticle.find(:all, :with_exclusive_scope => true)I just think it’s cleaner.
Or even better:Article.with_exclusive_scope.find(:all)(i.e.with_exclusive_scopewould be a named_scope here)Isn’t `with_exclusive_scope` protected now, for the very reasons cited in the first comment? You’d have to do something like `Article.send(:with_exclusive_scope) { Article.find(:all) }`, I think.
Nice addition – I’m excited to use it. This combined with named_scope are big time savers.
I just upgraded to Rails 2.3.0 RC1 and am working through all of these changes sort of “updating” an app I’ve been working on and one thing that I find puzzling is the fact that “with_exclusive_scope” is a protected method. Shouldn’t there be a public method allowing me to override the default_scope I’ve specified?
I like Daniel Tsadok’s suggestion Article.find(:all, :with_exclusive_scope => true) or something similar seems to better follow the AR patterns already in place.
Until then, are there any plans of making ‘with_exclusive_scope’ a public method so I don’t have to use the send hack to call it?
—Josh