railties 3.1.12 → 3.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. data/CHANGELOG.md +2292 -41
  2. data/README.rdoc +14 -5
  3. data/bin/rails +7 -0
  4. data/guides/code/getting_started/Gemfile +27 -0
  5. data/guides/code/getting_started/README.rdoc +261 -0
  6. data/guides/code/getting_started/Rakefile +7 -0
  7. data/guides/code/getting_started/app/assets/images/rails.png +0 -0
  8. data/guides/code/getting_started/app/assets/javascripts/application.js +9 -0
  9. data/guides/code/getting_started/app/assets/javascripts/comments.js.coffee +3 -0
  10. data/guides/code/getting_started/app/assets/javascripts/home.js.coffee +3 -0
  11. data/guides/code/getting_started/app/assets/javascripts/posts.js.coffee +3 -0
  12. data/guides/code/getting_started/app/assets/stylesheets/application.css +7 -0
  13. data/guides/code/getting_started/app/assets/stylesheets/comments.css.scss +3 -0
  14. data/guides/code/getting_started/app/assets/stylesheets/home.css.scss +3 -0
  15. data/guides/code/getting_started/app/assets/stylesheets/posts.css.scss +3 -0
  16. data/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss +56 -0
  17. data/guides/code/getting_started/app/controllers/application_controller.rb +3 -0
  18. data/guides/code/getting_started/app/controllers/comments_controller.rb +16 -0
  19. data/guides/code/getting_started/app/controllers/home_controller.rb +5 -0
  20. data/guides/code/getting_started/app/controllers/posts_controller.rb +84 -0
  21. data/guides/code/getting_started/app/helpers/application_helper.rb +2 -0
  22. data/guides/code/getting_started/app/helpers/comments_helper.rb +2 -0
  23. data/guides/code/getting_started/app/helpers/home_helper.rb +2 -0
  24. data/guides/code/getting_started/app/helpers/posts_helper.rb +5 -0
  25. data/guides/code/getting_started/app/models/comment.rb +3 -0
  26. data/guides/code/getting_started/app/models/post.rb +11 -0
  27. data/guides/code/getting_started/app/models/tag.rb +3 -0
  28. data/guides/code/getting_started/app/views/comments/_comment.html.erb +15 -0
  29. data/guides/code/getting_started/app/views/comments/_form.html.erb +13 -0
  30. data/guides/code/getting_started/app/views/home/index.html.erb +2 -0
  31. data/guides/code/getting_started/app/views/layouts/application.html.erb +14 -0
  32. data/guides/code/getting_started/app/views/posts/_form.html.erb +32 -0
  33. data/guides/code/getting_started/app/views/posts/edit.html.erb +6 -0
  34. data/guides/code/getting_started/app/views/posts/index.html.erb +27 -0
  35. data/guides/code/getting_started/app/views/posts/new.html.erb +5 -0
  36. data/guides/code/getting_started/app/views/posts/show.html.erb +31 -0
  37. data/guides/code/getting_started/app/views/tags/_form.html.erb +12 -0
  38. data/guides/code/getting_started/config.ru +4 -0
  39. data/guides/code/getting_started/config/application.rb +53 -0
  40. data/guides/code/getting_started/config/boot.rb +6 -0
  41. data/guides/code/getting_started/config/database.yml +25 -0
  42. data/guides/code/getting_started/config/environment.rb +5 -0
  43. data/guides/code/getting_started/config/environments/development.rb +37 -0
  44. data/guides/code/getting_started/config/environments/production.rb +67 -0
  45. data/guides/code/getting_started/config/environments/test.rb +37 -0
  46. data/guides/code/getting_started/config/initializers/backtrace_silencers.rb +7 -0
  47. data/guides/code/getting_started/config/initializers/inflections.rb +10 -0
  48. data/guides/code/getting_started/config/initializers/mime_types.rb +5 -0
  49. data/guides/code/getting_started/config/initializers/secret_token.rb +7 -0
  50. data/guides/code/getting_started/config/initializers/session_store.rb +8 -0
  51. data/guides/code/getting_started/config/initializers/wrap_parameters.rb +14 -0
  52. data/guides/code/getting_started/config/locales/en.yml +5 -0
  53. data/guides/code/getting_started/config/routes.rb +64 -0
  54. data/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb +11 -0
  55. data/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb +12 -0
  56. data/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb +11 -0
  57. data/guides/code/getting_started/db/schema.rb +43 -0
  58. data/guides/code/getting_started/db/seeds.rb +7 -0
  59. data/guides/code/getting_started/doc/README_FOR_APP +2 -0
  60. data/guides/code/getting_started/public/404.html +26 -0
  61. data/guides/code/getting_started/public/422.html +26 -0
  62. data/guides/code/getting_started/public/500.html +26 -0
  63. data/guides/code/getting_started/public/favicon.ico +0 -0
  64. data/guides/code/getting_started/public/robots.txt +5 -0
  65. data/guides/code/getting_started/script/rails +6 -0
  66. data/guides/code/getting_started/test/fixtures/comments.yml +11 -0
  67. data/guides/code/getting_started/test/fixtures/posts.yml +11 -0
  68. data/guides/code/getting_started/test/fixtures/tags.yml +9 -0
  69. data/guides/code/getting_started/test/functional/comments_controller_test.rb +7 -0
  70. data/guides/code/getting_started/test/functional/home_controller_test.rb +9 -0
  71. data/guides/code/getting_started/test/functional/posts_controller_test.rb +49 -0
  72. data/guides/code/getting_started/test/performance/browsing_test.rb +12 -0
  73. data/guides/code/getting_started/test/test_helper.rb +13 -0
  74. data/guides/code/getting_started/test/unit/comment_test.rb +7 -0
  75. data/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb +4 -0
  76. data/guides/code/getting_started/test/unit/helpers/home_helper_test.rb +4 -0
  77. data/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb +4 -0
  78. data/guides/code/getting_started/test/unit/post_test.rb +7 -0
  79. data/guides/code/getting_started/test/unit/tag_test.rb +7 -0
  80. data/guides/rails_guides/generator.rb +2 -1
  81. data/guides/source/3_0_release_notes.textile +2 -2
  82. data/guides/source/3_1_release_notes.textile +3 -110
  83. data/guides/source/action_controller_overview.textile +11 -13
  84. data/guides/source/action_mailer_basics.textile +7 -18
  85. data/guides/source/action_view_overview.textile +78 -9
  86. data/guides/source/active_model_basics.textile +205 -0
  87. data/guides/source/active_record_basics.textile +31 -31
  88. data/guides/source/active_record_querying.textile +288 -67
  89. data/guides/source/active_record_validations_callbacks.textile +69 -75
  90. data/guides/source/active_resource_basics.textile +48 -2
  91. data/guides/source/active_support_core_extensions.textile +145 -24
  92. data/guides/source/ajax_on_rails.textile +65 -7
  93. data/guides/source/api_documentation_guidelines.textile +0 -6
  94. data/guides/source/asset_pipeline.textile +2 -2
  95. data/guides/source/association_basics.textile +25 -34
  96. data/guides/source/caching_with_rails.textile +12 -17
  97. data/guides/source/command_line.textile +29 -19
  98. data/guides/source/configuring.textile +40 -18
  99. data/guides/source/contributing_to_ruby_on_rails.textile +11 -18
  100. data/guides/source/debugging_rails_applications.textile +10 -21
  101. data/guides/source/engines.textile +618 -0
  102. data/guides/source/form_helpers.textile +1 -12
  103. data/guides/source/generators.textile +9 -11
  104. data/guides/source/getting_started.textile +152 -152
  105. data/guides/source/i18n.textile +4 -5
  106. data/guides/source/index.html.erb +0 -1
  107. data/guides/source/initialization.textile +26 -26
  108. data/guides/source/layouts_and_rendering.textile +97 -61
  109. data/guides/source/migrations.textile +380 -161
  110. data/guides/source/performance_testing.textile +4 -10
  111. data/guides/source/plugins.textile +11 -19
  112. data/guides/source/rails_application_templates.textile +12 -4
  113. data/guides/source/rails_on_rack.textile +25 -19
  114. data/guides/source/routing.textile +6 -13
  115. data/guides/source/ruby_on_rails_guides_guidelines.textile +0 -5
  116. data/guides/source/security.textile +11 -15
  117. data/guides/source/testing.textile +1 -9
  118. data/lib/rails/application.rb +107 -42
  119. data/lib/rails/application/bootstrap.rb +12 -11
  120. data/lib/rails/application/configuration.rb +27 -21
  121. data/lib/rails/application/finisher.rb +40 -17
  122. data/lib/rails/application/route_inspector.rb +75 -0
  123. data/lib/rails/application/routes_reloader.rb +15 -4
  124. data/lib/rails/code_statistics.rb +16 -5
  125. data/lib/rails/commands.rb +6 -5
  126. data/lib/rails/commands/application.rb +8 -1
  127. data/lib/rails/commands/console.rb +2 -0
  128. data/lib/rails/commands/dbconsole.rb +2 -2
  129. data/lib/rails/commands/destroy.rb +0 -2
  130. data/lib/rails/commands/generate.rb +3 -3
  131. data/lib/rails/commands/plugin.rb +161 -159
  132. data/lib/rails/commands/plugin_new.rb +3 -2
  133. data/lib/rails/commands/runner.rb +4 -0
  134. data/lib/rails/console/app.rb +26 -22
  135. data/lib/rails/console/helpers.rb +9 -5
  136. data/lib/rails/engine.rb +70 -34
  137. data/lib/rails/engine/commands.rb +39 -0
  138. data/lib/rails/engine/configuration.rb +1 -1
  139. data/lib/rails/generators.rb +3 -14
  140. data/lib/rails/generators/actions.rb +36 -9
  141. data/lib/rails/generators/app_base.rb +34 -38
  142. data/lib/rails/generators/base.rb +4 -4
  143. data/lib/rails/generators/generated_attribute.rb +1 -1
  144. data/lib/rails/generators/named_base.rb +1 -3
  145. data/lib/rails/generators/rails/app/USAGE +6 -0
  146. data/lib/rails/generators/rails/app/app_generator.rb +6 -2
  147. data/lib/rails/generators/rails/app/templates/Gemfile +4 -3
  148. data/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +9 -3
  149. data/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +11 -5
  150. data/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory +0 -0
  151. data/lib/rails/generators/rails/app/templates/app/models/.empty_directory +0 -0
  152. data/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +1 -1
  153. data/lib/rails/generators/rails/app/templates/config/application.rb +11 -0
  154. data/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +1 -1
  155. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +1 -1
  156. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +10 -1
  157. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +10 -1
  158. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +6 -6
  159. data/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb +5 -0
  160. data/lib/rails/generators/rails/app/templates/config/routes.rb +1 -1
  161. data/lib/rails/generators/rails/app/templates/public/500.html +0 -1
  162. data/lib/rails/generators/rails/app/templates/public/index.html +1 -1
  163. data/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory +0 -0
  164. data/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory +0 -0
  165. data/lib/rails/generators/rails/app/templates/test/functional/.empty_directory +0 -0
  166. data/lib/rails/generators/rails/app/templates/test/integration/.empty_directory +0 -0
  167. data/lib/rails/generators/rails/app/templates/test/unit/.empty_directory +0 -0
  168. data/lib/rails/generators/rails/controller/templates/controller.rb +1 -1
  169. data/lib/rails/generators/rails/generator/templates/templates/.empty_directory +0 -0
  170. data/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +17 -5
  171. data/lib/rails/generators/rails/plugin_new/templates/Rakefile +1 -0
  172. data/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory +0 -0
  173. data/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory +0 -0
  174. data/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt +1 -1
  175. data/lib/rails/generators/rails/plugin_new/templates/gitignore +4 -3
  176. data/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb +1 -1
  177. data/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +1 -1
  178. data/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +5 -3
  179. data/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +2 -2
  180. data/lib/rails/generators/rails/task/USAGE +9 -0
  181. data/lib/rails/generators/rails/task/task_generator.rb +12 -0
  182. data/lib/rails/generators/rails/task/templates/task.rb +8 -0
  183. data/lib/rails/generators/resource_helpers.rb +3 -3
  184. data/lib/rails/generators/test_unit/integration/templates/integration_test.rb +0 -2
  185. data/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +4 -4
  186. data/lib/rails/paths.rb +11 -38
  187. data/lib/rails/rack/debugger.rb +3 -4
  188. data/lib/rails/rack/logger.rb +26 -12
  189. data/lib/rails/railtie.rb +6 -1
  190. data/lib/rails/railtie/configuration.rb +12 -5
  191. data/lib/rails/source_annotation_extractor.rb +12 -10
  192. data/lib/rails/tasks/documentation.rake +3 -1
  193. data/lib/rails/tasks/engine.rake +1 -0
  194. data/lib/rails/tasks/misc.rake +1 -1
  195. data/lib/rails/tasks/routes.rake +3 -23
  196. data/lib/rails/test_help.rb +1 -2
  197. data/lib/rails/test_unit/testing.rake +8 -4
  198. data/lib/rails/version.rb +3 -3
  199. metadata +131 -61
  200. checksums.yaml +0 -7
  201. data/lib/rails/generators/rails/plugin/USAGE +0 -13
  202. data/lib/rails/generators/rails/plugin/plugin_generator.rb +0 -54
  203. data/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt +0 -20
  204. data/lib/rails/generators/rails/plugin/templates/README.tt +0 -13
  205. data/lib/rails/generators/rails/plugin/templates/Rakefile.tt +0 -23
  206. data/lib/rails/generators/rails/plugin/templates/init.rb +0 -1
  207. data/lib/rails/generators/rails/plugin/templates/install.rb +0 -1
  208. data/lib/rails/generators/rails/plugin/templates/lib/%file_name%.rb.tt +0 -1
  209. data/lib/rails/generators/rails/plugin/templates/lib/tasks/%file_name%_tasks.rake.tt +0 -4
  210. data/lib/rails/generators/rails/plugin/templates/uninstall.rb +0 -1
