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.md121
1 files changed, 88 insertions, 33 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index ba0dc6d9eb..4725e2c8a2 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -436,7 +436,7 @@ to this code:
Client.where("orders_count = #{params[:orders]}")
```
-because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
+because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out they can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
TIP: For more information on the dangers of SQL injection, see the [Ruby on Rails Security Guide](security.html#sql-injection).
@@ -473,7 +473,7 @@ In the case of a belongs_to relationship, an association key can be used to spec
```ruby
Post.where(author: author)
-Author.joins(:posts).where(posts: {author: author})
+Author.joins(:posts).where(posts: { author: author })
```
NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`.
@@ -685,9 +685,9 @@ This will return single order objects for each day, but only those that are orde
Overriding Conditions
---------------------
-### `except`
+### `unscope`
-You can specify certain conditions to be excepted by using the `except` method. For example:
+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)
@@ -698,30 +698,24 @@ The SQL that would be executed:
```sql
SELECT * FROM posts WHERE id > 10 LIMIT 20
-# Original query without `except`
+# Original query without `unscope`
SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20
```
-### `unscope`
-
-The `except` method does not work when the relation is merged. For example:
-
-```ruby
-Post.comments.except(:order)
-```
-
-will still have an order if the order comes from a default scope on Comment. In order to remove all ordering, even from relations which are merged in, use unscope as follows:
+You can additionally unscope specific where clauses. For example:
```ruby
-Post.order('id DESC').limit(20).unscope(:order) = Post.limit(20)
-Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all
+Post.where(id: 10, trashed: false).unscope(where: :id)
+# => SELECT "posts".* FROM "posts" WHERE trashed = 0
```
-You can additionally unscope specific where clauses. For example:
+A relation which has used `unscope` will affect any relation it is
+merged in to:
```ruby
-Post.where(id: 10).limit(1).unscope({ where: :id }, :limit).order('id DESC') = Post.order('id DESC')
+Post.order('id asc').merge(Post.unscope(:order))
+# => SELECT "posts".* FROM "posts"
```
### `only`
@@ -796,6 +790,32 @@ SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC
This method accepts **no** arguments.
+### `rewhere`
+
+The `rewhere` method overrides an existing, named where condition. For example:
+
+```ruby
+Post.where(trashed: true).rewhere(trashed: false)
+```
+
+The SQL that would be executed:
+
+```sql
+SELECT * FROM posts WHERE `trashed` = 0
+```
+
+In case the `rewhere` clause is not used,
+
+```ruby
+Post.where(trashed: true).where(trashed: false)
+```
+
+the SQL executed would be:
+
+```sql
+SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0
+```
+
Null Relation
-------------
@@ -943,7 +963,7 @@ 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.
-For example, consider the following `Category`, `Post`, `Comments` and `Guest` models:
+For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models:
```ruby
class Category < ActiveRecord::Base
@@ -1022,7 +1042,7 @@ Or, in English: "return all posts that have a comment made by a guest."
#### Joining Nested Associations (Multiple Level)
```ruby
-Category.joins(posts: [{comments: :guest}, :tags])
+Category.joins(posts: [{ comments: :guest }, :tags])
```
This produces:
@@ -1048,7 +1068,7 @@ An alternative and cleaner syntax is to nest the hash conditions:
```ruby
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
-Client.joins(:orders).where(orders: {created_at: time_range})
+Client.joins(:orders).where(orders: { created_at: time_range })
```
This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression.
@@ -1109,7 +1129,7 @@ This loads all the posts and the associated category and comments for each post.
#### Nested Associations Hash
```ruby
-Category.includes(posts: [{comments: :guest}, :tags]).find(1)
+Category.includes(posts: [{ 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.
@@ -1189,7 +1209,7 @@ class Post < ActiveRecord::Base
end
```
-This may then be called using this:
+Call the scope as if it were a class method:
```ruby
Post.created_before(Time.zone.now)
@@ -1301,7 +1321,7 @@ especially useful if a `default_scope` is specified in the model and should not
applied for this particular query.
```ruby
-Client.unscoped.all
+Client.unscoped.load
```
This method removes all scoping and will do a normal query on the table.
@@ -1358,7 +1378,7 @@ COMMIT
The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`).
-Suppose we want to set the 'locked' attribute to true if we're
+Suppose we want to set the 'locked' attribute to `false` if we're
creating a new record, but we don't want to include it in the query. So
we want to find the client named "Andy", or if that client doesn't
exist, create a client named "Andy" which is not locked.
@@ -1466,7 +1486,7 @@ Client.pluck(:id, :name)
# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
```
-`pluck` makes it possible to replace code like
+`pluck` makes it possible to replace code like:
```ruby
Client.select(:id).map { |c| c.id }
@@ -1476,7 +1496,7 @@ Client.select(:id).map(&:id)
Client.select(:id, :name).map { |c| [c.id, c.name] }
```
-with
+with:
```ruby
Client.pluck(:id)
@@ -1484,6 +1504,37 @@ Client.pluck(:id)
Client.pluck(:id, :name)
```
+Unlike `select`, `pluck` directly converts a database result into a Ruby `Array`,
+without constructing `ActiveRecord` objects. This can mean better performance for
+a large or often-running query. However, any model method overrides will
+not be available. For example:
+
+```ruby
+class Client < ActiveRecord::Base
+ def name
+ "I am #{super}"
+ end
+end
+
+Client.select(:name).map &:name
+# => ["I am David", "I am Jeremy", "I am Jose"]
+
+Client.pluck(:name)
+# => ["David", "Jeremy", "Jose"]
+```
+
+Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate
+query, and thus cannot be chained with any further scopes, although it can work with
+scopes already constructed earlier:
+
+```ruby
+Client.pluck(:name).limit(1)
+# => NoMethodError: undefined method `limit' for #<Array:0x007ff34d3ad6d8>
+
+Client.limit(1).pluck(:name)
+# => ["David"]
+```
+
### `ids`
`ids` can be used to pluck all the IDs for the relation using the table's primary key.
@@ -1505,18 +1556,21 @@ Person.ids
Existence of Objects
--------------------
-If you simply want to check for the existence of the object there's a method called `exists?`. This method will query the database using the same query as `find`, but instead of returning an object or collection of objects it will return either `true` or `false`.
+If you simply want to check for the existence of the object there's a method called `exists?`.
+This method will query the database using the same query as `find`, but instead of returning an
+object or collection of objects it will return either `true` or `false`.
```ruby
Client.exists?(1)
```
-The `exists?` method also takes multiple ids, but the catch is that it will return true if any one of those records exists.
+The `exists?` method also takes multiple values, but the catch is that it will return `true` if any
+one of those records exists.
```ruby
-Client.exists?(1,2,3)
+Client.exists?(id: [1,2,3])
# or
-Client.exists?([1,2,3])
+Client.exists?(name: ['John', 'Sergei'])
```
It's even possible to use `exists?` without any arguments on a model or a relation.
@@ -1525,7 +1579,8 @@ It's even possible to use `exists?` without any arguments on a model or a relati
Client.where(first_name: 'Ryan').exists?
```
-The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` otherwise.
+The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false`
+otherwise.
```ruby
Client.exists?
@@ -1575,7 +1630,7 @@ Client.where(first_name: 'Ryan').count
You can also use various finder methods on a relation for performing complex calculations:
```ruby
-Client.includes("orders").where(first_name: 'Ryan', orders: {status: 'received'}).count
+Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received' }).count
```
Which will execute: