activerecord 4.2.0 → 6.1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1221 -796
  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 +267 -249
  8. data/lib/active_record/association_relation.rb +45 -7
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +172 -67
  11. data/lib/active_record/associations/association_scope.rb +105 -129
  12. data/lib/active_record/associations/belongs_to_association.rb +85 -59
  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 -33
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
  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 +168 -279
  22. data/lib/active_record/associations/collection_proxy.rb +263 -155
  23. data/lib/active_record/associations/foreign_association.rb +33 -0
  24. data/lib/active_record/associations/has_many_association.rb +57 -84
  25. data/lib/active_record/associations/has_many_through_association.rb +70 -82
  26. data/lib/active_record/associations/has_one_association.rb +74 -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 -73
  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 +175 -164
  32. data/lib/active_record/associations/preloader/association.rb +107 -112
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +99 -96
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +49 -24
  37. data/lib/active_record/associations.rb +1845 -1597
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -138
  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 +59 -36
  46. data/lib/active_record/attribute_methods/write.rb +25 -56
  47. data/lib/active_record/attribute_methods.rb +153 -162
  48. data/lib/active_record/attributes.rb +234 -70
  49. data/lib/active_record/autosave_association.rb +157 -69
  50. data/lib/active_record/base.rb +49 -50
  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 +46 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
  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 +196 -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 +271 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
  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 +78 -161
  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 -57
  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 +5 -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 +17 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
  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 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  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 +171 -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 +499 -293
  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 +595 -382
  119. data/lib/active_record/connection_adapters/schema_cache.rb +191 -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 +146 -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 -389
  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 +488 -243
  133. data/lib/active_record/counter_cache.rb +71 -50
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +273 -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 +212 -94
  143. data/lib/active_record/errors.rb +225 -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 +11 -6
  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 +273 -496
  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 +64 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +52 -0
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +103 -95
  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 +298 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +685 -309
  170. data/lib/active_record/model_schema.rb +420 -113
  171. data/lib/active_record/nested_attributes.rb +265 -216
  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 +574 -135
  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 +175 -54
  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 +533 -216
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +485 -310
  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 +326 -244
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +318 -256
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +99 -84
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
  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 +139 -96
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -409
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +23 -21
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -342
  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 -26
  209. data/lib/active_record/scoping/default.rb +96 -82
  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 +133 -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 +366 -129
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
  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 +291 -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 +181 -152
  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 -41
  231. data/lib/active_record/type/date_time.rb +4 -38
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
  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 +33 -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 +65 -48
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +44 -28
  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 -10
  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.rb +7 -5
  339. metadata +175 -65
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  347. data/lib/active_record/attribute.rb +0 -149
  348. data/lib/active_record/attribute_decorators.rb +0 -66
  349. data/lib/active_record/attribute_set/builder.rb +0 -86
  350. data/lib/active_record/attribute_set.rb +0 -77
  351. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  352. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  353. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  354. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  355. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  356. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  357. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  358. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  359. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  360. data/lib/active_record/type/big_integer.rb +0 -13
  361. data/lib/active_record/type/binary.rb +0 -50
  362. data/lib/active_record/type/boolean.rb +0 -30
  363. data/lib/active_record/type/decimal.rb +0 -40
  364. data/lib/active_record/type/decorator.rb +0 -14
  365. data/lib/active_record/type/float.rb +0 -19
  366. data/lib/active_record/type/integer.rb +0 -55
  367. data/lib/active_record/type/mutable.rb +0 -16
  368. data/lib/active_record/type/numeric.rb +0 -36
  369. data/lib/active_record/type/string.rb +0 -36
  370. data/lib/active_record/type/time_value.rb +0 -38
  371. data/lib/active_record/type/value.rb +0 -101
  372. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  373. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  374. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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,121 +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
- # TODO: Remove options argument as soon we remove support to
40
- # activerecord-deprecated_finders.
41
- column_name, options = nil, column_name if column_name.is_a?(Hash)
42
- calculate(:count, column_name, options)
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
48
+
49
+ super()
50
+ else
51
+ calculate(:count, column_name)
52
+ end
43
53
  end
44
54
 
45
55
  # Calculates the average value on a given column. Returns +nil+ if there's
46
- # no row. See +calculate+ for examples with options.
56
+ # no row. See #calculate for examples with options.
47
57
  #
48
58
  # Person.average(:age) # => 35.8
