activerecord 4.2.11.3 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (372) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +613 -1643
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record.rb +41 -22
  8. data/lib/active_record/aggregations.rb +267 -251
  9. data/lib/active_record/association_relation.rb +11 -6
  10. data/lib/active_record/associations.rb +1737 -1597
  11. data/lib/active_record/associations/alias_tracker.rb +29 -35
  12. data/lib/active_record/associations/association.rb +125 -58
  13. data/lib/active_record/associations/association_scope.rb +103 -132
  14. data/lib/active_record/associations/belongs_to_association.rb +65 -60
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -40
  17. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  18. data/lib/active_record/associations/builder/collection_association.rb +10 -33
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
  20. data/lib/active_record/associations/builder/has_many.rb +8 -4
  21. data/lib/active_record/associations/builder/has_one.rb +46 -5
  22. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  23. data/lib/active_record/associations/collection_association.rb +131 -287
  24. data/lib/active_record/associations/collection_proxy.rb +241 -146
  25. data/lib/active_record/associations/foreign_association.rb +10 -1
  26. data/lib/active_record/associations/has_many_association.rb +34 -97
  27. data/lib/active_record/associations/has_many_through_association.rb +60 -87
  28. data/lib/active_record/associations/has_one_association.rb +61 -49
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +137 -167
  31. data/lib/active_record/associations/join_dependency/join_association.rb +38 -86
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +90 -92
  35. data/lib/active_record/associations/preloader/association.rb +90 -123
  36. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  37. data/lib/active_record/associations/singular_association.rb +18 -39
  38. data/lib/active_record/associations/through_association.rb +38 -18
  39. data/lib/active_record/attribute_assignment.rb +56 -183
  40. data/lib/active_record/attribute_decorators.rb +39 -15
  41. data/lib/active_record/attribute_methods.rb +120 -135
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
  43. data/lib/active_record/attribute_methods/dirty.rb +174 -144
  44. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  45. data/lib/active_record/attribute_methods/query.rb +6 -5
  46. data/lib/active_record/attribute_methods/read.rb +20 -76
  47. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  49. data/lib/active_record/attribute_methods/write.rb +32 -54
  50. data/lib/active_record/attributes.rb +214 -82
  51. data/lib/active_record/autosave_association.rb +91 -37
  52. data/lib/active_record/base.rb +57 -45
  53. data/lib/active_record/callbacks.rb +100 -74
  54. data/lib/active_record/coders/json.rb +3 -1
  55. data/lib/active_record/coders/yaml_column.rb +24 -12
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -296
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -115
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -243
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +460 -204
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +510 -635
  69. data/lib/active_record/connection_adapters/column.rb +56 -43
  70. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  100. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +551 -356
  117. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -345
  127. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  128. data/lib/active_record/connection_handling.rb +176 -41
  129. data/lib/active_record/core.rb +251 -231
  130. data/lib/active_record/counter_cache.rb +67 -49
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +87 -105
  137. data/lib/active_record/enum.rb +163 -86
  138. data/lib/active_record/errors.rb +188 -53
  139. data/lib/active_record/explain.rb +23 -11
  140. data/lib/active_record/explain_registry.rb +4 -2
  141. data/lib/active_record/explain_subscriber.rb +10 -5
  142. data/lib/active_record/fixture_set/file.rb +35 -9
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +228 -499
  148. data/lib/active_record/gem_version.rb +6 -4
  149. data/lib/active_record/inheritance.rb +158 -112
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +123 -29
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  154. data/lib/active_record/locale/en.yml +3 -2
  155. data/lib/active_record/locking/optimistic.rb +87 -96
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +76 -33
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +621 -303
  162. data/lib/active_record/migration/command_recorder.rb +177 -90
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +8 -6
  165. data/lib/active_record/model_schema.rb +312 -112
  166. data/lib/active_record/nested_attributes.rb +264 -222
  167. data/lib/active_record/no_touching.rb +14 -1
  168. data/lib/active_record/null_relation.rb +24 -37
  169. data/lib/active_record/persistence.rb +557 -125
  170. data/lib/active_record/query_cache.rb +19 -23
  171. data/lib/active_record/querying.rb +43 -29
  172. data/lib/active_record/railtie.rb +143 -44
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +2 -0
  175. data/lib/active_record/railties/controller_runtime.rb +34 -33
  176. data/lib/active_record/railties/databases.rake +328 -185
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +428 -279
  179. data/lib/active_record/relation.rb +518 -341
  180. data/lib/active_record/relation/batches.rb +207 -55
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +267 -253
  183. data/lib/active_record/relation/delegation.rb +70 -80
  184. data/lib/active_record/relation/finder_methods.rb +277 -241
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +78 -87
  187. data/lib/active_record/relation/predicate_builder.rb +114 -119
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +575 -394
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +11 -13
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +79 -42
  202. data/lib/active_record/runtime_registry.rb +6 -4
  203. data/lib/active_record/sanitization.rb +144 -121
  204. data/lib/active_record/schema.rb +21 -24
  205. data/lib/active_record/schema_dumper.rb +112 -93
  206. data/lib/active_record/schema_migration.rb +24 -17
  207. data/lib/active_record/scoping.rb +45 -26
  208. data/lib/active_record/scoping/default.rb +101 -85
  209. data/lib/active_record/scoping/named.rb +86 -33
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +5 -5
  212. data/lib/active_record/statement_cache.rb +73 -36
  213. data/lib/active_record/store.rb +127 -42
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +307 -100
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +86 -40
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +216 -150
  225. data/lib/active_record/translation.rb +3 -1
  226. data/lib/active_record/type.rb +78 -23
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +4 -45
  229. data/lib/active_record/type/date_time.rb +4 -49
  230. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  231. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +24 -15
  235. data/lib/active_record/type/text.rb +2 -2
  236. data/lib/active_record/type/time.rb +11 -16
  237. data/lib/active_record/type/type_map.rb +15 -17
  238. data/lib/active_record/type/unsigned_integer.rb +9 -7
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +39 -35
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +13 -4
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +14 -13
  247. data/lib/active_record/validations/uniqueness.rb +42 -55
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +7 -5
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +31 -1
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
  337. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  340. metadata +164 -59
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_set.rb +0 -81
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  352. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  353. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  354. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  355. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  356. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  359. data/lib/active_record/type/big_integer.rb +0 -13
  360. data/lib/active_record/type/binary.rb +0 -50
  361. data/lib/active_record/type/boolean.rb +0 -31
  362. data/lib/active_record/type/decimal.rb +0 -64
  363. data/lib/active_record/type/decorator.rb +0 -14
  364. data/lib/active_record/type/float.rb +0 -19
  365. data/lib/active_record/type/integer.rb +0 -59
  366. data/lib/active_record/type/mutable.rb +0 -16
  367. data/lib/active_record/type/numeric.rb +0 -36
  368. data/lib/active_record/type/string.rb +0 -40
  369. data/lib/active_record/type/time_value.rb +0 -38
  370. data/lib/active_record/type/value.rb +0 -110
  371. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  372. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,8 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/batches/batch_enumerator"
