activerecord 4.2.9 → 6.1.4.1

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 (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +964 -1382
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +15 -14
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +266 -251
  8. data/lib/active_record/association_relation.rb +40 -15
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +162 -69
  11. data/lib/active_record/associations/association_scope.rb +105 -130
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -65
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  14. data/lib/active_record/associations/builder/association.rb +57 -43
  15. data/lib/active_record/associations/builder/belongs_to.rb +74 -57
  16. data/lib/active_record/associations/builder/collection_association.rb +15 -37
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
  18. data/lib/active_record/associations/builder/has_many.rb +13 -5
  19. data/lib/active_record/associations/builder/has_one.rb +44 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +148 -287
  22. data/lib/active_record/associations/collection_proxy.rb +252 -150
  23. data/lib/active_record/associations/foreign_association.rb +23 -1
  24. data/lib/active_record/associations/has_many_association.rb +56 -98
  25. data/lib/active_record/associations/has_many_through_association.rb +68 -89
  26. data/lib/active_record/associations/has_one_association.rb +73 -47
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +174 -169
  32. data/lib/active_record/associations/preloader/association.rb +108 -115
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +97 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -39
  36. data/lib/active_record/associations/through_association.rb +39 -19
  37. data/lib/active_record/associations.rb +1845 -1598
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -148
  41. data/lib/active_record/attribute_methods/primary_key.rb +93 -83
  42. data/lib/active_record/attribute_methods/query.rb +8 -10
  43. data/lib/active_record/attribute_methods/read.rb +19 -79
  44. data/lib/active_record/attribute_methods/serialization.rb +49 -24
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
  46. data/lib/active_record/attribute_methods/write.rb +24 -55
  47. data/lib/active_record/attribute_methods.rb +149 -154
  48. data/lib/active_record/attributes.rb +234 -78
  49. data/lib/active_record/autosave_association.rb +133 -60
  50. data/lib/active_record/base.rb +46 -46
  51. data/lib/active_record/callbacks.rb +234 -79
  52. data/lib/active_record/coders/json.rb +3 -1
  53. data/lib/active_record/coders/yaml_column.rb +34 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
  67. data/lib/active_record/connection_adapters/column.rb +67 -40
  68. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  69. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
  72. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
  80. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  81. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -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 +44 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  101. data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  109. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
  110. data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
  111. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  112. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  114. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
  116. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  117. data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
  118. data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
  119. data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
  120. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  121. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  122. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  123. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
  129. data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
  130. data/lib/active_record/connection_adapters.rb +52 -0
  131. data/lib/active_record/connection_handling.rb +314 -41
  132. data/lib/active_record/core.rb +458 -241
  133. data/lib/active_record/counter_cache.rb +70 -49
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  135. data/lib/active_record/database_configurations/database_config.rb +80 -0
  136. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  137. data/lib/active_record/database_configurations/url_config.rb +53 -0
  138. data/lib/active_record/database_configurations.rb +272 -0
  139. data/lib/active_record/delegated_type.rb +209 -0
  140. data/lib/active_record/destroy_association_async_job.rb +36 -0
  141. data/lib/active_record/dynamic_matchers.rb +87 -106
  142. data/lib/active_record/enum.rb +211 -92
  143. data/lib/active_record/errors.rb +224 -54
  144. data/lib/active_record/explain.rb +27 -11
  145. data/lib/active_record/explain_registry.rb +4 -2
  146. data/lib/active_record/explain_subscriber.rb +10 -5
  147. data/lib/active_record/fixture_set/file.rb +33 -14
  148. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  149. data/lib/active_record/fixture_set/render_context.rb +17 -0
  150. data/lib/active_record/fixture_set/table_row.rb +152 -0
  151. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  152. data/lib/active_record/fixtures.rb +275 -500
  153. data/lib/active_record/gem_version.rb +6 -4
  154. data/lib/active_record/inheritance.rb +175 -110
  155. data/lib/active_record/insert_all.rb +212 -0
  156. data/lib/active_record/integration.rb +121 -29
  157. data/lib/active_record/internal_metadata.rb +62 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +27 -5
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +98 -92
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +93 -31
  163. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector.rb +77 -0
  166. data/lib/active_record/migration/command_recorder.rb +185 -90
  167. data/lib/active_record/migration/compatibility.rb +295 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +673 -325
  170. data/lib/active_record/model_schema.rb +418 -113
  171. data/lib/active_record/nested_attributes.rb +263 -224
  172. data/lib/active_record/no_touching.rb +15 -2
  173. data/lib/active_record/null_relation.rb +24 -38
  174. data/lib/active_record/persistence.rb +572 -136
  175. data/lib/active_record/query_cache.rb +29 -23
  176. data/lib/active_record/querying.rb +50 -31
  177. data/lib/active_record/railtie.rb +170 -51
  178. data/lib/active_record/railties/console_sandbox.rb +3 -3
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +523 -199
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +454 -291
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
  184. data/lib/active_record/relation/batches.rb +217 -59
  185. data/lib/active_record/relation/calculations.rb +324 -249
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +316 -242
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +95 -103
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  192. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  193. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
  194. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  195. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  196. data/lib/active_record/relation/predicate_builder.rb +136 -122
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -413
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +18 -20
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -343
  203. data/lib/active_record/result.rb +91 -47
  204. data/lib/active_record/runtime_registry.rb +6 -4
  205. data/lib/active_record/sanitization.rb +134 -122
  206. data/lib/active_record/schema.rb +21 -24
  207. data/lib/active_record/schema_dumper.rb +141 -92
  208. data/lib/active_record/schema_migration.rb +24 -23
  209. data/lib/active_record/scoping/default.rb +96 -83
  210. data/lib/active_record/scoping/named.rb +78 -36
  211. data/lib/active_record/scoping.rb +45 -27
  212. data/lib/active_record/secure_token.rb +48 -0
  213. data/lib/active_record/serialization.rb +8 -6
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +89 -36
  216. data/lib/active_record/store.rb +128 -43
  217. data/lib/active_record/suppressor.rb +61 -0
  218. data/lib/active_record/table_metadata.rb +81 -0
  219. data/lib/active_record/tasks/database_tasks.rb +364 -130
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +287 -0
  225. data/lib/active_record/timestamp.rb +86 -43
  226. data/lib/active_record/touch_later.rb +65 -0
  227. data/lib/active_record/transactions.rb +182 -163
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type/adapter_specific_registry.rb +126 -0
  230. data/lib/active_record/type/date.rb +4 -45
  231. data/lib/active_record/type/date_time.rb +4 -49
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  234. data/lib/active_record/type/internal/timezone.rb +17 -0
  235. data/lib/active_record/type/json.rb +30 -0
  236. data/lib/active_record/type/serialized.rb +27 -15
  237. data/lib/active_record/type/text.rb +2 -2
  238. data/lib/active_record/type/time.rb +21 -16
  239. data/lib/active_record/type/type_map.rb +16 -19
  240. data/lib/active_record/type/unsigned_integer.rb +9 -8
  241. data/lib/active_record/type.rb +84 -23
  242. data/lib/active_record/type_caster/connection.rb +33 -0
  243. data/lib/active_record/type_caster/map.rb +23 -0
  244. data/lib/active_record/type_caster.rb +9 -0
  245. data/lib/active_record/validations/absence.rb +25 -0
  246. data/lib/active_record/validations/associated.rb +12 -4
  247. data/lib/active_record/validations/length.rb +26 -0
  248. data/lib/active_record/validations/numericality.rb +35 -0
  249. data/lib/active_record/validations/presence.rb +14 -13
  250. data/lib/active_record/validations/uniqueness.rb +63 -56
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +42 -29
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes/and.rb +32 -0
  269. data/lib/arel/nodes/ascending.rb +23 -0
  270. data/lib/arel/nodes/binary.rb +126 -0
  271. data/lib/arel/nodes/bind_param.rb +44 -0
  272. data/lib/arel/nodes/case.rb +55 -0
  273. data/lib/arel/nodes/casted.rb +62 -0
  274. data/lib/arel/nodes/comment.rb +29 -0
  275. data/lib/arel/nodes/count.rb +12 -0
  276. data/lib/arel/nodes/delete_statement.rb +45 -0
  277. data/lib/arel/nodes/descending.rb +23 -0
  278. data/lib/arel/nodes/equality.rb +15 -0
  279. data/lib/arel/nodes/extract.rb +24 -0
  280. data/lib/arel/nodes/false.rb +16 -0
  281. data/lib/arel/nodes/full_outer_join.rb +8 -0
  282. data/lib/arel/nodes/function.rb +44 -0
  283. data/lib/arel/nodes/grouping.rb +11 -0
  284. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  285. data/lib/arel/nodes/in.rb +15 -0
  286. data/lib/arel/nodes/infix_operation.rb +92 -0
  287. data/lib/arel/nodes/inner_join.rb +8 -0
  288. data/lib/arel/nodes/insert_statement.rb +37 -0
  289. data/lib/arel/nodes/join_source.rb +20 -0
  290. data/lib/arel/nodes/matches.rb +18 -0
  291. data/lib/arel/nodes/named_function.rb +23 -0
  292. data/lib/arel/nodes/node.rb +51 -0
  293. data/lib/arel/nodes/node_expression.rb +13 -0
  294. data/lib/arel/nodes/ordering.rb +27 -0
  295. data/lib/arel/nodes/outer_join.rb +8 -0
  296. data/lib/arel/nodes/over.rb +15 -0
  297. data/lib/arel/nodes/regexp.rb +16 -0
  298. data/lib/arel/nodes/right_outer_join.rb +8 -0
  299. data/lib/arel/nodes/select_core.rb +67 -0
  300. data/lib/arel/nodes/select_statement.rb +41 -0
  301. data/lib/arel/nodes/sql_literal.rb +19 -0
  302. data/lib/arel/nodes/string_join.rb +11 -0
  303. data/lib/arel/nodes/table_alias.rb +31 -0
  304. data/lib/arel/nodes/terminal.rb +16 -0
  305. data/lib/arel/nodes/true.rb +16 -0
  306. data/lib/arel/nodes/unary.rb +44 -0
  307. data/lib/arel/nodes/unary_operation.rb +20 -0
  308. data/lib/arel/nodes/unqualified_column.rb +22 -0
  309. data/lib/arel/nodes/update_statement.rb +41 -0
  310. data/lib/arel/nodes/values_list.rb +9 -0
  311. data/lib/arel/nodes/window.rb +126 -0
  312. data/lib/arel/nodes/with.rb +11 -0
  313. data/lib/arel/nodes.rb +70 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors/dot.rb +308 -0
  321. data/lib/arel/visitors/mysql.rb +93 -0
  322. data/lib/arel/visitors/postgresql.rb +120 -0
  323. data/lib/arel/visitors/sqlite.rb +38 -0
  324. data/lib/arel/visitors/to_sql.rb +899 -0
  325. data/lib/arel/visitors/visitor.rb +45 -0
  326. data/lib/arel/visitors.rb +13 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/arel.rb +54 -0
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  331. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
  332. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
  333. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
  334. data/lib/rails/generators/active_record/migration.rb +35 -1
  335. data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. data/lib/rails/generators/active_record.rb +7 -5
  340. metadata +172 -65
  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_decorators.rb +0 -66
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  353. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  354. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  355. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  356. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  357. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  358. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  359. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  360. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  361. data/lib/active_record/type/big_integer.rb +0 -13
  362. data/lib/active_record/type/binary.rb +0 -50
  363. data/lib/active_record/type/boolean.rb +0 -31
  364. data/lib/active_record/type/decimal.rb +0 -64
  365. data/lib/active_record/type/decorator.rb +0 -14
  366. data/lib/active_record/type/float.rb +0 -19
  367. data/lib/active_record/type/integer.rb +0 -59
  368. data/lib/active_record/type/mutable.rb +0 -16
  369. data/lib/active_record/type/numeric.rb +0 -36
  370. data/lib/active_record/type/string.rb +0 -40
  371. data/lib/active_record/type/time_value.rb +0 -38
  372. data/lib/active_record/type/value.rb +0 -110
  373. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  374. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
1
5
  module ActiveRecord
2
6
  module Calculations
3
7
  # Count the records.
@@ -14,127 +18,134 @@ module ActiveRecord
14
18
  # Person.distinct.count(:age)
15
19
  # # => counts the number of different age values
16
20
  #
17
- # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
21
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
22
+ # it returns a Hash whose keys represent the aggregated column,
18
23
  # and the values are the respective amounts:
19
24
  #
20
25
  # Person.group(:city).count
21
26
  # # => { 'Rome' => 5, 'Paris' => 3 }
22
27
  #
23
- # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
28
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
24
29
  # keys are an array containing the individual values of each column and the value
25
- # of each key would be the +count+.
30
+ # of each key would be the #count.
26
31
  #
27
32
  # Article.group(:status, :category).count
28
33
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
29
34
  # ["published", "business"]=>0, ["published", "technology"]=>2}
30
35
  #
31
- # If +count+ is used with +select+, it will count the selected columns:
36
+ # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
32
37
  #
33
38
  # Person.select(:age).count
34
39
  # # => counts the number of different age values
35
40
  #
36
- # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
41
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
37
42
  # 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."
43
+ def count(column_name = nil)
44
+ if block_given?
45
+ unless column_name.nil?
46
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
47
+ end
43
48
 
49
+ super()
50
+ else
51
+ calculate(:count, column_name)
44
52
  end
45
-
46
- # TODO: Remove options argument as soon we remove support to
47
- # activerecord-deprecated_finders.
48
- calculate(:count, column_name, options)
49
53
  end
50
54
 
51
55
  # Calculates the average value on a given column. Returns +nil+ if there's
52
- # no row. See +calculate+ for examples with options.
56
+ # no row. See #calculate for examples with options.
53
57
  #
54
58
  # 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)
59
+ def average(column_name)
60
+ calculate(:average, column_name)
59
61
  end
60
62
 
61
63
  # Calculates the minimum value on a given column. The value is returned
62
64
  # with the same data type of the column, or +nil+ if there's no row. See
63
- # +calculate+ for examples with options.
65
+ # #calculate for examples with options.
64
66
  #
65
67
  # 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)
68
+ def minimum(column_name)
69
+ calculate(:minimum, column_name)
70
70
  end
71
71
 
72
72
  # Calculates the maximum value on a given column. The value is returned
73
73
  # with the same data type of the column, or +nil+ if there's no row. See
74
- # +calculate+ for examples with options.
74
+ # #calculate for examples with options.
75
75
  #
76
76
  # 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)
77
+ def maximum(column_name)
78
+ calculate(:maximum, column_name)
81
79
  end
82
80
 
83
81
  # 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.
82
+ # with the same data type of the column, +0+ if there's no row. See
83
+ # #calculate for examples with options.
86
84
  #
87
85
  # Person.sum(:age) # => 4562
88
- def sum(*args)
89
- calculate(:sum, *args)
86
+ def sum(column_name = nil)
87
+ if block_given?
88
+ unless column_name.nil?
89
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
90
+ end
91
+
92
+ super()
93
+ else
94
+ calculate(:sum, column_name)
95
+ end
90
96
  end
91
97
 
92
- # This calculates aggregate values in the given column. Methods for count, sum, average,
93
- # minimum, and maximum have been added as shortcuts.
98
+ # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
99
+ # #minimum, and #maximum have been added as shortcuts.
94
100
  #
95
- # There are two basic forms of output:
101
+ # Person.calculate(:count, :all) # The same as Person.count
102
+ # Person.average(:age) # SELECT AVG(age) FROM people...
96
103
  #
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.
104
+ # # Selects the minimum age for any family without any minors
105
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
99
106
  #
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.
107
+ # Person.sum("2 * age")
102
108
  #
103
- # values = Person.group('last_name').maximum(:age)
104
- # puts values["Drake"]
105
- # # => 43
109
+ # There are two basic forms of output:
106
110
  #
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
111
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
112
+ # for AVG, and the given column's type for everything else.
111
113
  #
112
- # values.each do |family, max_age|
113
- # ...
114
- # end
114
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
115
+ # takes either a column name, or the name of a belongs_to association.
115
116
  #
116
- # Person.calculate(:count, :all) # The same as Person.count
117
- # Person.average(:age) # SELECT AVG(age) FROM people...
117
+ # values = Person.group('last_name').maximum(:age)
118
+ # puts values["Drake"]
119
+ # # => 43
118
120
  #
119
- # # Selects the minimum age for any family without any minors
120
- # Person.group(:last_name).having("min(age) > 17").minimum(:age)
121
+ # drake = Family.find_by(last_name: 'Drake')
122
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
123
+ # puts values[drake]
124
+ # # => 43
121
125
  #
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
-
126
+ # values.each do |family, max_age|
127
+ # ...
128
+ # end
129
+ def calculate(operation, column_name)
130
130
  if has_include?(column_name)
131
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
131
+ relation = apply_join_dependency
132
+
133
+ if operation.to_s.downcase == "count"
134
+ unless distinct_value || distinct_select?(column_name || select_for_count)
135
+ relation.distinct!
136
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
137
+ end
138
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
139
+ relation.order_values = [] if group_values.empty?
140
+ end
141
+
142
+ relation.calculate(operation, column_name)
132
143
  else
133
- perform_calculation(operation, column_name, options)
144
+ perform_calculation(operation, column_name)
134
145
  end
135
146
  end
136
147
 
137
- # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
148
+ # Use #pluck as a shortcut to select one or more attributes without
138
149
  # loading a bunch of records just to grab the attributes you want.
139
150
  #
140
151
  # Person.pluck(:name)
@@ -143,19 +154,19 @@ module ActiveRecord
143
154
  #
144
155
  # Person.all.map(&:name)
145
156
  #
146
- # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
157
+ # Pluck returns an Array of attribute values type-casted to match
147
158
  # the plucked column names, if they can be deduced. Plucking an SQL fragment
148
159
  # returns String values by default.
149
160
  #
150
- # Person.pluck(:id)
151
- # # SELECT people.id FROM people
152
- # # => [1, 2, 3]
161
+ # Person.pluck(:name)
162
+ # # SELECT people.name FROM people
163
+ # # => ['David', 'Jeremy', 'Jose']
153
164
  #
154
165
  # Person.pluck(:id, :name)
155
166
  # # SELECT people.id, people.name FROM people
156
167
  # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
157
168
  #
158
- # Person.pluck('DISTINCT role')
169
+ # Person.distinct.pluck(:role)
159
170
  # # SELECT DISTINCT role FROM people
160
171
  # # => ['admin', 'member', 'guest']
161
172
  #
@@ -163,29 +174,56 @@ module ActiveRecord
163
174
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
164
175
  # # => [2, 3]
165
176
  #
166
- # Person.pluck('DATEDIFF(updated_at, created_at)')
177
+ # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
167
178
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
168
179
  # # => ['0', '27761', '173']
169
180
  #
181
+ # See also #ids.
182
+ #
170
183
  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
184
+ if loaded? && all_attributes?(column_names)
185
+ return records.pluck(*column_names)
177
186
  end
178
187
 
179
188
  if has_include?(column_names.first)
180
- construct_relation_for_association_calculations.pluck(*column_names)
189
+ relation = apply_join_dependency
190
+ relation.pluck(*column_names)
181
191
  else
192
+ klass.disallow_raw_sql!(column_names)
193
+ columns = arel_columns(column_names)
182
194
  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)