49
- def average(column_name, options = {})
50
- # TODO: Remove options argument as soon we remove support to
51
- # activerecord-deprecated_finders.
52
- calculate(:average, column_name, options)
59
+ def average(column_name)
60
+ calculate(:average, column_name)
53
61
  end
54
62
 
55
63
  # Calculates the minimum value on a given column. The value is returned
56
64
  # with the same data type of the column, or +nil+ if there's no row. See
57
- # +calculate+ for examples with options.
65
+ # #calculate for examples with options.
58
66
  #
59
67
  # Person.minimum(:age) # => 7
60
- def minimum(column_name, options = {})
61
- # TODO: Remove options argument as soon we remove support to
62
- # activerecord-deprecated_finders.
63
- calculate(:minimum, column_name, options)
68
+ def minimum(column_name)
69
+ calculate(:minimum, column_name)
64
70
  end
65
71
 
66
72
  # Calculates the maximum value on a given column. The value is returned
67
73
  # with the same data type of the column, or +nil+ if there's no row. See
68
- # +calculate+ for examples with options.
74
+ # #calculate for examples with options.
69
75
  #
70
76
  # Person.maximum(:age) # => 93
71
- def maximum(column_name, options = {})
72
- # TODO: Remove options argument as soon we remove support to
73
- # activerecord-deprecated_finders.
74
- calculate(:maximum, column_name, options)
77
+ def maximum(column_name)
78
+ calculate(:maximum, column_name)
75
79
  end
76
80
 
77
81
  # Calculates the sum of values on a given column. The value is returned
78
- # with the same data type of the column, 0 if there's no row. See
79
- # +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.
80
84
  #
81
85
  # Person.sum(:age) # => 4562
82
- def sum(*args)
83
- 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
84
96
  end
85
97
 
86
- # This calculates aggregate values in the given column. Methods for count, sum, average,
87
- # 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.
88
100
  #
89
- # 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...
90
103
  #
91
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
92
- # 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)
93
106
  #
94
- # * Grouped values: This returns an ordered hash of the values and groups them. It
95
- # takes either a column name, or the name of a belongs_to association.
107
+ # Person.sum("2 * age")
96
108
  #
97
- # values = Person.group('last_name').maximum(:age)
98
- # puts values["Drake"]
99
- # # => 43
109
+ # There are two basic forms of output:
100
110
  #
101
- # drake = Family.find_by(last_name: 'Drake')
102
- # values = Person.group(:family).maximum(:age) # Person belongs_to :family
103
- # puts values[drake]
104
- # # => 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.
105
113
  #
106
- # values.each do |family, max_age|
107
- # ...
108
- # 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.
109
116
  #
110
- # Person.calculate(:count, :all) # The same as Person.count
111
- # Person.average(:age) # SELECT AVG(age) FROM people...
117
+ # values = Person.group('last_name').maximum(:age)
118
+ # puts values["Drake"]
119
+ # # => 43
112
120
  #
113
- # # Selects the minimum age for any family without any minors
114
- # 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
115
125
  #
116
- # Person.sum("2 * age")
117
- def calculate(operation, column_name, options = {})
118
- # TODO: Remove options argument as soon we remove support to
119
- # activerecord-deprecated_finders.
120
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
121
- column_name = attribute_alias(column_name)
122
- end
123
-
126
+ # values.each do |family, max_age|
127
+ # ...
128
+ # end
129
+ def calculate(operation, column_name)
124
130
  if has_include?(column_name)
125
- 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)
126
143
  else
127
- perform_calculation(operation, column_name, options)
144
+ perform_calculation(operation, column_name)
128
145
  end
129
146
  end
130
147
 
131
- # 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
132
149
  # loading a bunch of records just to grab the attributes you want.
133
150
  #
134
151
  # Person.pluck(:name)
@@ -137,19 +154,19 @@ module ActiveRecord
137
154
  #
138
155
  # Person.all.map(&:name)
139
156
  #
140
- # 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
141
158
  # the plucked column names, if they can be deduced. Plucking an SQL fragment
142
159
  # returns String values by default.
143
160
  #
144
- # Person.pluck(:id)
145
- # # SELECT people.id FROM people
146
- # # => [1, 2, 3]
161
+ # Person.pluck(:name)
162
+ # # SELECT people.name FROM people
163
+ # # => ['David', 'Jeremy', 'Jose']
147
164
  #
148
165
  # Person.pluck(:id, :name)
149
166
  # # SELECT people.id, people.name FROM people
150
167
  # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
151
168
  #
