diff options
Diffstat (limited to 'guides/source/active_record_querying.md')
-rw-r--r-- | guides/source/active_record_querying.md | 373 |
1 files changed, 246 insertions, 127 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 66e6390f67..57e8e080f4 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1,15 +1,17 @@ Active Record Query Interface ============================= -This guide covers different ways to retrieve data from the database using Active Record. By referring to this guide, you will be able to: +This guide covers different ways to retrieve data from the database using Active Record. -* Find records using a variety of methods and conditions -* Specify the order, retrieved attributes, grouping, and other properties of the found records -* Use eager loading to reduce the number of database queries needed for data retrieval -* Use dynamic finders methods -* Check for the existence of particular records -* Perform various calculations on Active Record models -* Run EXPLAIN on relations +After reading this guide, you will know: + +* How to find records using a variety of methods and conditions. +* 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 check for the existence of particular records. +* How to perform various calculations on Active Record models. +* How to run EXPLAIN on relations. -------------------------------------------------------------------------------- @@ -35,7 +37,7 @@ end ```ruby class Order < ActiveRecord::Base - belongs_to :client, :counter_cache => true + belongs_to :client, counter_cache: true end ``` @@ -56,6 +58,7 @@ The methods are: * `bind` * `create_with` +* `distinct` * `eager_load` * `extending` * `from` @@ -88,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 @@ -297,7 +300,7 @@ Client.first(2) The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 2 +SELECT * FROM clients ORDER BY id ASC LIMIT 2 ``` #### last @@ -313,7 +316,7 @@ Client.last(2) The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER By id DESC LIMIT 2 +SELECT * FROM clients ORDER BY id DESC LIMIT 2 ``` ### Retrieving Multiple Objects in Batches @@ -356,7 +359,7 @@ Two additional options, `:batch_size` and `:start`, are available as well. The `:batch_size` option allows you to specify the number of records to be retrieved in each batch, before being passed individually to the block. For example, to retrieve records in batches of 5000: ```ruby -User.find_each(:batch_size => 5000) do |user| +User.find_each(batch_size: 5000) do |user| NewsLetter.weekly_deliver(user) end ``` @@ -368,7 +371,7 @@ By default, records are fetched in ascending order of the primary key, which mus For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000: ```ruby -User.find_each(:start => 2000, :batch_size => 5000) do |user| +User.find_each(start: 2000, batch_size: 5000) do |user| NewsLetter.weekly_deliver(user) end ``` @@ -381,7 +384,7 @@ 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(include: :invoice_lines) do |invoices| export.add_invoices(invoices) end ``` @@ -443,7 +446,7 @@ Similar to the `(?)` replacement style of params, you can also specify keys/valu ```ruby Client.where("created_at >= :start_date AND created_at <= :end_date", - {:start_date => params[:start_date], :end_date => params[:end_date]}) + {start_date: params[:start_date], end_date: params[:end_date]}) ``` This makes for clearer readability if you have a large number of variable conditions. @@ -457,7 +460,7 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions #### Equality Conditions ```ruby -Client.where(:locked => true) +Client.where(locked: true) ``` The field name can also be a string: @@ -466,19 +469,19 @@ The field name can also be a string: Client.where('locked' => true) ``` -In the case of a belongs_to relationship, an association key can be used to specify the model if an ActiveRecord object is used as the value. This method works with polymorphic relationships as well. +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}) +Post.where(author: author) +Author.joins(:posts).where(posts: {author: author}) ``` -NOTE: The values cannot be symbols. For example, you cannot do `Client.where(:status => :active)`. +NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`. #### Range Conditions ```ruby -Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight) +Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) ``` This will find all clients created yesterday by using a `BETWEEN` SQL statement: @@ -494,7 +497,7 @@ This demonstrates a shorter syntax for the examples in [Array Conditions](#array If you want to find records using the `IN` expression you can pass an array to the conditions hash: ```ruby -Client.where(:orders_count => [1,3,5]) +Client.where(orders_count: [1,3,5]) ``` This code will generate SQL like this: @@ -503,6 +506,16 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) ``` +### NOT Conditions + +`NOT` SQL queries can be built by `where.not`. + +```ruby +Post.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. + Ordering -------- @@ -511,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") @@ -525,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 @@ -564,10 +587,10 @@ ActiveModel::MissingAttributeError: missing attribute: <attribute> Where `<attribute>` is the attribute you asked for. The `id` method will not raise the `ActiveRecord::MissingAttributeError`, so just be careful when working with associations because they need the `id` method to function properly. -If you would like to only grab a single record per unique value in a certain field, you can use `uniq`: +If you would like to only grab a single record per unique value in a certain field, you can use `distinct`: ```ruby -Client.select(:name).uniq +Client.select(:name).distinct ``` This would generate SQL like: @@ -579,10 +602,10 @@ SELECT DISTINCT name FROM clients You can also remove the uniqueness constraint: ```ruby -query = Client.select(:name).uniq +query = Client.select(:name).distinct # => Returns unique names -query.uniq(false) +query.distinct(false) # => Returns all names, even if there are duplicates ``` @@ -674,6 +697,31 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 LIMIT 20 + +# Original query without `except` +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: + +```ruby +Post.order('id DESC').limit(20).unscope(:order) = Post.limit(20) +Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all +``` + +You can additionally unscope specific where clauses. For example: + +```ruby +Post.where(id: 10).limit(1).unscope({ where: :id }, :limit).order('id DESC') = Post.order('id DESC') ``` ### `only` @@ -688,6 +736,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` @@ -698,7 +750,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') @@ -755,12 +807,12 @@ Post.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]) +@posts = current_user.visible_posts.where(name: params[:name]) def visible_posts case role when 'Country Manager' - Post.where(:country => country) + Post.where(country: country) when 'Reviewer' Post.published when 'Bad User' @@ -891,7 +943,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 @@ -933,7 +985,7 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.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).select("distinct(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`. #### Joining Multiple Associations @@ -954,7 +1006,7 @@ Or, in English: "return all posts that have a category and at least one comment" #### Joining Nested Associations (Single Level) ```ruby -Post.joins(:comments => :guest) +Post.joins(comments: :guest) ``` This produces: @@ -970,7 +1022,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: @@ -985,7 +1037,7 @@ SELECT categories.* FROM categories ### Specifying Conditions on the Joined Tables -You can specify conditions on the joined tables using the regular [Array](array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provides a special syntax for specifying conditions for the joined tables: +You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provides a special syntax for specifying conditions for the joined tables: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -996,7 +1048,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. @@ -1057,7 +1109,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. @@ -1087,7 +1139,7 @@ Scopes Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as `where`, `joins` and `includes`. All scope methods will return an `ActiveRecord::Relation` object which will allow for further methods (such as other scopes) to be called on it. -To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like run when this scope is called: +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 @@ -1109,7 +1161,7 @@ Scopes are also chainable within scopes: ```ruby class Post < ActiveRecord::Base - scope :published, -> { where(:published => true) } + scope :published, -> { where(published: true) } scope :published_and_commented, -> { published.where("comments_count > 0") } end ``` @@ -1137,7 +1189,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) @@ -1159,6 +1211,60 @@ Using a class method is the preferred way to accept arguments for scopes. These category.posts.created_before(time) ``` +### Merging of scopes + +Just like `where` clauses scopes are merged using `AND` conditions. + +```ruby +class User < ActiveRecord::Base + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } +end + +User.active.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` . + +```ruby +User.active.where(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 . + +```ruby +User.active.merge(User.inactive) +# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +``` + +One important caveat is that `default_scope` will be overridden by +`scope` and `where` conditions. + +```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' +``` + +As you can see above the `default_scope` is being overridden by 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 @@ -1188,7 +1294,7 @@ class Client < ActiveRecord::Base end ``` -### Removing all scoping +### Removing All Scoping If we wish to remove scoping for any reason we can use the `unscoped` method. This is especially useful if a `default_scope` is specified in the model and should not be @@ -1205,37 +1311,38 @@ recommended that you use the block form of `unscoped`: ```ruby Client.unscoped { - Client.created_before(Time.zome.now) + Client.created_before(Time.zone.now) } ``` Dynamic 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` and `find_all_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 `find_all_by_locked` methods. +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 -You can also use `find_last_by_*` methods which will find the last record matching your argument. +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)`. -WARNING: Up to and including Rails 3.1, when the number of arguments passed to a dynamic finder method is lesser than the number of fields, say `Client.find_by_name_and_locked("Ryan")`, the behavior is to pass `nil` as the missing argument. This is **unintentional** and this behavior will be changed in Rails 3.2 to throw an `ArgumentError`. - -Find or build a new object +Find or Build a New Object -------------------------- -It's common that you need to find a record or create it if it doesn't exist. You can do that with the `first_or_create` and `first_or_create!` methods. +It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods. -### `first_or_create` +### `find_or_create_by` -The `first_or_create` method checks whether `first` returns `nil` or not. If it does return `nil`, then `create` is called. This is very powerful when coupled with the `where` method. Let's see an example. +The `find_or_create_by` method checks whether a record with the attributes exists. If it doesn't, then `create` is called. Let's see an example. -Suppose you want to find a client named 'Andy', and if there's none, create one and additionally set his `locked` attribute to false. You can do so by running: +Suppose you want to find a client named 'Andy', and if there's none, create one. You can do so by running: ```ruby -Client.where(:first_name => 'Andy').first_or_create(:locked => false) -# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> +Client.find_or_create_by(first_name: 'Andy') +# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> ``` The SQL generated by this method looks like this: @@ -1243,48 +1350,62 @@ The SQL generated by this method looks like this: ```sql SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 BEGIN -INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57') +INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT ``` -`first_or_create` returns either the record that already exists or the new record. In our case, we didn't already have a client named Andy so the record is created and returned. +`find_or_create_by` returns either the record that already exists or the new record. In our case, we didn't already have a client named Andy so the record is created and returned. The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`). -It's also worth noting that `first_or_create` takes into account the arguments of the `where` method. In the example above we didn't explicitly pass a `:first_name => 'Andy'` argument to `first_or_create`. However, that was used when creating the new record because it was already passed before to the `where` method. +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. + +We can achieve this in two ways. The first is to use `create_with`: + +```ruby +Client.create_with(locked: false).find_or_create_by(first_name: 'Andy') +``` -You can do the same with the `find_or_create_by` method: +The second way is using a block: ```ruby -Client.find_or_create_by_first_name(:first_name => "Andy", :locked => false) +Client.find_or_create_by(first_name: 'Andy') do |c| + c.locked = false +end ``` -This method still works, but it's encouraged to use `first_or_create` because it's more explicit on which arguments are used to _find_ the record and which are used to _create_, resulting in less confusion overall. +The block will only be executed if the client is being created. The +second time we run this code, the block will be ignored. -### `first_or_create!` +### `find_or_create_by!` -You can also use `first_or_create!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add +You can also use `find_or_create_by!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add ```ruby -validates :orders_count, :presence => true +validates :orders_count, presence: true ``` to your `Client` model. If you try to create a new `Client` without passing an `orders_count`, the record will be invalid and an exception will be raised: ```ruby -Client.where(:first_name => 'Andy').first_or_create!(:locked => false) +Client.find_or_create_by!(first_name: 'Andy') # => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank ``` -As with `first_or_create` there is a `find_or_create_by!` method but the `first_or_create!` method is preferred for clarity. - -### `first_or_initialize` +### `find_or_initialize_by` -The `first_or_initialize` method will work just like `first_or_create` but it will not call `create` but `new`. This means that a new model instance will be created in memory but won't be saved to the database. Continuing with the `first_or_create` example, we now want the client named 'Nick': +The `find_or_initialize_by` method will work just like +`find_or_create_by` but it will call `new` instead of `create`. This +means that a new model instance will be created in memory but won't be +saved to the database. Continuing with the `find_or_create_by` example, we +now want the client named 'Nick': ```ruby -nick = Client.where(:first_name => 'Nick').first_or_initialize(:locked => false) -# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> +nick = Client.find_or_initialize_by(first_name: 'Nick') +# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> nick.persisted? # => false @@ -1332,11 +1453,11 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'") `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. ```ruby -Client.where(:active => true).pluck(:id) +Client.where(active: true).pluck(:id) # SELECT id FROM clients WHERE active = 1 # => [1, 2, 3] -Client.uniq.pluck(:role) +Client.distinct.pluck(:role) # SELECT DISTINCT role FROM clients # => ['admin', 'member', 'guest'] @@ -1345,15 +1466,17 @@ 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 } # or -Client.select(:id).map { |c| [c.id, c.name] } +Client.select(:id).map(&:id) +# or +Client.select(:id, :name).map { |c| [c.id, c.name] } ``` -with +with: ```ruby Client.pluck(:id) @@ -1361,6 +1484,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. @@ -1382,27 +1536,31 @@ 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. ```ruby -Client.where(:first_name => 'Ryan').exists? +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? @@ -1422,8 +1580,8 @@ Post.recent.any? Post.recent.many? # via a relation -Post.where(:published => true).any? -Post.where(:published => true).many? +Post.where(published: true).any? +Post.where(published: true).many? # via an association Post.first.categories.any? @@ -1445,14 +1603,14 @@ Client.count Or on a relation: ```ruby -Client.where(:first_name => 'Ryan').count +Client.where(first_name: 'Ryan').count # SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') ``` 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: @@ -1517,7 +1675,7 @@ 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(:posts).explain ``` may yield @@ -1556,7 +1714,7 @@ 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(:posts).explain ``` yields @@ -1581,45 +1739,6 @@ EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1) under MySQL. -### Automatic EXPLAIN - -Active Record is able to run EXPLAIN automatically on slow queries and log its -output. This feature is controlled by the configuration parameter - -```ruby -config.active_record.auto_explain_threshold_in_seconds -``` - -If set to a number, any query exceeding those many seconds will have its EXPLAIN -automatically triggered and logged. In the case of relations, the threshold is -compared to the total time needed to fetch records. So, a relation is seen as a -unit of work, no matter whether the implementation of eager loading involves -several queries under the hood. - -A threshold of `nil` disables automatic EXPLAINs. - -The default threshold in development mode is 0.5 seconds, and `nil` in test and -production modes. - -INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless -of the value of the threshold. - -#### Disabling Automatic EXPLAIN - -Automatic EXPLAIN can be selectively silenced with `ActiveRecord::Base.silence_auto_explain`: - -```ruby -ActiveRecord::Base.silence_auto_explain do - # no automatic EXPLAIN is triggered here -end -``` - -That may be useful for queries you know are slow but fine, like a heavyweight -report of an admin interface. - -As its name suggests, `silence_auto_explain` only silences automatic EXPLAINs. -Explicit calls to `ActiveRecord::Relation#explain` run. - ### Interpreting EXPLAIN Interpretation of the output of EXPLAIN is beyond the scope of this guide. The |