Overriding a Rails default_scope
Ruby on-RailsRuby on-Rails Problem Overview
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?
Ruby on-Rails Solutions
Solution 1 - Ruby on-Rails
In Rails 3:
foos = Foo.unscoped.where(:baz => baz)
Solution 2 - Ruby on-Rails
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.
Solution 3 - Ruby on-Rails
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
Solution 4 - Ruby on-Rails
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.
Solution 5 - Ruby on-Rails
You can override a default scope using the with_exclusive_scope
method. So:
foos = Foo.with_exclusive_scope { :conditions => ["baz = ?", baz] }
with_exclusive_scope
documentation
Solution 6 - Ruby on-Rails
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.
Solution 7 - Ruby on-Rails
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!
Solution 8 - Ruby on-Rails
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)
Solution 9 - Ruby on-Rails
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")