4
+
1
5
  module ActiveRecord
2
6
  module Batches
7
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
8
+
3
9
  # Looping through a collection of records from the database
4
- # (using the +all+ method, for example) is very inefficient
5
- # since it will try to instantiate all the objects at once.
10
+ # (using the Scoping::Named::ClassMethods.all method, for example)
11
+ # is very inefficient since it will try to instantiate all the objects at once.
6
12
  #
7
13
  # In that case, batch processing methods allow you to work
8
14
  # with the records in batches, thereby greatly reducing memory consumption.
@@ -26,38 +32,52 @@ module ActiveRecord
26
32
  # end
27
33
  #
28
34
  # ==== Options
29
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
30
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
31
- # This is especially useful if you want multiple workers dealing with
32
- # the same processing queue. You can make worker 1 handle all the records
33
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
34
- # (by setting the +:start+ option on that worker).
35
- #
36
- # # Let's process for a batch of 2000 records, skipping the first 2000 rows
37
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
35
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
36
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
37
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
38
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
39
+ # an order is present in the relation.
40
+ #
41
+ # Limits are honored, and if present there is no requirement for the batch
42
+ # size: it can be less than, equal to, or greater than the limit.
43
+ #
44
+ # The options +start+ and +finish+ are especially useful if you want
45
+ # multiple workers dealing with the same processing queue. You can make
46
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
47
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
48
+ # option on each worker.
49
+ #
50
+ # # In worker 1, let's process until 9999 records.
51
+ # Person.find_each(finish: 9_999) do |person|
52
+ # person.party_all_night!
53
+ # end
54
+ #
55
+ # # In worker 2, let's process from record 10_000 and onwards.
56
+ # Person.find_each(start: 10_000) do |person|
38
57
  # person.party_all_night!
39
58
  # end
40
59
  #
41
60
  # NOTE: It's not possible to set the order. That is automatically set to
42
61
  # ascending on the primary key ("id ASC") to make the batch ordering
43
- # work. This also means that this method only works with integer-based
44
- # primary keys.
62
+ # work. This also means that this method only works when the primary key is
63
+ # orderable (e.g. an integer or string).
45
64
  #
46
- # NOTE: You can't set the limit either, that's used to control
47
- # the batch sizes.
48
- def find_each(options = {})
65
+ # NOTE: By its nature, batch processing is subject to race conditions if
66
+ # other processes are modifying the database.
67
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
49
68
  if block_given?
50
- find_in_batches(options) do |records|
69
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
51
70
  records.each { |record| yield record }
52
71
  end
53
72
  else
54
- enum_for :find_each, options do
55
- options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
73
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
74
+ relation = self
75
+ apply_limits(relation, start, finish).size
56
76
  end
57
77
  end
58
78
  end
59
79
 
60
- # Yields each batch of records that was found by the find +options+ as
80
+ # Yields each batch of records that was found by the find options as
61
81
  # an array.
62
82
  #
63
83
  # Person.where("age > 21").find_in_batches do |group|
@@ -76,63 +96,195 @@ module ActiveRecord
76
96
  # To be yielded each record one by one, use #find_each instead.
77
97
  #
78
98
  # ==== Options