152
- # Person.pluck('DISTINCT role')
169
+ # Person.distinct.pluck(:role)
153
170
  # # SELECT DISTINCT role FROM people
154
171
  # # => ['admin', 'member', 'guest']
155
172
  #
@@ -157,31 +174,58 @@ module ActiveRecord
157
174
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
158
175
  # # => [2, 3]
159
176
  #
160
- # Person.pluck('DATEDIFF(updated_at, created_at)')
177
+ # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
161
178
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
162
179
  # # => ['0', '27761', '173']
163
180
  #
181
+ # See also #ids.
182
+ #
164
183
  def pluck(*column_names)
165
- column_names.map! do |column_name|
166
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
167
- attribute_alias(column_name)
168
- else
169
- column_name.to_s
170
- end
184
+ if loaded? && all_attributes?(column_names)
185
+ return records.pluck(*column_names)
171
186
  end
172
187
 
173
188
  if has_include?(column_names.first)
174
- construct_relation_for_association_calculations.pluck(*column_names)
189
+ relation = apply_join_dependency
190
+ relation.pluck(*column_names)
175
191
  else
192
+ klass.disallow_raw_sql!(column_names)
193
+ columns = arel_columns(column_names)
176
194
  relation = spawn
177
- relation.select_values = column_names.map { |cn|
178
- columns_hash.key?(cn) ? arel_table[cn] : cn
179
- }
180
- result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
181
- 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)
182
204
  end
183
205
  end
184
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)
224
+ end
225
+
226
+ limit(1).pluck(*column_names).first
227
+ end
228
+
185
229
  # Pluck all the ID's for the relation using the table's primary key
186
230
  #
187
231
  # Person.ids # SELECT people.id FROM people
@@ -191,213 +235,251 @@ module ActiveRecord
191
235
  end
192
236
 
193
237
  private
238
+ def all_attributes?(column_names)
239
+ (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
240
+ end
194
241
 
195
- def has_include?(column_name)
196
- eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
197
- end
198
-
199
- def perform_calculation(operation, column_name, options = {})
200
- # TODO: Remove options argument as soon we remove support to
201
- # activerecord-deprecated_finders.
202
- operation = operation.to_s.downcase
203
-
204
- # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
205
- distinct = self.distinct_value
206
-
207
- if operation == "count"
208
- column_name ||= select_for_count
242
+ def has_include?(column_name)
243
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
244
+ end
209
245
 
210
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
211
- 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
212
264
  end
213
265
 
214
- column_name = primary_key if column_name == :all && distinct
215
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
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
216
271
  end
217
272
 
218
- if group_values.any?
219
- execute_grouped_calculation(operation, column_name, distinct)
220
- else
221
- execute_simple_calculation(operation, column_name, distinct)
273
+ def distinct_select?(column_name)
274
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
222
275
  end
223
- end
224
276
 
225
- def aggregate_column(column_name)
226
- if @klass.column_names.include?(column_name.to_s)
227
- Arel::Attribute.new(@klass.unscoped.table, column_name)
228
- else
229
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
230
- end
231
- end
277
+ def aggregate_column(column_name)
278
+ return column_name if Arel::Expressions === column_name
232
279
 
233
- def operation_over_aggregate_column(column, operation, distinct)
234
- operation == 'count' ? column.count(distinct) : column.send(operation)
235
- end
236
-
237
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
238
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
239
- relation = unscope(:order)
280
+ arel_column(column_name.to_s) do |name|
281
+ Arel.sql(column_name == :all ? "*" : name)
282
+ end
283
+ end
240
284
 
241
- 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
242
288
 
243
- 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
244
293
 
245
- if operation == "count" && (relation.limit_value || relation.offset_value)
246
- # Shortcut when limit is zero.
247
- 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)
248
298
 
249
- query_builder = build_count_subquery(relation, column_name, distinct)
250
- bind_values = query_builder.bind_values + relation.bind_values
251
- else
252
- 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
253
302
 
254
- select_value = operation_over_aggregate_column(column, operation, distinct)
303
+ relation.select_values = [select_value]
255
304
 
256
- column_alias = select_value.alias
257
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
258
- relation.select_values = [select_value]
305
+ query_builder = relation.arel
306
+ end
259
307
 
260
- query_builder = relation.arel
261
- bind_values = query_builder.bind_values + relation.bind_values
262
- end
308
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
263
309
 
264
- result = @klass.connection.select_all(query_builder, nil, bind_values)
265
- row = result.first
266
- value = row && row.values.first
267
- column = result.column_types.fetch(column_alias) do
268
- 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
269
316
  end