@@ -8,12 +8,13 @@ This guide covers different ways to retrieve data from the database using Active
8
8
  * Use dynamic finders methods
9
9
  * Check for the existence of particular records
10
10
  * Perform various calculations on Active Record models
11
+ * Run EXPLAIN on relations
11
12
 
12
13
  endprologue.
13
14
 
14
15
  WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails.
15
16
 
16
- If you're used to using raw SQL to find database records then, generally, you will find that there are better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases.
17
+ If you're used to using raw SQL to find database records, then you will generally find that there are better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases.
17
18
 
18
19
  Code examples throughout this guide will refer to one or more of the following models:
19
20
 
@@ -69,28 +70,28 @@ The methods are:
69
70
 
70
71
  All of the above methods return an instance of <tt>ActiveRecord::Relation</tt>.
71
72
 
72
- Primary operation of <tt>Model.find(options)</tt> can be summarized as:
73
+ The primary operation of <tt>Model.find(options)</tt> can be summarized as:
73
74
 
74
75
  * Convert the supplied options to an equivalent SQL query.
75
76
  * Fire the SQL query and retrieve the corresponding results from the database.
76
77
  * Instantiate the equivalent Ruby object of the appropriate model for every resulting row.
77
- * Run +after_find+ callbacks if any.
78
+ * Run +after_find+ callbacks, if any.
78
79
 