79
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
80
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
81
- # This is especially useful if you want multiple workers dealing with
82
- # the same processing queue. You can make worker 1 handle all the records
83
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
84
- # (by setting the +:start+ option on that worker).
85
- #
86
- # # Let's process the next 2000 records
87
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
99
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
100
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
101
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
102
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
103
+ # an order is present in the relation.
104
+ #
105
+ # Limits are honored, and if present there is no requirement for the batch
106
+ # size: it can be less than, equal to, or greater than the limit.
107
+ #
108
+ # The options +start+ and +finish+ are especially useful if you want
109
+ # multiple workers dealing with the same processing queue. You can make
110
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
111
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
112
+ # option on each worker.
113
+ #
114
+ # # Let's process from record 10_000 on.
115
+ # Person.find_in_batches(start: 10_000) do |group|
88
116
  # group.each { |person| person.party_all_night! }
89
117
  # end
90
118
  #
91
119
  # NOTE: It's not possible to set the order. That is automatically set to
92
120
  # ascending on the primary key ("id ASC") to make the batch ordering
93
- # work. This also means that this method only works with integer-based
94
- # primary keys.
121
+ # work. This also means that this method only works when the primary key is
122
+ # orderable (e.g. an integer or string).
95
123
  #
96
- # NOTE: You can't set the limit either, that's used to control
97
- # the batch sizes.
98
- def find_in_batches(options = {})
99
- options.assert_valid_keys(:start, :batch_size)
100
-
124
+ # NOTE: By its nature, batch processing is subject to race conditions if
125
+ # other processes are modifying the database.
126
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
101
127
  relation = self
102
- start = options[:start]
103
- batch_size = options[:batch_size] || 1000
104
-
105
128
  unless block_given?
106
- return to_enum(:find_in_batches, options) do
107
- total = start ? where(table[primary_key].gteq(start)).size : size
129
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
130
+ total = apply_limits(relation, start, finish).size
108
131
  (total - 1).div(batch_size) + 1
109
132
  end
110
133
  end
111
134
 
112
- if logger && (arel.orders.present? || arel.taken.present?)
113
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
135
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
136
+ yield batch.to_a
137
+ end
138
+ end
139
+
140
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
141
+ #
142
+ # Person.where("age > 21").in_batches do |relation|
143
+ # relation.delete_all
144
+ # sleep(10) # Throttle the delete queries
145
+ # end
146
+ #
147
+ # If you do not provide a block to #in_batches, it will return a
148
+ # BatchEnumerator which is enumerable.
149
+ #
150
+ # Person.in_batches.each_with_index do |relation, batch_index|
151
+ # puts "Processing relation ##{batch_index}"
152
+ # relation.delete_all
153
+ # end
154
+ #
155
+ # Examples of calling methods on the returned BatchEnumerator object:
156
+ #
157
+ # Person.in_batches.delete_all
158
+ # Person.in_batches.update_all(awesome: true)
159
+ # Person.in_batches.each_record(&:party_all_night!)
160
+ #
161
+ # ==== Options
162
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
163
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
164
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
165
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
166
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
167
+ # an order is present in the relation.
168
+ #
169
+ # Limits are honored, and if present there is no requirement for the batch
170
+ # size, it can be less than, equal, or greater than the limit.
171
+ #
172
+ # The options +start+ and +finish+ are especially useful if you want
173
+ # multiple workers dealing with the same processing queue. You can make
174
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
175
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
176
+ # option on each worker.
177
+ #
178
+ # # Let's process from record 10_000 on.
179
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
180
+ #
181
+ # An example of calling where query method on the relation:
182
+ #
183
+ # Person.in_batches.each do |relation|
184
+ # relation.update_all('age = age + 1')
185
+ # relation.where('age > 21').update_all(should_party: true)
186
+ # relation.where('age <= 21').delete_all
187
+ # end
188
+ #
189
+ # NOTE: If you are going to iterate through each record, you should call
190
+ # #each_record on the yielded BatchEnumerator:
191
+ #
192
+ # Person.in_batches.each_record(&:party_all_night!)
193
+ #
194
+ # NOTE: It's not possible to set the order. That is automatically set to
195
+ # ascending on the primary key ("id ASC") to make the batch ordering
196
+ # consistent. Therefore the primary key must be orderable, e.g. an integer
197
+ # or a string.
198
+ #
199
+ # NOTE: By its nature, batch processing is subject to race conditions if
200
+ # other processes are modifying the database.
201
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
202
+ relation = self
203
+ unless block_given?
204
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
205
+ end
206
+
207
+ if arel.orders.present?
208
+ act_on_ignored_order(error_on_ignore)
209
+ end
210
+
211
+ batch_limit = of
212
+ if limit_value
213
+ remaining = limit_value
214
+ batch_limit = remaining if remaining < batch_limit
114
215
  end
115
216
 
116
- relation = relation.reorder(batch_order).limit(batch_size)
117
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
217
+ relation = relation.reorder(batch_order).limit(batch_limit)
218
+ relation = apply_limits(relation, start, finish)
219
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
220
+ batch_relation = relation
221
+
222
+ loop do
223
+ if load
224
+ records = batch_relation.records
225
+ ids = records.map(&:id)
226
+ yielded_relation = where(primary_key => ids)
227
+ yielded_relation.load_records(records)
228
+ else
229
+ ids = batch_relation.pluck(primary_key)
230
+ yielded_relation = where(primary_key => ids)
231
+ end
232
+
233
+ break if ids.empty?
118
234
 
