diff options
Diffstat (limited to 'guides/source/active_record_querying.md')
-rw-r--r-- | guides/source/active_record_querying.md | 70 |
1 files changed, 42 insertions, 28 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index bef903b751..1427903dfb 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -10,7 +10,7 @@ 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 use dynamic finder 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. @@ -80,7 +80,7 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `uniq` +* `distinct` * `where` All of the above methods return an instance of `ActiveRecord::Relation`. @@ -317,7 +317,7 @@ end The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`. -Two additional options, `:batch_size` and `:begin_at`, are available as well. +Three additional options, `:batch_size`, `:begin_at` and `:end_at`, are available as well. **`:batch_size`** @@ -341,14 +341,12 @@ User.find_each(begin_at: 2000, batch_size: 5000) do |user| end ``` -Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:begin_at` option on each worker. - **`:end_at`** Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need. This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at` -For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 1000: +For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000: ```ruby User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user| @@ -356,6 +354,10 @@ User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user| end ``` +Another example would be if you wanted multiple workers handling the same +processing queue. You could have each worker handle 10000 records by setting the +appropriate `:begin_at` and `:end_at` options on each worker. + #### `find_in_batches` The `find_in_batches` method is similar to `find_each`, since both retrieve batches of records. The difference is that `find_in_batches` yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 invoices at a time, with the final block containing any remaining invoices: @@ -390,7 +392,7 @@ Now what if that number could vary, say as an argument from somewhere? The find Client.where("orders_count = ?", params[:orders]) ``` -Active Record will go through the first element in the conditions value and any additional elements will replace the question marks `(?)` in the first element. +Active Record will take the first argument as the conditions string and any additional arguments will replace the question marks `(?)` in it. If you want to specify multiple conditions: @@ -418,7 +420,7 @@ TIP: For more information on the dangers of SQL injection, see the [Ruby on Rail #### Placeholder Conditions -Similar to the `(?)` replacement style of params, you can also specify keys/values hash in your array conditions: +Similar to the `(?)` replacement style of params, you can also specify keys in your conditions string along with a corresponding keys/values hash: ```ruby Client.where("created_at >= :start_date AND created_at <= :end_date", @@ -429,7 +431,7 @@ This makes for clearer readability if you have a large number of variable condit ### Hash Conditions -Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: +Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want qualified and the values of how you want to qualify them: NOTE: Only equality, range and subset checking are possible with Hash conditions. @@ -529,7 +531,7 @@ Client.order("orders_count ASC, created_at DESC") Client.order("orders_count ASC", "created_at DESC") ``` -If you want to call `order` multiple times e.g. in different context, new order will append previous one +If you want to call `order` multiple times, subsequent orders will be appended to the first: ```ruby Client.order("orders_count ASC").order("created_at DESC") @@ -617,9 +619,9 @@ SELECT * FROM clients LIMIT 5 OFFSET 30 Group ----- -To apply a `GROUP BY` clause to the SQL fired by the finder, you can specify the `group` method on the find. +To apply a `GROUP BY` clause to the SQL fired by the finder, you can use the `group` method. -For example, if you want to find a collection of the dates orders were created on: +For example, if you want to find a collection of the dates on which orders were created: ```ruby Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") @@ -637,7 +639,7 @@ 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`. +To get the total of grouped items on a single query, call `count` after the `group`. ```ruby Order.group(:status).count @@ -655,7 +657,7 @@ GROUP BY status Having ------ -SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `:having` option to the find. +SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `having` method to the find. For example: @@ -673,7 +675,7 @@ GROUP BY date(created_at) HAVING sum(price) > 100 ``` -This will return single order objects for each day, but only those that are ordered more than $100 in a day. +This returns the date and total price for each order object, grouped by the day they were ordered and where the price is more than $100. Overriding Conditions --------------------- @@ -703,8 +705,7 @@ 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: +A relation which has used `unscope` will affect any relation into which it is merged: ```ruby Article.order('id asc').merge(Article.unscope(:order)) @@ -748,7 +749,7 @@ 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: +In the case where the `reorder` clause is not used, the SQL executed would be: ```sql SELECT * FROM articles WHERE id = 10 @@ -837,7 +838,7 @@ end Readonly Objects ---------------- -Active Record provides `readonly` method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an `ActiveRecord::ReadOnlyRecord` exception. +Active Record provides the `readonly` method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an `ActiveRecord::ReadOnlyRecord` exception. ```ruby client = Client.readonly.first @@ -898,7 +899,7 @@ For example: Item.transaction do i = Item.lock.first i.name = 'Jones' - i.save + i.save! end ``` @@ -998,7 +999,7 @@ SELECT categories.* FROM categories INNER JOIN articles ON articles.category_id = categories.id ``` -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`. +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).distinct`. #### Joining Multiple Associations @@ -1050,7 +1051,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) provide a special syntax for specifying conditions for the joined tables: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -1089,7 +1090,7 @@ This code looks fine at the first sight. But the problem lies within the total n Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the `includes` method of the `Model.find` call. With `includes`, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries. -Revisiting the above case, we could rewrite `Client.limit(10)` to use eager load addresses: +Revisiting the above case, we could rewrite `Client.limit(10)` to eager load addresses: ```ruby clients = Client.includes(:address).limit(10) @@ -1266,6 +1267,18 @@ class Client < ActiveRecord::Base end ``` +NOTE: The `default_scope` is also applied while creating/building a record. +It is not applied while updating a record. E.g.: + +```ruby +class Client < ActiveRecord::Base + default_scope { where(active: true) } +end + +Client.new # => #<Client id: nil, active: true> +Client.unscoped.new # => #<Client id: nil, active: nil> +``` + ### Merging of scopes Just like `where` clauses scopes are merged using `AND` conditions. @@ -1343,7 +1356,7 @@ Client.unscoped { 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` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods. +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` method. 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")` @@ -1414,7 +1427,7 @@ It's common that you need to find a record or create it if it doesn't exist. You ### `find_or_create_by` -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. +The `find_or_create_by` method checks whether a record with the specified 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. You can do so by running: @@ -1787,8 +1800,9 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.` under MySQL. -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 +Active Record performs a pretty printing that emulates that of the +corresponding database shell. So, the same query running with the +PostgreSQL adapter would yield instead ``` EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1 @@ -1853,6 +1867,6 @@ following pointers may be helpful: * SQLite3: [EXPLAIN QUERY PLAN](http://www.sqlite.org/eqp.html) -* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.6/en/explain-output.html) +* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html) * PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) |