79
80
  h4. Retrieving a Single Object
80
81
 
81
- Active Record lets you retrieve a single object using five different ways.
82
+ Active Record provides five different ways of retrieving a single object.
82
83
 
83
84
  h5. Using a Primary Key
84
85
 
85
- Using <tt>Model.find(primary_key)</tt>, you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example:
86
+ Using <tt>Model.find(primary_key)</tt>, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example:
86
87
 
87
88
  <ruby>
88
89
  # Find the client with primary key (id) 10.
89
90
  client = Client.find(10)
90
- => #<Client id: 10, first_name: => "Ryan">
91
+ # => #<Client id: 10, first_name: "Ryan">
91
92
  </ruby>
92
93
 
93
- SQL equivalent of the above is:
94
+ The SQL equivalent of the above is:
94
95
 
95
96
  <sql>
96
97
  SELECT * FROM clients WHERE (clients.id = 10)
@@ -100,14 +101,14 @@ SELECT * FROM clients WHERE (clients.id = 10)
100
101
 
101
102
  h5. +first+
102
103
 
103
- <tt>Model.first</tt> finds the first record matched by the supplied options. For example:
104
+ <tt>Model.first</tt> finds the first record matched by the supplied options, if any. For example:
104
105
 
105
106
  <ruby>
106
107
  client = Client.first