119
- while records.any?
120
- records_size = records.size
121
- primary_key_offset = records.last.id
122
- raise "Primary key not included in the custom select clause" unless primary_key_offset
235
+ primary_key_offset = ids.last
236
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
123
237
 
124
- yield records
238
+ yield yielded_relation
125
239
 
126
- break if records_size < batch_size
240
+ break if ids.length < batch_limit
127
241
 
128
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
242
+ if limit_value
243
+ remaining -= ids.length
244
+
245
+ if remaining == 0
246
+ # Saves a useless iteration when the limit is a multiple of the
247
+ # batch size.
248
+ break
249
+ elsif remaining < batch_limit
250
+ relation = relation.limit(remaining)
251
+ end
252
+ end
253
+
254
+ batch_relation = relation.where(
255
+ bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
256
+ )
129
257
  end
130
258
  end
131
259
 
132
260
  private
133
261
 
134
- def batch_order
135
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
136
- end
262
+ def apply_limits(relation, start, finish)
263
+ relation = apply_start_limit(relation, start) if start
264
+ relation = apply_finish_limit(relation, finish) if finish
265
+ relation
266
+ end
267
+
268
+ def apply_start_limit(relation, start)
269
+ relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
270
+ end
271
+
272
+ def apply_finish_limit(relation, finish)
273
+ relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
274
+ end
275
+
276
+ def batch_order
277
+ arel_attribute(primary_key).asc
278
+ end
279
+
280
+ def act_on_ignored_order(error_on_ignore)
281
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
282
+
283
+ if raise_error
284
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
285
+ elsif logger
286
+ logger.warn(ORDER_IGNORE_MESSAGE)
287
+ end
288
+ end
137
289
  end
138
290
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Batches
5
+ class BatchEnumerator
6
+ include Enumerable
7
+
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
9
+ @of = of
10
+ @relation = relation
11
+ @start = start
12
+ @finish = finish
13
+ end
14
+
15
+ # Looping through a collection of records from the database (using the
16
+ # +all+ method, for example) is very inefficient since it will try to
17
+ # instantiate all the objects at once.
18
+ #
19
+ # In that case, batch processing methods allow you to work with the
20
+ # records in batches, thereby greatly reducing memory consumption.
21
+ #
22
+ # Person.in_batches.each_record do |person|
23
+ # person.do_awesome_stuff
24
+ # end
25
+ #
26
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
27
+ # person.party_all_night!
28
+ # end
29
+ #
30
+ # If you do not provide a block to #each_record, it will return an Enumerator
31
+ # for chaining with other methods:
32
+ #
33
+ # Person.in_batches.each_record.with_index do |person, index|
34
+ # person.award_trophy(index + 1)
35
+ # end
36
+ def each_record
37
+ return to_enum(:each_record) unless block_given?
38
+
39
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
40
+ relation.records.each { |record| yield record }
41
+ end
42
+ end
43
+
44
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
45
+ #
46
+ # People.in_batches.delete_all
47
+ # People.where('age < 10').in_batches.destroy_all
48
+ # People.in_batches.update_all('age = age + 1')
49
+ [:delete_all, :update_all, :destroy_all].each do |method|
50
+ define_method(method) do |*args, &block|
51
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
52
+ relation.send(method, *args, &block)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Yields an ActiveRecord::Relation object for each batch of records.
58
+ #
59
+ # Person.in_batches.each do |relation|
60
+ # relation.update_all(awesome: true)
61
+ # end
62
+ def each
63
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
64
+ return enum.each { |relation| yield relation } if block_given?
65
+ enum
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Calculations
3
5
  # Count the records.
@@ -14,127 +16,134 @@ module ActiveRecord
14
16
  # Person.distinct.count(:age)
15
17
  # # => counts the number of different age values
16
18
  #
17
- # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
19
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
20
+ # it returns a Hash whose keys represent the aggregated column,
18
21
  # and the values are the respective amounts:
19
22
  #
20
23
  # Person.group(:city).count
21
24
  # # => { 'Rome' => 5, 'Paris' => 3 }
22
25
  #
23
- # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
26
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
24
27
  # keys are an array containing the individual values of each column and the value
25
- # of each key would be the +count+.
28
+ # of each key would be the #count.
26
29
  #
27
30
  # Article.group(:status, :category).count
28
31
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
29
32
  # ["published", "business"]=>0, ["published", "technology"]=>2}
30
33
  #
31
- # If +count+ is used with +select+, it will count the selected columns:
34
+ # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
32
35
  #
33
36
  # Person.select(:age).count
34
37
  # # => counts the number of different age values
35
38
  #
36
- # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
39
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
37
40
  # between databases. In invalid cases, an error from the database is thrown.
38
- def count(column_name = nil, options = {})
39
- if options.present? && !ActiveRecord.const_defined?(:DeprecatedFinders)
40
- raise ArgumentError, "Relation#count does not support finder options anymore. " \
41
- "Please build a scope and then call count on it or use the " \
42
- "activerecord-deprecated_finders gem to enable this functionality."
41
+ def count(column_name = nil)
42
+ if block_given?
43
+ unless column_name.nil?
44
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
45
+ end
43
46
 
