Why is using the rails default_scope often recommend against?

wrtsprt picture wrtsprt · Aug 1, 2014 · Viewed 31.6k times · Source

Everywhere on the internet people mention that using the rails default_scope is a bad idea, and the top hits for default_scope on stackoverflow are about how to overwrite it. This feels messed up, and merits an explicit question (I think).

So: why is using the rails default_scope recommended against?

Answer

wrtsprt picture wrtsprt · Aug 1, 2014

Problem 1

Lets consider the basic example:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
end

The motivation to make the default published: true, might be to make sure you have to be explict when wanting to show unpublished (private) posts. So far so good.

2.1.1 :001 > Post.all
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't'

Well this is pretty much what we expect. Now lets try:

2.1.1 :004 > Post.new
 => #<Post id: nil, title: nil, published: true, created_at: nil, updated_at: nil>

And there we have the first big problem with default scope:

=> default_scope will affect your model initialization

In a newly created instance of such a model, the default_scope will be reflected. So while you might have wanted to be sure to not list unpublished posts by chance, you're now creating published ones by default.

Problem 2

Consider a more elaborate example:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
  belongs_to :user
end 

class User < ActiveRecord::Base
  has_many :posts
end

Lets get the first users posts:

2.1.1 :001 > User.first.posts
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't' AND "posts"."user_id" = ?  [["user_id", 1]]

This looks like expected (make sure to scroll all the way to the right to see the part about the user_id).

Now we want to get the list of all posts - unpublished included - say for the logged in user's view. You'll realise you have to 'overwrite' or 'undo' the effect of default_scope. After a quick google, you'll likely find out about unscoped. See what happens next:

2.1.1 :002 > User.first.posts.unscoped
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"

=> Unscoped removes ALL scopes that might normally apply to your select, including (but not limited to) associations.

There are multiple ways to overwrite the different effects of the default_scope. Getting that right gets complicated very quickly and I would argue not using the default_scope in the first place, would be a safer choice.