107
- => #<Client id: 1, first_name: "Lifo">
108
+ # => #<Client id: 1, first_name: "Lifo">
108
109
  </ruby>
109
110
 
110
- SQL equivalent of the above is:
111
+ The SQL equivalent of the above is:
111
112
 
112
113
  <sql>
113
114
  SELECT * FROM clients LIMIT 1
@@ -121,10 +122,10 @@ h5. +last+
121
122
 
122
123
  <ruby>
123
124
  client = Client.last
124
- => #<Client id: 221, first_name: "Russel">
125
+ # => #<Client id: 221, first_name: "Russel">
125
126
  </ruby>
126
127
 
127
- SQL equivalent of the above is:
128
+ The SQL equivalent of the above is:
128
129
 
129
130
  <sql>
130
131
  SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
@@ -132,16 +133,16 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
132
133
 
133
134
  <tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
134
135
 
135
- h5. +first!+
136
+ h5(#first_1). +first!+
136
137
 
137
138
  <tt>Model.first!</tt> finds the first record. For example:
138
139
 
139
140
  <ruby>
140
141
  client = Client.first!
141
- => #<Client id: 1, first_name: "Lifo">
142
+ # => #<Client id: 1, first_name: "Lifo">
142
143
  </ruby>
143
144
 
144
- SQL equivalent of the above is:
145
+ The SQL equivalent of the above is:
145
146
 
146
147
  <sql>
147
148
  SELECT * FROM clients LIMIT 1
@@ -149,16 +150,16 @@ SELECT * FROM clients LIMIT 1
149
150
 
150
151
  <tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
151
152
 
152
- h5. +last!+
153
+ h5(#last_1). +last!+
153
154
 
154
155
  <tt>Model.last!</tt> finds the last record. For example:
155
156
 
156
157
  <ruby>
157
158
  client = Client.last!
158
- => #<Client id: 221, first_name: "Russel">
159
+ # => #<Client id: 221, first_name: "Russel">
159
160
  </ruby>
160
161
 
161
- SQL equivalent of the above is:
162
+ The SQL equivalent of the above is:
162
163
 
163
164
  <sql>
164
165
  SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
@@ -170,15 +171,15 @@ h4. Retrieving Multiple Objects
170
171
 
171
172
  h5. Using Multiple Primary Keys
172
173
 
173
- <tt>Model.find(array_of_primary_key)</tt> also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example:
174
+ <tt>Model.find(array_of_primary_key)</tt> accepts an array of _primary keys_, returning an array containing all of the matching records for the supplied _primary keys_. For example:
174
175
 
175
176
  <ruby>
176
177
  # Find the clients with primary keys 1 and 10.
177
- client = Client.find(1, 10) # Or even Client.find([1, 10])
178
- => [#<Client id: 1, first_name: => "Lifo">, #<Client id: 10, first_name: => "Ryan">]
178
+ client = Client.find([1, 10]) # Or even Client.find(1, 10)
179
+ # => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
179
180
  </ruby>
180
181
 
181
- SQL equivalent of the above is:
182
+ The SQL equivalent of the above is:
182
183
 
183
184
  <sql>
184
185
  SELECT * FROM clients WHERE (clients.id IN (1,10))
@@ -188,24 +189,26 @@ WARNING: <tt>Model.find(array_of_primary_key)</tt> will raise an +ActiveRecord::
188
189
 
189
190
  h4. Retrieving Multiple Objects in Batches
190
191
 
191
- Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc.
192
+ We often need to iterate over a large set of records, as when we send a newsletter to a large set of users, or when we export data.
192
193
 
193
- The following may seem very straight forward at first:
194
+ This may appear straightforward:
194
195
 
195
196
  <ruby>
196
- # Very inefficient when users table has thousands of rows.
197
+ # This is very inefficient when the users table has thousands of rows.
197
198
  User.all.each do |user|
198
199
  NewsLetter.weekly_deliver(user)
199
200
  end
200
201
  </ruby>
201
202
 
202
- But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible.
203
+ But this approach becomes increasingly impractical as the table size increases, since +User.all.each+ instructs Active Record to fetch _the entire table_ in a single pass, build a model object per row, and then keep the entire array of model objects in memory. Indeed, if we have a large number of records, the entire collection may exceed the amount of memory available.
203
204
 
204
- This is because +User.all.each+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory.
205
+ Rails provides two methods that address this problem by dividing records into memory-friendly batches for processing. The first method, +find_each+, retrieves a batch of records and then yields _each_ record to the block individually as a model. The second method, +find_in_batches+, retrieves a batch of records and then yields _the entire batch_ to the block as an array of models.
206
+
207
+ TIP: The +find_each+ and +find_in_batches+ methods are intended for use in the batch processing of a large number of records that wouldn't fit in memory all at once. If you just need to loop over a thousand records the regular find methods are the preferred option.
205
208
 
206
209
  h5. +find_each+
207
210
 
208
- To efficiently iterate over a large table, Active Record provides a batch finder method called +find_each+:
211
+ The +find_each+ method retrieves a batch of records and then yields _each_ record to the block individually as a model. In the following example, +find_each+ will retrieve 1000 records (the current default for both +find_each+ and +find_in_batches+) and then yield each record individually to the block as a model. This process is repeated until all of the records have been processed:
209
212
 
210
213
  <ruby>
211
214
  User.find_each do |user|
@@ -213,11 +216,15 @@ User.find_each do |user|
213
216
  end
214
217
  </ruby>
215
218
 
216
- *Configuring the batch size*
219
+ h6. Options for +find_each+
220
+
221
+ 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+.
217
222
 
218
- Behind the scenes +find_each+ fetches rows in batches of +1000+ and yields them one by one. The size of the underlying batches is configurable via the +:batch_size+ option.
223
+ Two additional options, +:batch_size+ and +:start+, are available as well.
219
224
 
220
- To fetch +User+ records in batch size of +5000+:
225
+ *+:batch_size+*
226
+
227
+ 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:
221
228
 
222
229
  <ruby>
223
230
  User.find_each(:batch_size => 5000) do |user|
@@ -225,34 +232,38 @@ User.find_each(:batch_size => 5000) do |user|
225
232
  end
226
233
  </ruby>
227
234
 
228
- *Starting batch find from a specific primary key*
235
+ *+:start+*
229
236
 
230
- Records are fetched in ascending order on the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence if the lowest is not the one you need. This may be useful for example to be able to resume an interrupted batch process if it saves the last processed ID as a checkpoint.
237
+ By default, records are fetched in ascending order of the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
231
238
 
232
- To send newsletters only to users with the primary key starting from +2000+:
239
+ For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000:
233
240
 
234
241
  <ruby>
235
- User.find_each(:batch_size => 5000, :start => 2000) do |user|
242
+ User.find_each(:start => 2000, :batch_size => 5000) do |user|
236
243
  NewsLetter.weekly_deliver(user)
237
244
  end
238
245
  </ruby>
239
246
 
240
- *Additional options*
247
+ 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 <tt>:start</tt> option on each worker.
241
248
 
242
- +find_each+ accepts the same options as the regular +find+ method. However, +:order+ and +:limit+ are needed internally and hence not allowed to be passed explicitly.
249
+ NOTE: The +:include+ option allows you to name associations that should be loaded alongside with the models.
243
250
 
244
251
  h5. +find_in_batches+
245
252
 
246
- You can also work by chunks instead of row by row using +find_in_batches+. This method is analogous to +find_each+, but it yields arrays of models instead:
253
+ 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:
247
254
 
248
255
  <ruby>
249
- # Works in chunks of 1000 invoices at a time.
256
+ # Give add_invoices an array of 1000 invoices at a time
250
257
  Invoice.find_in_batches(:include => :invoice_lines) do |invoices|
251
258
  export.add_invoices(invoices)
252
259
  end
253
260
  </ruby>
254
261
 
255
- The above will yield the supplied block with +1000+ invoices every time.
262
+ NOTE: The +:include+ option allows you to name associations that should be loaded alongside with the models.
263
+
264
+ h6. Options for +find_in_batches+
265
+
266
+ 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+.
256
267
 
257
268
  h3. Conditions
258
269
 
@@ -266,7 +277,7 @@ WARNING: Building your own conditions as pure strings can leave you vulnerable t
266
277
 
267
278
  h4. Array Conditions
268
279
 
269
- Now what if that number could vary, say as an argument from somewhere? The find then becomes something like:
280
+ Now what if that number could vary, say as an argument from somewhere? The find would then take the form:
270
281
 
271
282
  <ruby>
272
283
  Client.where("orders_count = ?", params[:orders])
@@ -274,7 +285,7 @@ Client.where("orders_count = ?", params[:orders])
274
285
 
275
286
  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.
276
287
 
277
- Or if you want to specify two conditions, you can do it like:
288
+ If you want to specify multiple conditions:
278
289
 
279
290
  <ruby>
280
291
  Client.where("orders_count = ? AND locked = ?", params[:orders], false)
@@ -282,19 +293,19 @@ Client.where("orders_count = ? AND locked = ?", params[:orders], false)
282
293
 
283
294
  In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter.
284
295
 
285
- The reason for doing code like:
296
+ This code is highly preferable:
286
297
 
287
298
  <ruby>
288
299
  Client.where("orders_count = ?", params[:orders])
289
300
  </ruby>
290
301
 
291
- instead of:
302
+ to this code:
292
303
 
293
304
  <ruby>
294
305
  Client.where("orders_count = #{params[:orders]}")
295
306
  </ruby>
296
307
 
297
- is 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.
308
+ 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.
298
309
 
299
310
  TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection.
300
311
 
@@ -425,10 +436,26 @@ ActiveModel::MissingAttributeError: missing attribute: <attribute>
425
436
 
426
437
  Where +&lt;attribute&gt;+ 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.
427
438
 
428
- You can also call SQL functions within the select option. For example, if you would like to only grab a single record per unique value in a certain field by using the +DISTINCT+ function you can do it like this:
439
+ If you would like to only grab a single record per unique value in a certain field, you can use +uniq+:
429
440
 
430
441
  <ruby>
431
- Client.select("DISTINCT(name)")
442
+ Client.select(:name).uniq
443
+ </ruby>
444
+
445
+ This would generate SQL like:
446
+
447
+ <sql>
448
+ SELECT DISTINCT name FROM clients
449
+ </sql>
450
+
451
+ You can also remove the uniqueness constraint:
452
+
453
+ <ruby>
454
+ query = Client.select(:name).uniq
455
+ # => Returns unique names
456
+
457
+ query.uniq(false)
458
+ # => Returns all names, even if there are duplicates
432
459
  </ruby>
433
460
 
434
461
  h3. Limit and Offset
@@ -616,7 +643,7 @@ c1.first_name = "Michael"
616
643
  c1.save
617
644
 
618
645
  c2.name = "should fail"
619
- c2.save # Raises a ActiveRecord::StaleObjectError
646
+ c2.save # Raises an ActiveRecord::StaleObjectError
620
647
  </ruby>
621
648
 
622
649
  You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, or otherwise apply the business logic needed to resolve the conflict.
@@ -731,7 +758,7 @@ SELECT categories.* FROM categories
731
758
  INNER JOIN posts ON posts.category_id = categories.id
732
759
  </sql>
733
760
 
734
- 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(:post).select("distinct(categories.id)").
761
+ 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(:post).select("distinct(categories.id)").
735
762
 
736
763
  h5. Joining Multiple Associations
737
764
 
@@ -911,14 +938,14 @@ end
911
938
  To call this +published+ scope we can call it on either the class:
912
939
 
913
940
  <ruby>
914
- Post.published => [published posts]
941
+ Post.published # => [published posts]
915
942
  </ruby>
916
943
 
917
944
  Or on an association consisting of +Post+ objects:
918
945
 
919
946
  <ruby>
920
947
  category = Category.first
921
- category.posts.published => [published posts belonging to this category]
948
+ category.posts.published # => [published posts belonging to this category]
922
949
  </ruby>
923
950
 
924
951
  h4. Working with times
@@ -939,7 +966,7 @@ When a +lambda+ is used for a +scope+, it can take arguments:
939
966
 
940
967
  <ruby>
941
968
  class Post < ActiveRecord::Base
942
- scope :1_week_before, lambda { |time| where("created_at < ?", time) }
969
+ scope :1_week_before, lambda { |time| where("created_at < ?", time)
943
970
  end
944
971
  </ruby>
945
972
 
@@ -1014,27 +1041,90 @@ You can also use +find_last_by_*+ methods which will find the last record matchi
1014
1041
 
1015
1042
  You can specify an exclamation point (<tt>!</tt>) 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")+
1016
1043
 
1017
- 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)+.
1044
+ 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)+.
1018
1045
 
1019
1046
  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 <tt>Client.find_by_name_and_locked("Ryan")</tt>, 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+.
1020
1047
 
1021
- There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
1048
+ h3. Find or build a new object
1049
+
1050
+ 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.
1051
+
1052
+ h4. +first_or_create+
1053
+
1054
+ 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.
1055
+
1056
+ 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:
1057
+
1058
+ <ruby>
1059
+ Client.where(:first_name => 'Andy').first_or_create(:locked => false)
1060
+ # => #<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">
1061
+ </ruby>
1062
+
1063
+ The SQL generated by this method looks like this:
1022
1064
 
1023
1065
  <sql>
1024
- SELECT * FROM clients WHERE (clients.first_name = 'Ryan') LIMIT 1
1066
+ SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
1025
1067
  BEGIN
1026
- INSERT INTO clients (first_name, updated_at, created_at, orders_count, locked)
1027
- VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', 0, '0')
1068
+ 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')
1028
1069
  COMMIT
1029
1070
  </sql>
1030
1071
 
1031
- +find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similarly to calling +new+ with the arguments you passed in. For example:
1072
+ +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.
1073
+
1074
+ The new record might not be saved to the database; that depends on whether validations passed or not (just like +create+).
1075
+
1076
+ 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.
1077
+
1078
+ You can do the same with the +find_or_create_by+ method:
1032
1079
 
1033
1080
  <ruby>
1034
- client = Client.find_or_initialize_by_first_name('Ryan')
1081
+ Client.find_or_create_by_first_name(:first_name => "Andy", :locked => false)
1035
1082
  </ruby>
1036
1083
 
1037
- will either assign an existing client object with the name "Ryan" to the client local variable, or initialize a new object similar to calling +Client.new(:first_name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
1084
+ 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.
1085
+
1086
+ h4. +first_or_create!+
1087
+
1088
+ 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
1089
+
1090
+ <ruby>
1091
+ validates :orders_count, :presence => true
1092
+ </ruby>
1093
+
1094
+ 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:
1095
+
1096
+ <ruby>
1097
+ Client.where(:first_name => 'Andy').first_or_create!(:locked => false)
1098
+ # => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
1099
+ </ruby>
1100
+
1101
+ h4. +first_or_initialize+
1102
+
1103
+ 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':
1104
+
1105
+ <ruby>
1106
+ nick = Client.where(:first_name => 'Nick').first_or_initialize(:locked => false)
1107
+ # => <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">
1108
+
1109
+ nick.persisted?
1110
+ # => false
1111
+
1112
+ nick.new_record?
1113
+ # => true
1114
+ </ruby>
1115
+
1116
+ Because the object is not yet stored in the database, the SQL generated looks like this:
1117
+
1118
+ <sql>
1119
+ SELECT * FROM clients WHERE (clients.first_name = 'Nick') LIMIT 1
1120
+ </sql>
1121
+
1122
+ When you want to save it to the database, just call +save+:
1123
+
1124
+ <ruby>
1125
+ nick.save
1126
+ # => true
1127
+ </ruby>
1038
1128
 
1039
1129
  h3. Finding by SQL
1040
1130
 
@@ -1056,6 +1146,30 @@ h3. +select_all+
1056
1146
  Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
1057
1147
  </ruby>
1058
1148
 
1149
+ h3. +pluck+
1150
+
1151
+ <tt>pluck</tt> can be used to query a single column from the underlying table of a model. It accepts a column name as argument and returns an array of values of the specified column with the corresponding data type.
1152
+
1153
+ <ruby>
1154
+ Client.where(:active => true).pluck(:id)
1155
+ # SELECT id FROM clients WHERE active = 1
1156
+
1157
+ Client.uniq.pluck(:role)
1158
+ # SELECT DISTINCT role FROM clients
1159
+ </ruby>
1160
+
1161
+ +pluck+ makes it possible to replace code like
1162
+
1163
+ <ruby>
1164
+ Client.select(:id).map { |c| c.id }
1165
+ </ruby>
1166
+
1167
+ with
1168
+
1169
+ <ruby>
1170
+ Client.pluck(:id)
1171
+ </ruby>
1172
+
1059
1173
  h3. Existence of Objects
1060
1174
 
1061
1175
  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+.
@@ -1186,11 +1300,118 @@ Client.sum("orders_count")
1186
1300
 
1187
1301
  For options, please see the parent section, "Calculations":#calculations.
1188
1302
 
1189
- h3. Changelog
1303
+ h3. Running EXPLAIN
1304
+
1305
+ You can run EXPLAIN on the queries triggered by relations. For example,
1306
+
1307
+ <ruby>
1308
+ User.where(:id => 1).joins(:posts).explain
1309
+ </ruby>
1310
+
1311
+ may yield
1312
+
1313
+ <plain>
1314
+ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
1315
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
1316
+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1317
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
1318
+ | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
1319
+ | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
1320
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
1321
+ 2 rows in set (0.00 sec)
1322
+ </plain>
1323
+
1324
+ under MySQL.
1325
+
1326
+ Active Record performs a pretty printing that emulates the one of the database
1327
+ shells. So, the same query running with the PostgreSQL adapter would yield instead
1328
+
1329
+ <plain>
1330
+ EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
1331
+ QUERY PLAN
1332
+ ------------------------------------------------------------------------------
1333
+ Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
1334
+ Join Filter: (posts.user_id = users.id)
1335
+ -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
1336
+ Index Cond: (id = 1)
1337
+ -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
1338
+ Filter: (posts.user_id = 1)
1339
+ (6 rows)
1340
+ </plain>
1341
+
1342
+ Eager loading may trigger more than one query under the hood, and some queries
1343
+ may need the results of previous ones. Because of that, +explain+ actually
1344
+ executes the query, and then asks for the query plans. For example,
1345
+
1346
+ <ruby>
1347
+ User.where(:id => 1).includes(:posts).explain
1348
+ </ruby>
1349
+
1350
+ yields
1351
+
1352
+ <plain>
1353
+ EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
1354
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
1355
+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1356
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
1357
+ | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
1358
+ <plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
1359
+ 1 row in set (0.00 sec)
1360
+
1361
+ EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
1362
+ <plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
1363
+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1364
+ <plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
1365
+ | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
1366
+ <plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
1367
+ 1 row in set (0.00 sec)
1368
+ </plain>
1369
+
1370
+ under MySQL.
1371
+
1372
+ h4. Automatic EXPLAIN
1373
+
1374
+ Active Record is able to run EXPLAIN automatically on slow queries and log its
1375
+ output. This feature is controlled by the configuration parameter
1376
+
1377
+ <ruby>
1378
+ config.active_record.auto_explain_threshold_in_seconds
1379
+ </ruby>
1380
+
1381
+ If set to a number, any query exceeding those many seconds will have its EXPLAIN
1382
+ automatically triggered and logged. In the case of relations, the threshold is
1383
+ compared to the total time needed to fetch records. So, a relation is seen as a
1384
+ unit of work, no matter whether the implementation of eager loading involves
1385
+ several queries under the hood.
1386
+
1387
+ A threshold of +nil+ disables automatic EXPLAINs.
1388
+
1389
+ The default threshold in development mode is 0.5 seconds, and +nil+ in test and
1390
+ production modes.
1391
+
1392
+ h5. Disabling Automatic EXPLAIN
1393
+
1394
+ Automatic EXPLAIN can be selectively silenced with +ActiveRecord::Base.silence_auto_explain+:
1395
+
1396
+ <ruby>
1397
+ ActiveRecord::Base.silence_auto_explain do
1398
+ # no automatic EXPLAIN is triggered here
1399
+ end
1400
+ </ruby>
1401
+
1402
+ That may be useful for queries you know are slow but fine, like a heavyweight
1403
+ report of an admin interface.
1404
+
1405
+ As its name suggests, +silence_auto_explain+ only silences automatic EXPLAINs.
1406
+ Explicit calls to +ActiveRecord::Relation#explain+ run.
1407
+
1408
+ h4. Interpreting EXPLAIN
1409
+
1410
+ Interpretation of the output of EXPLAIN is beyond the scope of this guide. The
1411
+ following pointers may be helpful:
1412
+
1413
+ * SQLite3: "EXPLAIN QUERY PLAN":http://www.sqlite.org/eqp.html
1414
+
1415
+ * MySQL: "EXPLAIN Output Format":http://dev.mysql.com/doc/refman/5.6/en/explain-output.html
1190
1416
 
1191
- * June 26 2011: Added documentation for the +scoped+, +unscoped+ and +default+ methods. "Ryan Bigg":credits.html#radar
1192
- * December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":credits.html#radar
1193
- * April 7, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
1194
- * February 3, 2010: Update to Rails 3 by "James Miller":credits.html#bensie
1195
- * February 7, 2009: Second version by "Pratik":credits.html#lifo
1196
- * December 29 2008: Initial version by "Ryan Bigg":credits.html#radar
1417
+ * PostgreSQL: "Using EXPLAIN":http://www.postgresql.org/docs/current/static/using-explain.html