47
+ super()
48
+ else
49
+ calculate(:count, column_name)
44
50
  end
45
-
46
- # TODO: Remove options argument as soon we remove support to
47
- # activerecord-deprecated_finders.
48
- calculate(:count, column_name, options)
49
51
  end
50
52
 
51
53
  # Calculates the average value on a given column. Returns +nil+ if there's
52
- # no row. See +calculate+ for examples with options.
54
+ # no row. See #calculate for examples with options.
53
55
  #
54
56
  # Person.average(:age) # => 35.8
55
- def average(column_name, options = {})
56
- # TODO: Remove options argument as soon we remove support to
57
- # activerecord-deprecated_finders.
58
- calculate(:average, column_name, options)
57
+ def average(column_name)
58
+ calculate(:average, column_name)
59
59
  end
60
60
 
61
61
  # Calculates the minimum value on a given column. The value is returned
62
62
  # with the same data type of the column, or +nil+ if there's no row. See
63
- # +calculate+ for examples with options.
63
+ # #calculate for examples with options.
64
64
  #
65
65
  # Person.minimum(:age) # => 7
66
- def minimum(column_name, options = {})
67
- # TODO: Remove options argument as soon we remove support to
68
- # activerecord-deprecated_finders.
69
- calculate(:minimum, column_name, options)
66
+ def minimum(column_name)
67
+ calculate(:minimum, column_name)
70
68
  end
71
69
 
72
70
  # Calculates the maximum value on a given column. The value is returned
73
71
  # with the same data type of the column, or +nil+ if there's no row. See
74
- # +calculate+ for examples with options.
72
+ # #calculate for examples with options.
75
73
  #
76
74
  # Person.maximum(:age) # => 93
77
- def maximum(column_name, options = {})
78
- # TODO: Remove options argument as soon we remove support to
79
- # activerecord-deprecated_finders.
80
- calculate(:maximum, column_name, options)
75
+ def maximum(column_name)
76
+ calculate(:maximum, column_name)
81
77
  end
82
78
 
83
79
  # Calculates the sum of values on a given column. The value is returned
84
- # with the same data type of the column, 0 if there's no row. See
85
- # +calculate+ for examples with options.
80
+ # with the same data type of the column, +0+ if there's no row. See
81
+ # #calculate for examples with options.
86
82
  #
87
83
  # Person.sum(:age) # => 4562
88
- def sum(*args)
89
- calculate(:sum, *args)
84
+ def sum(column_name = nil)
85
+ if block_given?
86
+ unless column_name.nil?
87
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
88
+ end
89
+
90
+ super()
91
+ else
92
+ calculate(:sum, column_name)
93
+ end
90
94
  end
91
95
 
92
- # This calculates aggregate values in the given column. Methods for count, sum, average,
93
- # minimum, and maximum have been added as shortcuts.
96
+ # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
97
+ # #minimum, and #maximum have been added as shortcuts.
94
98
  #
95
- # There are two basic forms of output:
99
+ # Person.calculate(:count, :all) # The same as Person.count
100
+ # Person.average(:age) # SELECT AVG(age) FROM people...
96
101
  #
97
- # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
98
- # for AVG, and the given column's type for everything else.
102
+ # # Selects the minimum age for any family without any minors
103
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
99
104
  #
100
- # * Grouped values: This returns an ordered hash of the values and groups them. It
101
- # takes either a column name, or the name of a belongs_to association.
105
+ # Person.sum("2 * age")
102
106
  #
103
- # values = Person.group('last_name').maximum(:age)
104
- # puts values["Drake"]
105
- # # => 43
107
+ # There are two basic forms of output:
106
108
  #
107
- # drake = Family.find_by(last_name: 'Drake')
108
- # values = Person.group(:family).maximum(:age) # Person belongs_to :family
109
- # puts values[drake]
110
- # # => 43
109
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
110
+ # for AVG, and the given column's type for everything else.
111
111
  #
112
- # values.each do |family, max_age|
113
- # ...
114
- # end
112
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
113
+ # takes either a column name, or the name of a belongs_to association.
115
114
  #
116
- # Person.calculate(:count, :all) # The same as Person.count
117
- # Person.average(:age) # SELECT AVG(age) FROM people...
115
+ # values = Person.group('last_name').maximum(:age)
116
+ # puts values["Drake"]
117
+ # # => 43
118
118
  #
119
- # # Selects the minimum age for any family without any minors
120
- # Person.group(:last_name).having("min(age) > 17").minimum(:age)
119
+ # drake = Family.find_by(last_name: 'Drake')
120
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
121
+ # puts values[drake]
122
+ # # => 43
121
123
  #
122
- # Person.sum("2 * age")
123
- def calculate(operation, column_name, options = {})
124
- # TODO: Remove options argument as soon we remove support to
125
- # activerecord-deprecated_finders.
126
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
127
- column_name = attribute_alias(column_name)
128
- end
129
-
124
+ # values.each do |family, max_age|
125
+ # ...
126
+ # end
127
+ def calculate(operation, column_name)
130
128
  if has_include?(column_name)
