diff options
Diffstat (limited to 'guides/source/active_record_querying.md')
-rw-r--r-- | guides/source/active_record_querying.md | 151 |
1 files changed, 114 insertions, 37 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 2589accadd..4725e2c8a2 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -58,6 +58,7 @@ The methods are: * `bind` * `create_with` +* `distinct` * `eager_load` * `extending` * `from` @@ -76,7 +77,6 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `distinct` * `uniq` * `where` @@ -91,7 +91,7 @@ The primary operation of `Model.find(options)` can be summarized as: ### Retrieving a Single Object -Active Record provides five different ways of retrieving a single object. +Active Record provides several different ways of retrieving a single object. #### Using a Primary Key @@ -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)`. @@ -524,12 +524,18 @@ To retrieve records from the database in a specific order, you can use the `orde For example, if you're getting a set of records and want to order them in ascending order by the `created_at` field in your table: ```ruby +Client.order(:created_at) +# OR Client.order("created_at") ``` You could specify `ASC` or `DESC` as well: ```ruby +Client.order(created_at: :desc) +# OR +Client.order(created_at: :asc) +# OR Client.order("created_at DESC") # OR Client.order("created_at ASC") @@ -538,16 +544,20 @@ Client.order("created_at ASC") Or ordering by multiple fields: ```ruby +Client.order(orders_count: :asc, created_at: :desc) +# OR +Client.order(:orders_count, created_at: :desc) +# OR Client.order("orders_count ASC, created_at DESC") # OR Client.order("orders_count ASC", "created_at DESC") ``` -If you want to call `order` multiple times e.g. in different context, new order will prepend previous one +If you want to call `order` multiple times e.g. in different context, new order will append previous one ```ruby Client.order("orders_count ASC").order("created_at DESC") -# SELECT * FROM clients ORDER BY created_at DESC, orders_count ASC +# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC ``` Selecting Specific Fields @@ -675,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) @@ -687,27 +697,25 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 LIMIT 20 -``` - -### `unscope` -The `except` method does not work when the relation is merged. For example: +# Original query without `unscope` +SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 -```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` @@ -722,6 +730,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 ORDER BY id DESC + +# Original query without `only` +SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 + ``` ### `reorder` @@ -732,7 +744,7 @@ The `reorder` method overrides the default scope order. For example: class Post < ActiveRecord::Base .. .. - has_many :comments, order: 'posted_at DESC' + has_many :comments, -> { order('posted_at DESC') } end Post.find(10).comments.reorder('name') @@ -778,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 ------------- @@ -925,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 @@ -1004,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: @@ -1030,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. @@ -1091,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. @@ -1171,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) @@ -1203,7 +1241,6 @@ class User < ActiveRecord::Base scope :inactive, -> { where state: 'inactive' } end -```ruby User.active.inactive # => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' ``` @@ -1229,7 +1266,7 @@ One important caveat is that `default_scope` will be overridden by ```ruby class User < ActiveRecord::Base - default_scope { where state: 'pending' } + default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end @@ -1284,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. @@ -1301,6 +1338,11 @@ 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")` @@ -1336,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. @@ -1444,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 } @@ -1454,7 +1496,7 @@ Client.select(:id).map(&:id) Client.select(:id, :name).map { |c| [c.id, c.name] } ``` -with +with: ```ruby Client.pluck(:id) @@ -1462,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. @@ -1483,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. @@ -1503,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? @@ -1553,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: |