167

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?

edgerunner
  • 14,873
  • 2
  • 57
  • 69
Gareth
  • 133,157
  • 36
  • 148
  • 157

9 Answers9

220

In Rails 3:

foos = Foo.unscoped.where(:baz => baz)
Vincent
  • 16,086
  • 18
  • 67
  • 73
  • 65
    This has a side effect, if Post has_many Comment, Post.first.comments.unscoped returns ALL comments. – Enrico Carlesso Sep 05 '11 at 16:28
  • 3
    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 `unstopped` – Schneems Dec 25 '11 at 21:58
  • 8
    WARNING! Unscoped does NOT remove the default_scope only, it was already said in another comment but it can really mess up with things. – dsimard Feb 29 '12 at 14:42
  • 19
    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`. – Grant Birchmeier Aug 01 '13 at 22:23
  • https://stackoverflow.com/questions/1834159/overriding-a-rails-default-scope/58142166#58142166 works around the side effect mentioned by Enrico – wbharding Sep 27 '19 at 23:18
161

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.

Community
  • 1
  • 1
Pär Wieslander
  • 28,374
  • 7
  • 55
  • 54
  • Thanks for the link to the previous question – Gareth Dec 03 '09 at 00:45
  • 11
    > Don't use default_scope unless you really have to. An excellent advise! Thank you! – installero Feb 01 '13 at 10:02
  • 3
    So true. Using `default_scope` might seem like a good idea, but will likely cause multiple headaches during the lifetime of your app. – thomax May 08 '14 at 07:46
  • See this answer too http://stackoverflow.com/questions/25087336/why-is-using-the-rails-default-scope-often-recommend-against : Why not use `default_scope`? – MrYoshiji Aug 01 '14 at 19:37
  • 7
    [`with_exclusive_scope` was removed in rails 3](https://github.com/rails/rails/commit/bd1666ad1de88598ed6f04ceffb8488a77be4385) – Jared Beck Dec 17 '15 at 19:16
  • 7
    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. – pedrozath Jan 27 '17 at 09:53
  • @pedrozath You could have an ActiveProduct class which inherits from Product and applies the default scope. – Kris Oct 11 '17 at 13:42
  • 1
    Default scope violates the principle of least astonishment. I'm busy cursing previous developers for using this! – Petercopter Apr 26 '19 at 15:33
  • the paranoid gem uses it. – baash05 Sep 09 '19 at 02:16
116

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
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
GuiGS
  • 2,070
  • 1
  • 19
  • 18
  • 4
    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). – Henrik N Jul 01 '15 at 13:49
  • Reorder did it for me. Thanks a lot! – Andre Zimpel Sep 23 '19 at 13:09
55

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.

jibiel
  • 8,175
  • 7
  • 51
  • 74
13

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

foos = Foo.with_exclusive_scope { :conditions => ["baz = ?", baz] }
Lucy Bain
  • 2,496
  • 7
  • 30
  • 45
John Topley
  • 113,588
  • 46
  • 195
  • 237
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.

wbharding
  • 4,213
  • 2
  • 30
  • 25
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!

vanboom
  • 1,274
  • 12
  • 20
4

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

# model User has a default scope
query = User.where(email: "foo@example.com")

# get rid of default scope and then merge the conditions
query = query.unscoped.merge(query)
santervo
  • 534
  • 5
  • 8
  • This also worked for me, to call unscoped first (Rails 4.2): `User.unscoped.where(email: "foo@example.com")` – Nick B May 17 '17 at 04:55
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")

Ady Rosen
  • 33
  • 5