131
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
129
+ relation = apply_join_dependency
130
+
131
+ if operation.to_s.downcase == "count"
132
+ unless distinct_value || distinct_select?(column_name || select_for_count)
133
+ relation.distinct!
134
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
135
+ end
136
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
137
+ relation.order_values = []
138
+ end
139
+
140
+ relation.calculate(operation, column_name)
132
141
  else
133
- perform_calculation(operation, column_name, options)
142
+ perform_calculation(operation, column_name)
134
143
  end
135
144
  end
136
145
 
137
- # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
146
+ # Use #pluck as a shortcut to select one or more attributes without
138
147
  # loading a bunch of records just to grab the attributes you want.
139
148
  #
140
149
  # Person.pluck(:name)
@@ -143,19 +152,19 @@ module ActiveRecord
143
152
  #
144
153
  # Person.all.map(&:name)
145
154
  #
146
- # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
155
+ # Pluck returns an Array of attribute values type-casted to match
147
156
  # the plucked column names, if they can be deduced. Plucking an SQL fragment
148
157
  # returns String values by default.
149
158
  #
150
- # Person.pluck(:id)
151
- # # SELECT people.id FROM people
152
- # # => [1, 2, 3]
159
+ # Person.pluck(:name)
160
+ # # SELECT people.name FROM people
161
+ # # => ['David', 'Jeremy', 'Jose']
153
162
  #
154
163
  # Person.pluck(:id, :name)
155
164
  # # SELECT people.id, people.name FROM people
156
165
  # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
157
166
  #
158
- # Person.pluck('DISTINCT role')
167
+ # Person.distinct.pluck(:role)
159
168
  # # SELECT DISTINCT role FROM people
160
169
  # # => ['admin', 'member', 'guest']
161
170
  #
@@ -167,27 +176,43 @@ module ActiveRecord
167
176
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
168
177
  # # => ['0', '27761', '173']
169
178
  #
179
+ # See also #ids.
180
+ #
170
181
  def pluck(*column_names)
171
- column_names.map! do |column_name|
172
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
173
- attribute_alias(column_name)
174
- else
175
- column_name.to_s
176
- end
182
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
183
+ return records.pluck(*column_names)
177
184
  end
178
185
 
179
186
  if has_include?(column_names.first)
180
- construct_relation_for_association_calculations.pluck(*column_names)
187
+ relation = apply_join_dependency
188
+ relation.pluck(*column_names)
181
189
  else
190
+ klass.disallow_raw_sql!(column_names)
182
191
  relation = spawn
183
- relation.select_values = column_names.map { |cn|
184
- columns_hash.key?(cn) ? arel_table[cn] : cn
185
- }
186
- result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
187
- result.cast_values(klass.column_types)
192
+ relation.select_values = column_names
193
+ result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
194
+ result.cast_values(klass.attribute_types)
188
195
  end
189
196
  end
190
197
 
198
+ # Pick the value(s) from the named column(s) in the current relation.
199
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
200
+ # when you have a relation that's already narrowed down to a single row.
201
+ #
202
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
203
+ # more efficient. The value is, again like with pluck, typecast by the column type.
204
+ #
205
+ # Person.where(id: 1).pick(:name)
206
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
207
+ # # => 'David'
208
+ #
209
+ # Person.where(id: 1).pick(:name, :email_address)
210
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
211
+ # # => [ 'David', 'david@loudthinking.com' ]
212
+ def pick(*column_names)
213
+ limit(1).pluck(*column_names).first
214
+ end
215
+
191
216
  # Pluck all the ID's for the relation using the table's primary key
192
217
  #
193
218
  # Person.ids # SELECT people.id FROM people
@@ -197,214 +222,203 @@ module ActiveRecord
197
222
  end
198
223
 
199
224
  private
225
+ def has_include?(column_name)
226
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
227
+ end
200
228
 
201
- def has_include?(column_name)
202
- eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
203
- end
204
-
205
- def perform_calculation(operation, column_name, options = {})
206
- # TODO: Remove options argument as soon we remove support to
207
- # activerecord-deprecated_finders.
208
- operation = operation.to_s.downcase
209
-
210
- # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
211
- distinct = self.distinct_value
212
-
213
- if operation == "count"
214
- column_name ||= select_for_count
215
-
216
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
217
- distinct = true
229
+ def perform_calculation(operation, column_name)
230
+ operation = operation.to_s.downcase
231
+
232
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
233
+ # considered distinct.
234
+ distinct = distinct_value
235
+
236
+ if operation == "count"
237
+ column_name ||= select_for_count
238
+ if column_name == :all
239
+ if !distinct
240
+ distinct = distinct_select?(select_for_count) if group_values.empty?
241
+ elsif group_values.any? || select_values.empty? && order_values.empty?
242
+ column_name = primary_key
243
+ end
244
+ elsif distinct_select?(column_name)
245
+ distinct = nil
246
+ end
218
247
  end
219
248
 
220
- column_name = primary_key if column_name == :all && distinct
221
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
249
+ if group_values.any?
250
+ execute_grouped_calculation(operation, column_name, distinct)
251
+ else
252
+ execute_simple_calculation(operation, column_name, distinct)
253
+ end
222
254
  end