195
+ relation.select_values = columns
196
+ result = skip_query_cache_if_necessary do
197
+ if where_clause.contradiction?
198
+ ActiveRecord::Result.new([], [])
199
+ else
200
+ klass.connection.select_all(relation.arel, nil)
201
+ end
202
+ end
203
+ type_cast_pluck_values(result, columns)
204
+ end
205
+ end
206
+
207
+ # Pick the value(s) from the named column(s) in the current relation.
208
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
209
+ # when you have a relation that's already narrowed down to a single row.
210
+ #
211
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
212
+ # more efficient. The value is, again like with pluck, typecast by the column type.
213
+ #
214
+ # Person.where(id: 1).pick(:name)
215
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
216
+ # # => 'David'
217
+ #
218
+ # Person.where(id: 1).pick(:name, :email_address)
219
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
220
+ # # => [ 'David', 'david@loudthinking.com' ]
221
+ def pick(*column_names)
222
+ if loaded? && all_attributes?(column_names)
223
+ return records.pick(*column_names)
188
224
  end
225
+
226
+ limit(1).pluck(*column_names).first
189
227
  end
190
228
 
191
229
  # Pluck all the ID's for the relation using the table's primary key
@@ -197,214 +235,251 @@ module ActiveRecord
197
235
  end
198
236
 
