Overriding a Rails default_scope
Asked Answered
V

9

171

If I have an ActiveRecord::Base model with a default-scope:

class Foo < ActiveRecord::Base

  default_scope :conditions => ["bar = ?",bar]

end

Is there any way to do a Foo.find without using the default_scope conditions? In other words, can you override a default scope?

I would have thought that using 'default' in the name would suggest that it was overridable, otherwise it would be called something like global_scope, right?

Volga answered 2/12, 2009 at 16:36 Comment(1)
andycroll.com/ruby/dont-use-default-scopeBattaglia
A
163

Short answer: Do not use default_scope unless you really have to. You'll probably be better off with named scopes. With that said, you can use with_exclusive_scope to override the default scope if you need to.

Have a look at this question for more details.

Aida answered 2/12, 2009 at 16:50 Comment(9)
Thanks for the link to the previous questionVolga
> Don't use default_scope unless you really have to. An excellent advise! Thank you!Dull
So true. Using default_scope might seem like a good idea, but will likely cause multiple headaches during the lifetime of your app.Alodie
See this answer too #25087836 : Why not use default_scope?Narah
with_exclusive_scope was removed in rails 3Bolivia
You are exaggerating a little sir. default_scope is an excellent tool and there are situations where you could another way but default_scope its just the right thing to do. For example, when you have a Product model that has a inactive flag, setting a default_scope { where inactive: false } is the best thing to do, since 99% or cases you will not want to display a inactive product. Then you just call unscoped on the remaining 1% cases, which is probably a Admin panel.V1
@V1 You could have an ActiveProduct class which inherits from Product and applies the default scope.Battaglia
Default scope violates the principle of least astonishment. I'm busy cursing previous developers for using this!Exaction
the paranoid gem uses it.Brunelle
N
222

In Rails 3:

foos = Foo.unscoped.where(:baz => baz)
Neocolonialism answered 12/11, 2010 at 16:42 Comment(5)
This has a side effect, if Post has_many Comment, Post.first.comments.unscoped returns ALL comments.Rigel
This really screwed me up for a while. Especially if you end up putting this in a class method like: def self.random; unscoped.order('rand()'); end unscoped removes ALL sql before it, not just what is listed under default_scope. While technically a correct answer, be careful using unstoppedBrodeur
WARNING! Unscoped does NOT remove the default_scope only, it was already said in another comment but it can really mess up with things.Furtive
A good rule of thumb is to only unscoped when it can directly follow a model, e.g. Foo.unscoped.blah() is ok but never Foo.blah().unscoped.Schlueter
#1834659 works around the side effect mentioned by EnricoWellestablished
A
163

Short answer: Do not use default_scope unless you really have to. You'll probably be better off with named scopes. With that said, you can use with_exclusive_scope to override the default scope if you need to.

Have a look at this question for more details.

Aida answered 2/12, 2009 at 16:50 Comment(9)
Thanks for the link to the previous questionVolga
> Don't use default_scope unless you really have to. An excellent advise! Thank you!Dull
So true. Using default_scope might seem like a good idea, but will likely cause multiple headaches during the lifetime of your app.Alodie
See this answer too #25087836 : Why not use default_scope?Narah
with_exclusive_scope was removed in rails 3Bolivia
You are exaggerating a little sir. default_scope is an excellent tool and there are situations where you could another way but default_scope its just the right thing to do. For example, when you have a Product model that has a inactive flag, setting a default_scope { where inactive: false } is the best thing to do, since 99% or cases you will not want to display a inactive product. Then you just call unscoped on the remaining 1% cases, which is probably a Admin panel.V1
@V1 You could have an ActiveProduct class which inherits from Product and applies the default scope.Battaglia
Default scope violates the principle of least astonishment. I'm busy cursing previous developers for using this!Exaction
the paranoid gem uses it.Brunelle
S
118

If all you need is to change the order defined in default_scope, you can use the reorder method.

class Foo < ActiveRecord::Base
  default_scope order('created_at desc')
end

Foo.reorder('created_at asc')

runs the following SQL:

SELECT * FROM "foos" ORDER BY created_at asc
Sherburne answered 2/8, 2012 at 14:27 Comment(2)
Tip: define a scope like scope :without_default_order, -> { reorder("") } and you can do things like Foo.without_default_order.order("created_at ASC") In some situations it reads better (maybe not this exact situation, but I had one).Spoony
Reorder did it for me. Thanks a lot!Alluring
C
56

Since 4.1 you can use ActiveRecord::QueryMethods#unscope to fight default scope:

class User < ActiveRecord::Base
  default_scope { where tester: false }
  scope :testers, -> { unscope(:where).where tester: true }
  scope :with_testers, -> { unscope(:where).where tester: [true, false] }
  # ...
end

It is currently possible to unscope stuff like: :where, :select, :group, :order, :lock, :limit, :offset, :joins, :includes, :from, :readonly, :having.

But still please avoid using of default_scope if you can. It's for your own good.

Caddoan answered 16/7, 2014 at 14:3 Comment(1)
This answer should be higherWeitman
C
13

You can override a default scope using the with_exclusive_scope method. So:

foos = Foo.with_exclusive_scope { :conditions => ["baz = ?", baz] }
Chihli answered 2/12, 2009 at 16:54 Comment(2)
Not true, it's just been moved: apidock.com/rails/ActiveRecord/Scoping/ClassMethods/…Jandy
It looks like it has been deprecatedIndustrialist
W
11

On Rails 5.1+ (and maybe earlier, but I've tested it works on 5.1) it is possible to unscope a specific column, which imho is the ideal solution for removing a default_scope in a fashion that can be used inside a named scope. In the case of the OPs default_scope,

Foo.unscope(where: :bar)

Or

scope :not_default, -> { unscope(where: :bar) }
Foo.not_default

Will both result in a sql query that doesn't apply the original scope, but does apply whatever other conditions get merged into the arel.

Wellestablished answered 27/9, 2019 at 23:16 Comment(0)
D
5

Rails 3 default_scope does not appear to get overridden like it did in Rails 2.

e.g.

class Foo < ActiveRecord::Base
  belongs_to :bar
  default_scope :order=>"created_at desc"
end

class Bar < ActiveRecord::Base
  has_many :foos
end

> Bar.foos
  SELECT * from Foo where bar_id = 2 order by "created_at desc";
> Bar.unscoped.foos
  SELECT * from Foo;  (WRONG!  removes the "has" relationship)
> Bar.foos( :order=>"created_at asc" )  # trying to override ordering
  SELECT * from Foo where bar_id = 2 order by "created_at desc, created_at asc"

In my app, using PostgreSQL, the ordering in the default scope WINS. I'm removing all of my default_scopes and coding it in explicitly everywhere.

Pitfall Rails3!

De answered 16/2, 2012 at 2:4 Comment(1)
You have to use Bar.foos.reorder(:created_at => :asc)Havelock
L
4

With Rails 3+ you can use a combination of unscoped and merge:

# model User has a default scope
query = User.where(email: "[email protected]")

# get rid of default scope and then merge the conditions
query = query.unscoped.merge(query)
Lowis answered 13/8, 2014 at 7:44 Comment(1)
This also worked for me, to call unscoped first (Rails 4.2): User.unscoped.where(email: "[email protected]")Wardmote
M
2

Well, you can always use the old time favorite find_by_sql with the complete query. For example: Model.find_by_sql("SELECT * FROM models WHERE id=123")

Mustang answered 26/4, 2015 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.