223
255
 
224
- if group_values.any?
225
- execute_grouped_calculation(operation, column_name, distinct)
226
- else
227
- execute_simple_calculation(operation, column_name, distinct)
256
+ def distinct_select?(column_name)
257
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
228
258
  end
229
- end
230
259
 
231
- def aggregate_column(column_name)
232
- if @klass.column_names.include?(column_name.to_s)
233
- Arel::Attribute.new(@klass.unscoped.table, column_name)
234
- else
235
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
260
+ def aggregate_column(column_name)
261
+ return column_name if Arel::Expressions === column_name
262
+
263
+ arel_column(column_name.to_s) do |name|
264
+ Arel.sql(column_name == :all ? "*" : name)
265
+ end
236
266
  end
237
- end
238
267
 
239
- def operation_over_aggregate_column(column, operation, distinct)
240
- operation == 'count' ? column.count(distinct) : column.send(operation)
241
- end
268
+ def operation_over_aggregate_column(column, operation, distinct)
269
+ operation == "count" ? column.count(distinct) : column.send(operation)
270
+ end
242
271
 
243
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
244
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
245
- relation = unscope(:order)
272
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
273
+ column_alias = column_name
246
274
 
247
- column_alias = column_name
275
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
276
+ # Shortcut when limit is zero.
277
+ return 0 if limit_value == 0
248
278
 
249
- bind_values = nil
279
+ query_builder = build_count_subquery(spawn, column_name, distinct)
280
+ else
281
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
282
+ relation = unscope(:order).distinct!(false)
250
283
 
251
- if operation == "count" && (relation.limit_value || relation.offset_value)
252
- # Shortcut when limit is zero.
253
- return 0 if relation.limit_value == 0
284
+ column = aggregate_column(column_name)
254
285
 
255
- query_builder = build_count_subquery(relation, column_name, distinct)
256
- bind_values = query_builder.bind_values + relation.bind_values
257
- else
258
- column = aggregate_column(column_name)
286
+ select_value = operation_over_aggregate_column(column, operation, distinct)
287
+ if operation == "sum" && distinct
288
+ select_value.distinct = true
289
+ end
259
290
 
260
- select_value = operation_over_aggregate_column(column, operation, distinct)
291
+ column_alias = select_value.alias
292
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
293
+ relation.select_values = [select_value]
261
294
 
262
- column_alias = select_value.alias
263
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
264
- relation.select_values = [select_value]
295
+ query_builder = relation.arel
296
+ end
265
297
 
266
- query_builder = relation.arel
267
- bind_values = query_builder.bind_values + relation.bind_values
268
- end
298
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
299
+ row = result.first
300
+ value = row && row.values.first
301
+ type = result.column_types.fetch(column_alias) do
302
+ type_for(column_name)
303
+ end
269
304
 
270
- result = @klass.connection.select_all(query_builder, nil, bind_values)
271
- row = result.first
272
- value = row && row.values.first
273
- column = result.column_types.fetch(column_alias) do
274
- type_for(column_name)
305
+ type_cast_calculated_value(value, type, operation)
275
306
  end
276
307
 
277
- type_cast_calculated_value(value, column, operation)
278
- end
279
-
280
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
281
- group_attrs = group_values
308
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
309
+ group_fields = group_values
282
310
 
283
- if group_attrs.first.respond_to?(:to_sym)
284
- association = @klass._reflect_on_association(group_attrs.first)
285
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
286
- group_fields = Array(associated ? association.foreign_key : group_attrs)
287
- else
288
- group_fields = group_attrs
289
- end
290
- group_fields = arel_columns(group_fields)
311
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
312
+ association = klass._reflect_on_association(group_fields.first)
313
+ associated = association && association.belongs_to? # only count belongs_to associations
314
+ group_fields = Array(association.foreign_key) if associated
315
+ end
316
+ group_fields = arel_columns(group_fields)
291
317
 
292
- group_aliases = group_fields.map { |field|
293
- column_alias_for(field)
294
- }
295
- group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
296
- [aliaz, field]
297
- }
318
+ group_aliases = group_fields.map { |field|
319
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
320
+ column_alias_for(field.to_s.downcase)
321
+ }
322
+ group_columns = group_aliases.zip(group_fields)
323
+
324
+ aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
325
+
326
+ select_values = [
327
+ operation_over_aggregate_column(
328
+ aggregate_column(column_name),
329
+ operation,
330
+ distinct).as(aggregate_alias)
331
+ ]
332
+ select_values += self.select_values unless having_clause.empty?
333
+
334
+ select_values.concat group_columns.map { |aliaz, field|
335
+ if field.respond_to?(:as)
336
+ field.as(aliaz)
337
+ else
338
+ "#{field} AS #{aliaz}"
339
+ end
340
+ }
298
341
 
299
- group = group_fields
342
+ relation = except(:group).distinct!(false)
343
+ relation.group_values = group_fields
344
+ relation.select_values = select_values
300
345
 
301
- if operation == 'count' && column_name == :all
302
- aggregate_alias = 'count_all'
303
- else
304
- aggregate_alias = column_alias_for([operation, column_name].join(' '))
305
- end
346
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
306
347
 