199
237
  private
238
+ def all_attributes?(column_names)
239
+ (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
240
+ end
200
241
 
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
242
+ def has_include?(column_name)
243
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
244
+ end
215
245
 
216
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
217
- distinct = true
246
+ def perform_calculation(operation, column_name)
247
+ operation = operation.to_s.downcase
248
+
249
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
250
+ # considered distinct.
251
+ distinct = distinct_value
252
+
253
+ if operation == "count"
254
+ column_name ||= select_for_count
255
+ if column_name == :all
256
+ if !distinct
257
+ distinct = distinct_select?(select_for_count) if group_values.empty?
258
+ elsif group_values.any? || select_values.empty? && order_values.empty?
259
+ column_name = primary_key
260
+ end
261
+ elsif distinct_select?(column_name)
262
+ distinct = nil
263
+ end
218
264
  end
219
265
 
220
- column_name = primary_key if column_name == :all && distinct
221
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
222
- end
223
-
224
- if group_values.any?
225
- execute_grouped_calculation(operation, column_name, distinct)
226
- else
227
- execute_simple_calculation(operation, column_name, distinct)
266
+ if group_values.any?
267
+ execute_grouped_calculation(operation, column_name, distinct)
268
+ else
269
+ execute_simple_calculation(operation, column_name, distinct)
270
+ end
228
271
  end
229
- end
230
272
 
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)
273
+ def distinct_select?(column_name)
274
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
236
275
  end
