Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is using the rails default_scope often recommend against?

People also ask

Why not use default scope?

Adding a default scope affects your model initialization. In the example, Post. new is defaulted to hidden = false whether you are expecting it or not. Trying not to use your defined default scope is a pain.

What is scope how and when it is to be used in rails models?

Scopes are custom queries that you define inside your Rails models with the scope method. Every scope takes two arguments: A name, which you use to call this scope in your code. A lambda, which implements the query.


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.


Another reason to not use default_scope is when you're deleting an instance of a model that has a 1 to many relation with the default_scope model

Consider for example:

    class User < ActiveRecord::Base
      has_many :posts, dependent: :destroy
    end 

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

Calling user.destroy will delete all the posts that are published, but it won't delete posts that are unpublished. Hence the database will throw a foreign key violation because it contains records that reference the user you want to remove.


default_scope is often recommended against because it is sometimes incorrectly used to limit the result set. A good use of default_scope is to order the result set.

I would stay away from using where in default_scope and rather create a scope for that.


I only find default_scope to be useful only in ordering some parameters to be in asc or desc order in all situation. Otherwise I avoid it like plague


For me is not a bad idea but must be used with caution!. There is a case where I always wanted to hide certain records when a field is set.

  1. Preferably the default_scope must match with the DB default value (e.g: { where(hidden_id: nil) })
  2. When you are totally sure you want to show those records, there is always the unscoped method that will avoid your default_scope

So it will depend and the real needs.