270
317
 
271
- type_cast_calculated_value(value, column, operation)
272
- 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 7.0.
325
+ To migrate to Rails 7.0'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
273
330
 
274
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
275
- 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)
276
337
 
277
- if group_attrs.first.respond_to?(:to_sym)
278
- association = @klass._reflect_on_association(group_attrs.first)
279
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
280
- group_fields = Array(associated ? association.foreign_key : group_attrs)
281
- else
282
- group_fields = group_attrs
283
- end
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)
284
343
 
285
- group_aliases = group_fields.map { |field|
286
- column_alias_for(field)
287
- }
288
- group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
289
- [aliaz, field]
290
- }
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)
291
348
 
292
- group = group_fields
349
+ select_values = [select_value]
350
+ select_values += self.select_values unless having_clause.empty?
293
351
 
294
- if operation == 'count' && column_name == :all
295
- aggregate_alias = 'count_all'
296
- else
297
- aggregate_alias = column_alias_for([operation, column_name].join(' '))
298
- 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
+ }
299
359
 
300
- select_values = [
301
- operation_over_aggregate_column(
302
- aggregate_column(column_name),
303
- operation,
304
- distinct).as(aggregate_alias)
305
- ]
306
- select_values += select_values unless having_values.empty?
307
-
308
- select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
309
- if field.respond_to?(:as)
310
- field.as(aliaz)
311
- else
312
- "#{field} AS #{aliaz}"
313
- end
314
- }
360
+ relation = except(:group).distinct!(false)
361
+ relation.group_values = group_fields
362
+ relation.select_values = select_values
315
363
 
316
- relation = except(:group)
317
- relation.group_values = group
318
- relation.select_values = select_values
364
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
319
365
 
320
- 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
321
371
 
322
- if association
323
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
324
- key_records = association.klass.base_class.find(key_ids)
325
- key_records = Hash[key_records.map { |r| [r.id, r] }]
326
- 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
327
377
 
328
- Hash[calculated_data.map do |row|
329
- key = group_columns.map { |aliaz, col_name|
330
- column = calculated_data.column_types.fetch(aliaz) do
331
- 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]
332
381
  end
333
- type_cast_calculated_value(row[aliaz], column)
334
- }
335
- key = key.first if key.size == 1
336
- key = key_records[key] if associated
382
+ end
337
383
 
338
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
339
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
340
- end]
341
- 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
342
400
 
343
- # Converts the given keys to the value that the database adapter returns as
344
- # a usable column name:
345
- #
346
- # column_alias_for("users.id") # => "users_id"
347
- # column_alias_for("sum(id)") # => "sum_id"
348
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
349
- # column_alias_for("count(*)") # => "count_all"
350
- # column_alias_for("count", "id") # => "count_id"
351
- def column_alias_for(keys)
352
- if keys.respond_to? :name
353
- 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)
354
416
  end
355
417
 
356
- table_name = keys.to_s.downcase
357
- table_name.gsub!(/\*/, 'all')
358
- table_name.gsub!(/\W+/, ' ')
359
- table_name.strip!
360
- 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
361
422
 
362
- @klass.connection.table_alias_for(table_name)
363
- 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
364
430
 
365
- def type_for(field)
366
- field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
367
- @klass.type_for_attribute(field_name)
368
- 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
369
447
 
370
- def type_cast_calculated_value(value, type, operation = nil)
371
- case operation
372
- when 'count' then value.to_i
373
- when 'sum' then type.type_cast_from_database(value || 0)
374
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
375
- 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
376
459
  end
377
- end
378
460
 
379
- # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
380
- def select_for_count
381
- if select_values.present?
382
- select_values.join(", ")
383
- else
384
- :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
385
468
  end
386
- end
387
469
 
388
- def build_count_subquery(relation, column_name, distinct)
389
- column_alias = Arel.sql('count_column')
390
- 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
391
478
 
392
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
393
- relation.select_values = [aliased_column]
394
- arel = relation.arel
395
- subquery = arel.as(subquery_alias)
479
+ subquery_alias = Arel.sql("subquery_for_count")
480
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
396
481
 
397
- sm = Arel::SelectManager.new relation.engine
398
- sm.bind_values = arel.bind_values
399
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
400
- sm.project(select_value).from(subquery)
401
- end
482
+ relation.build_subquery(subquery_alias, select_value)
483
+ end
402
484
  end
403
485
  end