237
- end
238
276
 
239
- def operation_over_aggregate_column(column, operation, distinct)
240
- operation == 'count' ? column.count(distinct) : column.send(operation)
241
- end
277
+ def aggregate_column(column_name)
278
+ return column_name if Arel::Expressions === column_name
242
279
 
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)
280
+ arel_column(column_name.to_s) do |name|
281
+ Arel.sql(column_name == :all ? "*" : name)
282
+ end
283
+ end
246
284
 
247
- column_alias = column_name
285
+ def operation_over_aggregate_column(column, operation, distinct)
286
+ operation == "count" ? column.count(distinct) : column.public_send(operation)
287
+ end
248
288
 
249
- bind_values = nil
289
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
290
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
291
+ # Shortcut when limit is zero.
292
+ return 0 if limit_value == 0
250
293
 
251
- if operation == "count" && (relation.limit_value || relation.offset_value)
252
- # Shortcut when limit is zero.
253
- return 0 if relation.limit_value == 0
294
+ query_builder = build_count_subquery(spawn, column_name, distinct)
295
+ else
296
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
297
+ relation = unscope(:order).distinct!(false)
254
298
 
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)
299
+ column = aggregate_column(column_name)
300
+ select_value = operation_over_aggregate_column(column, operation, distinct)
301
+ select_value.distinct = true if operation == "sum" && distinct
259
302
 
