aboutsummaryrefslogtreecommitdiffstats
path: root/guides/source/active_record_querying.md
diff options
context:
space:
mode:
Diffstat (limited to 'guides/source/active_record_querying.md')
-rw-r--r--guides/source/active_record_querying.md615
1 files changed, 344 insertions, 271 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 4725e2c8a2..434b308170 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Query Interface
=============================
@@ -9,6 +11,7 @@ After reading this guide, you will know:
* How to specify the order, retrieved attributes, grouping, and other properties of the found records.
* How to use eager loading to reduce the number of database queries needed for data retrieval.
* How to use dynamic finders methods.
+* How to use method chaining to use multiple ActiveRecord methods together.
* How to check for the existence of particular records.
* How to perform various calculations on Active Record models.
* How to run EXPLAIN on relations.
@@ -87,15 +90,15 @@ The primary operation of `Model.find(options)` can be summarized as:
* Convert the supplied options to an equivalent SQL query.
* Fire the SQL query and retrieve the corresponding results from the database.
* Instantiate the equivalent Ruby object of the appropriate model for every resulting row.
-* Run `after_find` callbacks, if any.
+* Run `after_find` and then `after_initialize` callbacks, if any.
### Retrieving a Single Object
Active Record provides several different ways of retrieving a single object.
-#### Using a Primary Key
+#### `find`
-Using `Model.find(primary_key)`, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example:
+Using the `find` method, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example:
```ruby
# Find the client with primary key (id) 10.
@@ -109,119 +112,103 @@ The SQL equivalent of the above is:
SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
```
-`Model.find(primary_key)` will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found.
-
-#### `take`
+The `find` method will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found.
-`Model.take` retrieves a record without any implicit ordering. For example:
+You can also use this method to query for multiple objects. Call the `find` method and pass in an array of primary keys. The return will be an array containing all of the matching records for the supplied _primary keys_. For example:
```ruby
-client = Client.take
-# => #<Client id: 1, first_name: "Lifo">
+# Find the clients with primary keys 1 and 10.
+client = Client.find([1, 10]) # Or even Client.find(1, 10)
+# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients WHERE (clients.id IN (1,10))
```
-`Model.take` returns `nil` if no record is found and no exception will be raised.
+WARNING: The `find` method will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys.
-TIP: The retrieved record may vary depending on the database engine.
-
-#### `first`
+#### `take`
-`Model.first` finds the first record ordered by the primary key. For example:
+The `take` method retrieves a record without any implicit ordering. For example:
```ruby
-client = Client.first
+client = Client.take
# => #<Client id: 1, first_name: "Lifo">
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
+SELECT * FROM clients LIMIT 1
```
-`Model.first` returns `nil` if no matching record is found and no exception will be raised.
-
-#### `last`
+The `take` method returns `nil` if no record is found and no exception will be raised.
-`Model.last` finds the last record ordered by the primary key. For example:
+You can pass in a numerical argument to the `take` method to return up to that number of results. For example
```ruby
-client = Client.last
-# => #<Client id: 221, first_name: "Russel">
+client = Client.take(2)
+# => [
+ #<Client id: 1, first_name: "Lifo">,
+ #<Client id: 220, first_name: "Sara">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
-```
-
-`Model.last` returns `nil` if no matching record is found and no exception will be raised.
-
-#### `find_by`
-
-`Model.find_by` finds the first record matching some conditions. For example:
-
-```ruby
-Client.find_by first_name: 'Lifo'
-# => #<Client id: 1, first_name: "Lifo">
-
-Client.find_by first_name: 'Jon'
-# => nil
+SELECT * FROM clients LIMIT 2
```
-It is equivalent to writing:
+The `take!` method behaves exactly like `take`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-```ruby
-Client.where(first_name: 'Lifo').take
-```
+TIP: The retrieved record may vary depending on the database engine.
-#### `take!`
+#### `first`
-`Model.take!` retrieves a record without any implicit ordering. For example:
+The `first` method finds the first record ordered by the primary key. For example:
```ruby
-client = Client.take!
+client = Client.first
# => #<Client id: 1, first_name: "Lifo">
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
```
-`Model.take!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
-
-#### `first!`
+The `first` method returns `nil` if no matching record is found and no exception will be raised.
-`Model.first!` finds the first record ordered by the primary key. For example:
+You can pass in a numerical argument to the `first` method to return up to that number of results. For example
```ruby
-client = Client.first!
-# => #<Client id: 1, first_name: "Lifo">
+client = Client.first(3)
+# => [
+ #<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Fifo">,
+ #<Client id: 3, first_name: "Filo">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3
```
-`Model.first!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
+The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-#### `last!`
+#### `last`
-`Model.last!` finds the last record ordered by the primary key. For example:
+The `last` method finds the last record ordered by the primary key. For example:
```ruby
-client = Client.last!
+client = Client.last
# => #<Client id: 221, first_name: "Russel">
```
@@ -231,92 +218,62 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
```
-`Model.last!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
-
-#### `find_by!`
-
-`Model.find_by!` finds the first record matching some conditions. It raises `ActiveRecord::RecordNotFound` if no matching record is found. For example:
-
-```ruby
-Client.find_by! first_name: 'Lifo'
-# => #<Client id: 1, first_name: "Lifo">
-
-Client.find_by! first_name: 'Jon'
-# => ActiveRecord::RecordNotFound
-```
-
-It is equivalent to writing:
-
-```ruby
-Client.where(first_name: 'Lifo').take!
-```
-
-### Retrieving Multiple Objects
+The `last` method returns `nil` if no matching record is found and no exception will be raised.
-#### Using Multiple Primary Keys
-
-`Model.find(array_of_primary_key)` accepts an array of _primary keys_, returning an array containing all of the matching records for the supplied _primary keys_. For example:
+You can pass in a numerical argument to the `last` method to return up to that number of results. For example
```ruby
-# Find the clients with primary keys 1 and 10.
-client = Client.find([1, 10]) # Or even Client.find(1, 10)
-# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
+client = Client.last(3)
+# => [
+ #<Client id: 219, first_name: "James">,
+ #<Client id: 220, first_name: "Sara">,
+ #<Client id: 221, first_name: "Russel">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients WHERE (clients.id IN (1,10))
+SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3
```
-WARNING: `Model.find(array_of_primary_key)` will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys.
+The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-#### take
+#### `find_by`
-`Model.take(limit)` retrieves the first number of records specified by `limit` without any explicit ordering:
+The `find_by` method finds the first record matching some conditions. For example:
```ruby
-Client.take(2)
-# => [#<Client id: 1, first_name: "Lifo">,
- #<Client id: 2, first_name: "Raf">]
-```
-
-The SQL equivalent of the above is:
+Client.find_by first_name: 'Lifo'
+# => #<Client id: 1, first_name: "Lifo">
-```sql
-SELECT * FROM clients LIMIT 2
+Client.find_by first_name: 'Jon'
+# => nil
```
-#### first
-
-`Model.first(limit)` finds the first number of records specified by `limit` ordered by primary key:
+It is equivalent to writing:
```ruby
-Client.first(2)
-# => [#<Client id: 1, first_name: "Lifo">,
- #<Client id: 2, first_name: "Raf">]
+Client.where(first_name: 'Lifo').take
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY id ASC LIMIT 2
+SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1
```
-#### last
-
-`Model.last(limit)` finds the number of records specified by `limit` ordered by primary key in descending order:
+The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example:
```ruby
-Client.last(2)
-# => [#<Client id: 10, first_name: "Ryan">,
- #<Client id: 9, first_name: "John">]
+Client.find_by! first_name: 'does not exist'
+# => ActiveRecord::RecordNotFound
```
-The SQL equivalent of the above is:
+This is equivalent to writing:
-```sql
-SELECT * FROM clients ORDER BY id DESC LIMIT 2
+```ruby
+Client.where(first_name: 'does not exist').take!
```
### Retrieving Multiple Objects in Batches
@@ -328,7 +285,7 @@ This may appear straightforward:
```ruby
# This is very inefficient when the users table has thousands of rows.
User.all.each do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver_now
end
```
@@ -344,7 +301,15 @@ The `find_each` method retrieves a batch of records and then yields _each_ recor
```ruby
User.find_each do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver_now
+end
+```
+
+To add conditions to a `find_each` operation you can chain other Active Record methods such as `where`:
+
+```ruby
+User.where(weekly_subscriber: true).find_each do |user|
+ NewsMailer.weekly(user).deliver_now
end
```
@@ -360,7 +325,7 @@ The `:batch_size` option allows you to specify the number of records to be retri
```ruby
User.find_each(batch_size: 5000) do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver_now
end
```
@@ -372,7 +337,7 @@ For example, to send newsletters only to users with the primary key starting fro
```ruby
User.find_each(start: 2000, batch_size: 5000) do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver_now
end
```
@@ -384,16 +349,14 @@ The `find_in_batches` method is similar to `find_each`, since both retrieve batc
```ruby
# Give add_invoices an array of 1000 invoices at a time
-Invoice.find_in_batches(include: :invoice_lines) do |invoices|
+Invoice.find_in_batches do |invoices|
export.add_invoices(invoices)
end
```
-NOTE: The `:include` option allows you to name associations that should be loaded alongside with the models.
-
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`, as well as most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_in_batches`.
+The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`.
Conditions
----------
@@ -472,8 +435,8 @@ Client.where('locked' => true)
In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well.
```ruby
-Post.where(author: author)
-Author.joins(:posts).where(posts: { author: author })
+Article.where(author: author)
+Author.joins(:articles).where(articles: { author: author })
```
NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`.
@@ -511,7 +474,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
`NOT` SQL queries can be built by `where.not`.
```ruby
-Post.where.not(author: author)
+Article.where.not(author: author)
```
In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
@@ -659,6 +622,23 @@ FROM orders
GROUP BY date(created_at)
```
+### Total of grouped items
+
+To get the total of grouped items on a single query call `count` after the `group`.
+
+```ruby
+Order.group(:status).count
+# => { 'awaiting_approval' => 7, 'paid' => 12 }
+```
+
+The SQL that would be executed would be something like this:
+
+```sql
+SELECT COUNT (*) AS count_all, status AS status
+FROM "orders"
+GROUP BY status
+```
+
Having
------
@@ -690,32 +670,32 @@ Overriding Conditions
You can specify certain conditions to be removed using the `unscope` method. For example:
```ruby
-Post.where('id > 10').limit(20).order('id asc').except(:order)
+Article.where('id > 10').limit(20).order('id asc').unscope(:order)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id > 10 LIMIT 20
+SELECT * FROM articles WHERE id > 10 LIMIT 20
# Original query without `unscope`
-SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20
+SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20
```
-You can additionally unscope specific where clauses. For example:
+You can also unscope specific `where` clauses. For example:
```ruby
-Post.where(id: 10, trashed: false).unscope(where: :id)
-# => SELECT "posts".* FROM "posts" WHERE trashed = 0
+Article.where(id: 10, trashed: false).unscope(where: :id)
+# SELECT "articles".* FROM "articles" 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"
+Article.order('id asc').merge(Article.unscope(:order))
+# SELECT "articles".* FROM "articles"
```
### `only`
@@ -723,16 +703,16 @@ Post.order('id asc').merge(Post.unscope(:order))
You can also override conditions using the `only` method. For example:
```ruby
-Post.where('id > 10').limit(20).order('id desc').only(:order, :where)
+Article.where('id > 10').limit(20).order('id desc').only(:order, :where)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id > 10 ORDER BY id DESC
+SELECT * FROM articles WHERE id > 10 ORDER BY id DESC
# Original query without `only`
-SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20
+SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
```
@@ -741,25 +721,25 @@ SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20
The `reorder` method overrides the default scope order. For example:
```ruby
-class Post < ActiveRecord::Base
- ..
- ..
+class Article < ActiveRecord::Base
has_many :comments, -> { order('posted_at DESC') }
end
-Post.find(10).comments.reorder('name')
+Article.find(10).comments.reorder('name')
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id = 10 ORDER BY name
+SELECT * FROM articles WHERE id = 10
+SELECT * FROM comments WHERE article_id = 10 ORDER BY name
```
In case the `reorder` clause is not used, the SQL executed would be:
```sql
-SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
+SELECT * FROM articles WHERE id = 10
+SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC
```
### `reverse_order`
@@ -795,25 +775,25 @@ This method accepts **no** arguments.
The `rewhere` method overrides an existing, named where condition. For example:
```ruby
-Post.where(trashed: true).rewhere(trashed: false)
+Article.where(trashed: true).rewhere(trashed: false)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE `trashed` = 0
+SELECT * FROM articles WHERE `trashed` = 0
```
In case the `rewhere` clause is not used,
```ruby
-Post.where(trashed: true).where(trashed: false)
+Article.where(trashed: true).where(trashed: false)
```
the SQL executed would be:
```sql
-SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0
+SELECT * FROM articles WHERE `trashed` = 1 AND `trashed` = 0
```
Null Relation
@@ -822,21 +802,21 @@ Null Relation
The `none` method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results.
```ruby
-Post.none # returns an empty Relation and fires no queries.
+Article.none # returns an empty Relation and fires no queries.
```
```ruby
-# The visible_posts method below is expected to return a Relation.
-@posts = current_user.visible_posts.where(name: params[:name])
+# The visible_articles method below is expected to return a Relation.
+@articles = current_user.visible_articles.where(name: params[:name])
-def visible_posts
+def visible_articles
case role
when 'Country Manager'
- Post.where(country: country)
+ Article.where(country: country)
when 'Reviewer'
- Post.published
+ Article.published
when 'Bad User'
- Post.none # => returning [] or nil breaks the caller code in this case
+ Article.none # => returning [] or nil breaks the caller code in this case
end
end
```
@@ -961,23 +941,23 @@ SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id =
WARNING: This method only works with `INNER JOIN`.
-Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method.
+Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method.
-For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models:
+For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models:
```ruby
class Category < ActiveRecord::Base
- has_many :posts
+ has_many :articles
end
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
belongs_to :category
has_many :comments
has_many :tags
end
class Comment < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
has_one :guest
end
@@ -986,7 +966,7 @@ class Guest < ActiveRecord::Base
end
class Tag < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
end
```
@@ -995,64 +975,64 @@ Now all of the following will produce the expected join queries using `INNER JOI
#### Joining a Single Association
```ruby
-Category.joins(:posts)
+Category.joins(:articles)
```
This produces:
```sql
SELECT categories.* FROM categories
- INNER JOIN posts ON posts.category_id = categories.id
+ INNER JOIN articles ON articles.category_id = categories.id
```
-Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).uniq`.
+Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`.
#### Joining Multiple Associations
```ruby
-Post.joins(:category, :comments)
+Article.joins(:category, :comments)
```
This produces:
```sql
-SELECT posts.* FROM posts
- INNER JOIN categories ON posts.category_id = categories.id
- INNER JOIN comments ON comments.post_id = posts.id
+SELECT articles.* FROM articles
+ INNER JOIN categories ON articles.category_id = categories.id
+ INNER JOIN comments ON comments.article_id = articles.id
```
-Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times.
+Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times.
#### Joining Nested Associations (Single Level)
```ruby
-Post.joins(comments: :guest)
+Article.joins(comments: :guest)
```
This produces:
```sql
-SELECT posts.* FROM posts
- INNER JOIN comments ON comments.post_id = posts.id
+SELECT articles.* FROM articles
+ INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id
```
-Or, in English: "return all posts that have a comment made by a guest."
+Or, in English: "return all articles that have a comment made by a guest."
#### Joining Nested Associations (Multiple Level)
```ruby
-Category.joins(posts: [{ comments: :guest }, :tags])
+Category.joins(articles: [{ comments: :guest }, :tags])
```
This produces:
```sql
SELECT categories.* FROM categories
- INNER JOIN posts ON posts.category_id = categories.id
- INNER JOIN comments ON comments.post_id = posts.id
+ INNER JOIN articles ON articles.category_id = categories.id
+ INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id
- INNER JOIN tags ON tags.post_id = posts.id
+ INNER JOIN tags ON tags.article_id = articles.id
```
### Specifying Conditions on the Joined Tables
@@ -1121,18 +1101,18 @@ Active Record lets you eager load any number of associations with a single `Mode
#### Array of Multiple Associations
```ruby
-Post.includes(:category, :comments)
+Article.includes(:category, :comments)
```
-This loads all the posts and the associated category and comments for each post.
+This loads all the articles and the associated category and comments for each article.
#### Nested Associations Hash
```ruby
-Category.includes(posts: [{ comments: :guest }, :tags]).find(1)
+Category.includes(articles: [{ comments: :guest }, :tags]).find(1)
```
-This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association.
+This will find the category with id 1 and eager load all of the associated articles, the associated articles' tags and comments, and every comment's guest association.
### Specifying Conditions on Eager Loaded Associations
@@ -1141,18 +1121,31 @@ Even though Active Record lets you specify conditions on the eager loaded associ
However if you must do this, you may use `where` as you would normally.
```ruby
-Post.includes(:comments).where("comments.visible" => true)
+Article.includes(:comments).where(comments: { visible: true })
```
-This would generate a query which contains a `LEFT OUTER JOIN` whereas the `joins` method would generate one using the `INNER JOIN` function instead.
+This would generate a query which contains a `LEFT OUTER JOIN` whereas the
+`joins` method would generate one using the `INNER JOIN` function instead.
```ruby
- SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1)
+ SELECT "articles"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "articles" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" WHERE (comments.visible = 1)
```
If there was no `where` condition, this would generate the normal set of two queries.
-If, in the case of this `includes` query, there were no comments for any posts, all the posts would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned.
+NOTE: Using `where` like this will only work when you pass it a Hash. For
+SQL-fragments you need use `references` to force joined tables:
+
+```ruby
+Article.includes(:comments).where("comments.visible = true").references(:comments)
+```
+
+If, in the case of this `includes` query, there were no comments for any
+articles, all the articles would still be loaded. By using `joins` (an INNER
+JOIN), the join conditions **must** match, otherwise no records will be
+returned.
+
+
Scopes
------
@@ -1162,7 +1155,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m
To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
```
@@ -1170,7 +1163,7 @@ end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
def self.published
where(published: true)
end
@@ -1180,7 +1173,7 @@ end
Scopes are also chainable within scopes:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :published_and_commented, -> { published.where("comments_count > 0") }
end
@@ -1189,14 +1182,14 @@ end
To call this `published` scope we can call it on either the class:
```ruby
-Post.published # => [published posts]
+Article.published # => [published articles]
```
-Or on an association consisting of `Post` objects:
+Or on an association consisting of `Article` objects:
```ruby
category = Category.first
-category.posts.published # => [published posts belonging to this category]
+category.articles.published # => [published articles belonging to this category]
```
### Passing in arguments
@@ -1204,7 +1197,7 @@ category.posts.published # => [published posts belonging to this category]
Your scope can take arguments:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
```
@@ -1212,13 +1205,13 @@ end
Call the scope as if it were a class method:
```ruby
-Post.created_before(Time.zone.now)
+Article.created_before(Time.zone.now)
```
However, this is just duplicating the functionality that would be provided to you by a class method.
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
def self.created_before(time)
where("created_at < ?", time)
end
@@ -1228,7 +1221,36 @@ end
Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects:
```ruby
-category.posts.created_before(time)
+category.articles.created_before(time)
+```
+
+### Applying a default scope
+
+If we wish for a scope to be applied across all queries to the model we can use the
+`default_scope` method within the model itself.
+
+```ruby
+class Client < ActiveRecord::Base
+ default_scope { where("removed_at IS NULL") }
+end
+```
+
+When queries are executed on this model, the SQL query will now look something like
+this:
+
+```sql
+SELECT * FROM clients WHERE removed_at IS NULL
+```
+
+If you need to do more complex things with a default scope, you can alternatively
+define it as a class method:
+
+```ruby
+class Client < ActiveRecord::Base
+ def self.default_scope
+ # Should return an ActiveRecord::Relation.
+ end
+end
```
### Merging of scopes
@@ -1242,26 +1264,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,48 +1294,18 @@ 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.
-
-### Applying a default scope
-
-If we wish for a scope to be applied across all queries to the model we can use the
-`default_scope` method within the model itself.
-
-```ruby
-class Client < ActiveRecord::Base
- default_scope { where("removed_at IS NULL") }
-end
-```
-
-When queries are executed on this model, the SQL query will now look something like
-this:
-
-```sql
-SELECT * FROM clients WHERE removed_at IS NULL
-```
-
-If you need to do more complex things with a default scope, you can alternatively
-define it as a class method:
-
-```ruby
-class Client < ActiveRecord::Base
- def self.default_scope
- # Should return an ActiveRecord::Relation.
- end
-end
-```
-
### Removing All Scoping
If we wish to remove scoping for any reason we can use the `unscoped` method. This is
@@ -1338,17 +1330,69 @@ Client.unscoped {
Dynamic Finders
---------------
-NOTE: Dynamic finders have been deprecated in Rails 4.0 and will be
-removed in Rails 4.1. The best practice is to use Active Record scopes
-instead. You can find the deprecation gem at
-https://github.com/rails/activerecord-deprecated_finders
-
For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods.
You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")`
If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`.
+Understanding The Method Chaining
+---------------------------------
+
+The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining),
+which allow us to use multiple Active Record methods together in a simple and straightforward way.
+
+You can chain methods in a statement when the previous method called returns an
+`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return
+a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object))
+have to be at the end of the statement.
+
+There are some examples below. This guide won't cover all the possibilities, just a few as examples.
+When an Active Record method is called, the query is not immediately generated and sent to the database,
+this just happens when the data is actually needed. So each example below generates a single query.
+
+### Retrieving filtered data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, comments.text')
+ .joins(:comments)
+ .where('comments.created_at > ?', 1.week.ago)
+```
+
+The result should be something like this:
+
+```sql
+SELECT people.id, people.name, comments.text
+FROM people
+INNER JOIN comments
+ ON comments.person_id = people.id
+WHERE comments.created_at = '2015-01-01'
+```
+
+### Retrieving specific data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, companies.name')
+ .joins(:company)
+ .find_by('people.name' => 'John') # this should be the last
+```
+
+The above should generate:
+
+```sql
+SELECT people.id, people.name, companies.name
+FROM people
+INNER JOIN companies
+ ON companies.person_id = people.id
+WHERE people.name = 'John'
+LIMIT 1
+```
+
+NOTE: Remember that, if `find_by` returns more than one registry, it will take
+just the first and ignore the others. Note the `LIMIT 1` statement above.
+
Find or Build a New Object
--------------------------
@@ -1455,7 +1499,12 @@ If you'd like to use your own SQL to find records in a table you can use `find_b
```ruby
Client.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
- ORDER clients.created_at desc")
+ ORDER BY clients.created_at desc")
+# => [
+ #<Client id: 1, first_name: "Lucas" >,
+ #<Client id: 2, first_name: "Jan" >,
+ # ...
+]
```
`find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects.
@@ -1465,12 +1514,16 @@ Client.find_by_sql("SELECT * FROM clients
`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record.
```ruby
-Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
+Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'")
+# => [
+ {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
+ {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
+]
```
### `pluck`
-`pluck` can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type.
+`pluck` can be used to query single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type.
```ruby
Client.where(active: true).pluck(:id)
@@ -1592,20 +1645,20 @@ You can also use `any?` and `many?` to check for existence on a model or relatio
```ruby
# via a model
-Post.any?
-Post.many?
+Article.any?
+Article.many?
# via a named scope
-Post.recent.any?
-Post.recent.many?
+Article.recent.any?
+Article.recent.many?
# via a relation
-Post.where(published: true).any?
-Post.where(published: true).many?
+Article.where(published: true).any?
+Article.where(published: true).many?
# via an association
-Post.first.categories.any?
-Post.first.categories.many?
+Article.first.categories.any?
+Article.first.categories.many?
```
Calculations
@@ -1695,19 +1748,26 @@ Running EXPLAIN
You can run EXPLAIN on the queries triggered by relations. For example,
```ruby
-User.where(id: 1).joins(:posts).explain
+User.where(id: 1).joins(:articles).explain
```
may yield
```
-EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
++----+-------------+----------+-------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+----------+-------+---------------+
+| 1 | SIMPLE | users | const | PRIMARY |
+| 1 | SIMPLE | articles | ALL | NULL |
++----+-------------+----------+-------+---------------+
++---------+---------+-------+------+-------------+
+| key | key_len | ref | rows | Extra |
++---------+---------+-------+------+-------------+
+| PRIMARY | 4 | const | 1 | |
+| NULL | NULL | NULL | 1 | Using where |
++---------+---------+-------+------+-------------+
+
2 rows in set (0.00 sec)
```
@@ -1717,15 +1777,15 @@ Active Record performs a pretty printing that emulates the one of the database
shells. So, the same query running with the PostgreSQL adapter would yield instead
```
-EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
+EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
- Join Filter: (posts.user_id = users.id)
+ Join Filter: (articles.user_id = users.id)
-> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
Index Cond: (id = 1)
- -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
- Filter: (posts.user_id = 1)
+ -> Seq Scan on articles (cost=0.00..28.88 rows=8 width=4)
+ Filter: (articles.user_id = 1)
(6 rows)
```
@@ -1734,26 +1794,39 @@ may need the results of previous ones. Because of that, `explain` actually
executes the query, and then asks for the query plans. For example,
```ruby
-User.where(id: 1).includes(:posts).explain
+User.where(id: 1).includes(:articles).explain
```
yields
```
EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
-| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
++----+-------------+-------+-------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+-------+-------+---------------+
+| 1 | SIMPLE | users | const | PRIMARY |
++----+-------------+-------+-------+---------------+
++---------+---------+-------+------+-------+
+| key | key_len | ref | rows | Extra |
++---------+---------+-------+------+-------+
+| PRIMARY | 4 | const | 1 | |
++---------+---------+-------+------+-------+
+
1 row in set (0.00 sec)
-EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
-| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
+EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN (1)
++----+-------------+----------+------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+----------+------+---------------+
+| 1 | SIMPLE | articles | ALL | NULL |
++----+-------------+----------+------+---------------+
++------+---------+------+------+-------------+
+| key | key_len | ref | rows | Extra |
++------+---------+------+------+-------------+
+| NULL | NULL | NULL | 1 | Using where |
++------+---------+------+------+-------------+
+
+
1 row in set (0.00 sec)
```