307
- select_values = [
308
- operation_over_aggregate_column(
309
- aggregate_column(column_name),
310
- operation,
311
- distinct).as(aggregate_alias)
312
- ]
313
- select_values += self.select_values unless having_values.empty?
314
-
315
- select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
316
- if field.respond_to?(:as)
317
- field.as(aliaz)
318
- else
319
- "#{field} AS #{aliaz}"
348
+ if association
349
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
350
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
351
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
320
352
  end
321
- }
322
-
323
- relation = except(:group)
324
- relation.group_values = group
325
- relation.select_values = select_values
326
-
327
- calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
328
353
 
329
- if association
330
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
331
- key_records = association.klass.base_class.find(key_ids)
332
- key_records = Hash[key_records.map { |r| [r.id, r] }]
354
+ Hash[calculated_data.map do |row|
355
+ key = group_columns.map { |aliaz, col_name|
356
+ type = type_for(col_name) do
357
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
358
+ end
359
+ type_cast_calculated_value(row[aliaz], type)
360
+ }
361
+ key = key.first if key.size == 1
362
+ key = key_records[key] if associated
363
+
364
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
365
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
366
+ end]
333
367
  end
334
368
 
335
- Hash[calculated_data.map do |row|
336
- key = group_columns.map { |aliaz, col_name|
337
- column = calculated_data.column_types.fetch(aliaz) do
338
- type_for(col_name)
339
- end
340
- type_cast_calculated_value(row[aliaz], column)
341
- }
342
- key = key.first if key.size == 1
343
- key = key_records[key] if associated
344
-
345
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
346
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
347
- end]
348
- end
349
-
350
- # Converts the given keys to the value that the database adapter returns as
351
- # a usable column name:
352
- #
353
- # column_alias_for("users.id") # => "users_id"
354
- # column_alias_for("sum(id)") # => "sum_id"
355
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
356
- # column_alias_for("count(*)") # => "count_all"
357
- # column_alias_for("count", "id") # => "count_id"
358
- def column_alias_for(keys)
359
- if keys.respond_to? :name
360
- keys = "#{keys.relation.name}.#{keys.name}"
369
+ # Converts the given field to the value that the database adapter returns as
370
+ # a usable column name:
371
+ #
372
+ # column_alias_for("users.id") # => "users_id"
373
+ # column_alias_for("sum(id)") # => "sum_id"
374
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
375
+ # column_alias_for("count(*)") # => "count_all"
376
+ def column_alias_for(field)
377
+ column_alias = +field
378
+ column_alias.gsub!(/\*/, "all")
379
+ column_alias.gsub!(/\W+/, " ")
380
+ column_alias.strip!
381
+ column_alias.gsub!(/ +/, "_")
382
+
383
+ connection.table_alias_for(column_alias)
361
384
  end
362
385
 
363
- table_name = keys.to_s.downcase
364
- table_name.gsub!(/\*/, 'all')
365
- table_name.gsub!(/\W+/, ' ')
366
- table_name.strip!
367
- table_name.gsub!(/ +/, '_')
368
-
369
- @klass.connection.table_alias_for(table_name)
370
- end
371
-
372
- def type_for(field)
373
- field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
374
- @klass.type_for_attribute(field_name)
375
- end
386
+ def type_for(field, &block)
387
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
388
+ @klass.type_for_attribute(field_name, &block)
389
+ end
376
390
 
377
- def type_cast_calculated_value(value, type, operation = nil)
378
- case operation
379
- when 'count' then value.to_i
380
- when 'sum' then type.type_cast_from_database(value || 0)
381
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
382
- else type.type_cast_from_database(value)
391
+ def type_cast_calculated_value(value, type, operation = nil)
392
+ case operation
393
+ when "count" then value.to_i
394
+ when "sum" then type.deserialize(value || 0)
395
+ when "average" then value&.respond_to?(:to_d) ? value.to_d : value
396
+ else type.deserialize(value)
397
+ end
383
398
  end
384
- end
385
399
 
386
- # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
387
- def select_for_count
388
- if select_values.present?
389
- select_values.join(", ")
390
- else
391
- :all
400
+ def select_for_count
401
+ if select_values.present?
402
+ return select_values.first if select_values.one?
403
+ select_values.join(", ")
404
+ else
405
+ :all
406
+ end
392
407
  end
393
- end
394
408
 
395
- def build_count_subquery(relation, column_name, distinct)
396
- column_alias = Arel.sql('count_column')
397
- subquery_alias = Arel.sql('subquery_for_count')
409
+ def build_count_subquery(relation, column_name, distinct)
410
+ if column_name == :all
411
+ column_alias = Arel.star
412
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
413
+ else
414
+ column_alias = Arel.sql("count_column")
415
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
416
+ end
398
417
 
399
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
400
- relation.select_values = [aliased_column]
401
- arel = relation.arel
402
- subquery = arel.as(subquery_alias)
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
403
420
 
404
- sm = Arel::SelectManager.new relation.engine
405
- sm.bind_values = arel.bind_values
406
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
407
- sm.project(select_value).from(subquery)
408
- end
421
+ relation.build_subquery(subquery_alias, select_value)
422
+ end
409
423
  end
410
424
  end