260
- select_value = operation_over_aggregate_column(column, operation, distinct)
303
+ relation.select_values = [select_value]
261
304
 
262
- column_alias = select_value.alias
263
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
264
- relation.select_values = [select_value]
305
+ query_builder = relation.arel
306
+ end
265
307
 
266
- query_builder = relation.arel
267
- bind_values = query_builder.bind_values + relation.bind_values
268
- end
308
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
269
309
 
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)
310
+ type_cast_calculated_value(result.cast_values.first, operation) do |value|
311
+ type = column.try(:type_caster) ||
312
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
313
+ type = type.subtype if Enum::EnumType === type
314
+ type.deserialize(value)
315
+ end
275
316
  end
276
317
 
277
- type_cast_calculated_value(value, column, operation)
278
- end
318
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
319
+ group_fields = group_values
320
+ group_fields = group_fields.uniq if group_fields.size > 1
321
+
322
+ unless group_fields == group_values
323
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
324
+ `#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2.
325
+ To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields
326
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
327
+ MSG
328
+ group_fields = group_values
329
+ end
279
330
 
280
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
281
- group_attrs = group_values
331
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
332
+ association = klass._reflect_on_association(group_fields.first)
333
+ associated = association && association.belongs_to? # only count belongs_to associations
334
+ group_fields = Array(association.foreign_key) if associated
335
+ end
336
+ group_fields = arel_columns(group_fields)
282
337
 
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)
338
+ group_aliases = group_fields.map { |field|
339
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
340
+ column_alias_for(field.to_s.downcase)
341
+ }
342
+ group_columns = group_aliases.zip(group_fields)
291
343
 
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
- }
344
+ column = aggregate_column(column_name)
345
+ column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
346
+ select_value = operation_over_aggregate_column(column, operation, distinct)
347
+ select_value.as(column_alias)
298
348
 
299
- group = group_fields
349
+ select_values = [select_value]
350
+ select_values += self.select_values unless having_clause.empty?
300
351
 
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
352
+ select_values.concat group_columns.map { |aliaz, field|
353
+ if field.respond_to?(:as)
354
+ field.as(aliaz)
355
+ else
356
+ "#{field} AS #{aliaz}"
357
+ end
358
+ }
306
359
 
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}"
320
- end
321
- }
360
+ relation = except(:group).distinct!(false)
361
+ relation.group_values = group_fields
362
+ relation.select_values = select_values
322
363
 
323
- relation = except(:group)
324
- relation.group_values = group
325
- relation.select_values = select_values
364
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
326
365
 
327
- calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
366
+ if association
367
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
368
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
369
+ key_records = key_records.index_by(&:id)
370
+ end
328
371
 
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] }]
333
- end
372
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
373
+ types[aliaz] = type_for(col_name) do
374
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
375
+ end
376
+ end
334
377
 
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)
378
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
379
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
380
+ hash[col_name] = row[i]
339
381
  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
382
+ end
344
383
 
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
384
+ type = nil
385
+ hash_rows.each_with_object({}) do |row, result|
386
+ key = group_aliases.map { |aliaz| row[aliaz] }
387
+ key = key.first if key.size == 1
388
+ key = key_records[key] if associated
389
+
390
+ result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
391
+ unless type
392
+ type = column.try(:type_caster) ||
393
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
394
+ type = type.subtype if Enum::EnumType === type
395
+ end
396
+ type.deserialize(value)
397
+ end
398
+ end
399
+ end
349
400
 
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}"
401
+ # Converts the given field to the value that the database adapter returns as
402
+ # a usable column name:
403
+ #
404
+ # column_alias_for("users.id") # => "users_id"
405
+ # column_alias_for("sum(id)") # => "sum_id"
406
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
407
+ # column_alias_for("count(*)") # => "count_all"
408
+ def column_alias_for(field)
409
+ column_alias = +field
410
+ column_alias.gsub!(/\*/, "all")
411
+ column_alias.gsub!(/\W+/, " ")
412
+ column_alias.strip!
413
+ column_alias.gsub!(/ +/, "_")
414
+
415
+ connection.table_alias_for(column_alias)
361
416
  end
362
417
 
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!(/ +/, '_')
418
+ def type_for(field, &block)
419
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
420
+ @klass.type_for_attribute(field_name, &block)
421
+ end
368
422
 
369
- @klass.connection.table_alias_for(table_name)
370
- end
423
+ def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
424
+ each_join_dependencies(join_dependencies) do |join|
425
+ type = join.base_klass.attribute_types.fetch(name, nil)
426
+ return type if type
427
+ end
428
+ nil
429
+ end
371
430
 
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
431
+ def type_cast_pluck_values(result, columns)
432
+ cast_types = if result.columns.size != columns.size
433
+ klass.attribute_types
434
+ else
435
+ join_dependencies = nil
436
+ columns.map.with_index do |column, i|
437
+ column.try(:type_caster) ||
438
+ klass.attribute_types.fetch(name = result.columns[i]) do
439
+ join_dependencies ||= build_join_dependencies
440
+ lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
441
+ result.column_types[name] || Type.default_value
442
+ end
443
+ end
444
+ end
445
+ result.cast_values(cast_types)
446
+ end
376
447
 
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)
448
+ def type_cast_calculated_value(value, operation)
449
+ case operation
450
+ when "count"
451
+ value.to_i
452
+ when "sum"
453
+ yield value || 0
454
+ when "average"
455
+ value&.respond_to?(:to_d) ? value.to_d : value
456
+ else # "minimum", "maximum"
457
+ yield value
458
+ end
383
459
  end
384
- end
385
460
 
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
461
+ def select_for_count
462
+ if select_values.present?
463
+ return select_values.first if select_values.one?
464
+ select_values.join(", ")
465
+ else
466
+ :all
467
+ end
392
468
  end
393
- end
394
469
 
395
- def build_count_subquery(relation, column_name, distinct)
396
- column_alias = Arel.sql('count_column')
397
- subquery_alias = Arel.sql('subquery_for_count')
470
+ def build_count_subquery(relation, column_name, distinct)
471
+ if column_name == :all
472
+ column_alias = Arel.star
473
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
474
+ else
475
+ column_alias = Arel.sql("count_column")
476
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
477
+ end
398
478
 
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)
479
+ subquery_alias = Arel.sql("subquery_for_count")
480
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
403
481
 
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
482
+ relation.build_subquery(subquery_alias, select_value)
483
+ end
409
484
  end
410
485
  end