Fork me on GitHub

default_scope is evil

15 Jun 2013

Richard Huang (flyerhzm@gmail.com)

I have used default_scope many times before, but later I regretted doing so.

Assume we defined a default_scope in Post model

class Post
  default_scope where(published: true).order("created_at desc")
end

default_scope added some behaviors that you may not expect.

1. You can't override default scope. e.g. by default, it lists posts order by created_at,

> Post.limit(10)
  Post Load (3.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc LIMIT 10

if you want to display posts order by updated_at rather than created_at, you may use the following line,

> Post.order("updated_at desc").limit(10)
  Post Load (17.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc, updated_at desc LIMIT 10

but as you can see, it order by both created_at and updated_at, default scope is not overriden, you have to use unscoped to disable default scope explicitly,

> Post.unscoped.order("updated_at desc").limit(10)
  Post Load (1.9ms)  SELECT `posts`.* FROM `posts` ORDER BY updated_at desc LIMIT 10

so you have to remember the model has a default_scope, and add unscoped if you want to override the default scope, it's an accident waiting to happen.

2. default_scope will affect your model initialization. e.g.

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

most developers are not aware this point, they think default_scope only affect queries, it is the wrong way default_scope does.

Don't use default_scope any more, just define it as a scope and explictly call that scope.

Tags