Permalink
Browse files

Merge pull request #14095 from rails/rm-scopes

Document `default_scope` changes
  • Loading branch information...
2 parents 67e57ea + fc641a1 commit 6c496a6d2a21fb8f6f5fb4a97acf1f2de828d1d5 @rafaelfranca rafaelfranca committed Feb 18, 2014
View
61 activerecord/CHANGELOG.md
@@ -1,3 +1,64 @@
+* Default scopes are no longer overriden by chained conditions.
+
+ Before this change when you defined a `default_scope` in a model
+ it was overriden by chained conditions in the same field. Now it
+ is merged like any other scope.
+
+ Before:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+ User.where(state: 'inactive')
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+
+ After:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
+
+ User.where(state: 'inactive')
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
+
+ To get the previous behavior it is needed to explicitly remove the
+ `default_scope` condition using `unscoped`, `unscope`, `rewhere` or
+ `except`.
+
+ Example:
+
+ class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { unescope(where: :state).where(state: 'active') }
+ scope :inactive, -> { rewhere state: 'inactive' }
+ end
+
+ User.all
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+ User.active
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+ User.inactive
+ # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+
* Perform necessary deeper encoding when hstore is inside an array.
Fixes #11135.
View
6 guides/source/4_1_release_notes.md
@@ -453,6 +453,12 @@ for detailed changes.
### Notable changes
+* Default scopes are no longer overriden by chained conditions.
+
+ Before this change when you defined a `default_scope` in a model
+ it was overriden by chained conditions in the same field. Now it
+ is merged like any other scope. [More Details](upgrading_ruby_on_rails.html#changes-on-default-scopes).
+
* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from
a model's attribute or
method. ([Pull Request](https://github.com/rails/rails/pull/12891))
View
24 guides/source/active_record_querying.md
@@ -707,15 +707,15 @@ You can additionally unscope specific where clauses. For example:
```ruby
Post.where(id: 10, trashed: false).unscope(where: :id)
-# => SELECT "posts".* FROM "posts" WHERE trashed = 0
+# SELECT "posts".* FROM "posts" WHERE trashed = 0
```
A relation which has used `unscope` will affect any relation it is
merged in to:
```ruby
Post.order('id asc').merge(Post.unscope(:order))
-# => SELECT "posts".* FROM "posts"
+# SELECT "posts".* FROM "posts"
```
### `only`
@@ -1242,26 +1242,26 @@ class User < ActiveRecord::Base
end
User.active.inactive
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
```
We can mix and match `scope` and `where` conditions and the final sql
-will have all conditions joined with `AND` .
+will have all conditions joined with `AND`.
```ruby
User.active.where(state: 'finished')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
```
If we do want the `last where clause` to win then `Relation#merge` can
-be used .
+be used.
```ruby
User.active.merge(User.inactive)
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
```
-One important caveat is that `default_scope` will be overridden by
+One important caveat is that `default_scope` will be prepended in
`scope` and `where` conditions.
```ruby
@@ -1272,16 +1272,16 @@ class User < ActiveRecord::Base
end
User.all
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
User.active
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
User.where(state: 'inactive')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
```
-As you can see above the `default_scope` is being overridden by both
+As you can see above the `default_scope` is being merged in both
`scope` and `where` conditions.
View
67 guides/source/upgrading_ruby_on_rails.md
@@ -262,6 +262,73 @@ authors = Author.where(name: 'Hank Moody').to_a
authors.compact!
```
+### Changes on Default Scopes
+
+Default scopes are no longer overriden by chained conditions.
+
+In previous versions when you defined a `default_scope` in a model
+it was overriden by chained conditions in the same field. Now it
+is merged like any other scope.
+
+Before:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
+After:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
+```
+
+To get the previous behavior it is needed to explicitly remove the
+`default_scope` condition using `unscoped`, `unscope`, `rewhere` or
+`except`.
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { unescope(where: :state).where(state: 'active') }
+ scope :inactive, -> { rewhere state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.inactive
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------

0 comments on commit 6c496a6

Please sign in to comment.