Merge pull request #49096 from abaldwin88/document_class_method_scope_behavior

Document clean chain behavior for ActiveRecord scope
This commit is contained in:
Adrianna Chang 2023-09-05 13:33:02 -04:00 committed by GitHub
commit f8c10e790b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1935,6 +1935,61 @@ SELECT books.* FROM books WHERE books.out_of_print = true
[`unscoped`]: https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-unscoped
### New Chains Inside Scope Block
Unlike class methods, [`scope`][] can easily start a new clean chain against the
model it is defined on.
```ruby
class Topic < ApplicationRecord
scope :toplevel, -> { where(parent_id: nil) }
scope :children, -> { where.not(parent_id: nil) }
scope :has_children, -> {
where(id: Topic.children.select(:parent_id))
}
end
Topic.toplevel.has_children
```
Inside `has_children` the `Topic` chain generates a subquery like this:
```sql
SELECT "topics"."parent_id" FROM "topics" WHERE "topics"."parent_id" IS NOT NULL
```
Class methods have different behavior which can be surprising if you expect them
to work exactly like scopes.
```ruby
class Topic < ApplicationRecord
def self.toplevel
where(parent_id: nil)
end
def self.children
where.not(parent_id: nil)
end
def self.has_children
where(id: Topic.children.select(:parent_id))
end
end
Topic.toplevel.has_children
```
`Topic` inside `has_children` will implicitly include `toplevel` from the outer
chain resulting in a subquery of:
```sql
SELECT "topics"."parent_id" FROM "topics" WHERE "topics"."parent_id" IS NULL AND "topics"."parent_id" IS NOT NULL
```
NOTE: In class methods, `self` refers back to the model. In scope, `self` acts as
a chained relation. For the example above, `self` and `Topic` are interchangeable
within the class method definition.
Dynamic Finders
---------------