activerecord 3.2.6 → 6.0.0

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

Potentially problematic release.


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

Files changed (371) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +611 -6417
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +44 -47
  5. data/examples/performance.rb +79 -71
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +268 -238
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +173 -81
  11. data/lib/active_record/associations/association_scope.rb +124 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -38
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +11 -9
  14. data/lib/active_record/associations/builder/association.rb +113 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +105 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +53 -56
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +98 -41
  18. data/lib/active_record/associations/builder/has_many.rb +11 -63
  19. data/lib/active_record/associations/builder/has_one.rb +47 -45
  20. data/lib/active_record/associations/builder/singular_association.rb +30 -18
  21. data/lib/active_record/associations/collection_association.rb +217 -295
  22. data/lib/active_record/associations/collection_proxy.rb +1074 -77
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +78 -50
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -61
  26. data/lib/active_record/associations/has_one_association.rb +75 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +45 -119
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +208 -164
  32. data/lib/active_record/associations/preloader/association.rb +93 -87
  33. data/lib/active_record/associations/preloader/through_association.rb +87 -38
  34. data/lib/active_record/associations/preloader.rb +134 -110
  35. data/lib/active_record/associations/singular_association.rb +19 -24
  36. data/lib/active_record/associations/through_association.rb +61 -27
  37. data/lib/active_record/associations.rb +1766 -1505
  38. data/lib/active_record/attribute_assignment.rb +57 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +58 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +187 -67
  42. data/lib/active_record/attribute_methods/primary_key.rb +100 -78
  43. data/lib/active_record/attribute_methods/query.rb +10 -8
  44. data/lib/active_record/attribute_methods/read.rb +29 -118
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -72
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -42
  47. data/lib/active_record/attribute_methods/write.rb +36 -44
  48. data/lib/active_record/attribute_methods.rb +306 -161
  49. data/lib/active_record/attributes.rb +279 -0
  50. data/lib/active_record/autosave_association.rb +324 -238
  51. data/lib/active_record/base.rb +114 -507
  52. data/lib/active_record/callbacks.rb +147 -83
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +962 -279
  56. data/lib/active_record/connection_adapters/abstract/database_limits.rb +32 -5
  57. data/lib/active_record/connection_adapters/abstract/database_statements.rb +331 -209
  58. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -23
  59. data/lib/active_record/connection_adapters/abstract/quoting.rb +201 -65
  60. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +510 -289
  63. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1182 -313
  65. data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
  66. data/lib/active_record/connection_adapters/abstract_adapter.rb +585 -120
  67. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +610 -463
  68. data/lib/active_record/connection_adapters/column.rb +58 -233
  69. data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
  70. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  73. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  74. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  79. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +75 -207
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +695 -1052
  116. data/lib/active_record/connection_adapters/schema_cache.rb +115 -24
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +528 -26
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +267 -0
  128. data/lib/active_record/core.rb +599 -0
  129. data/lib/active_record/counter_cache.rb +177 -103
  130. data/lib/active_record/database_configurations/database_config.rb +37 -0
  131. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  132. data/lib/active_record/database_configurations/url_config.rb +79 -0
  133. data/lib/active_record/database_configurations.rb +233 -0
  134. data/lib/active_record/define_callbacks.rb +22 -0
  135. data/lib/active_record/dynamic_matchers.rb +107 -64
  136. data/lib/active_record/enum.rb +274 -0
  137. data/lib/active_record/errors.rb +254 -61
  138. data/lib/active_record/explain.rb +35 -70
  139. data/lib/active_record/explain_registry.rb +32 -0
  140. data/lib/active_record/explain_subscriber.rb +18 -8
  141. data/lib/active_record/fixture_set/file.rb +82 -0
  142. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  143. data/lib/active_record/fixture_set/render_context.rb +17 -0
  144. data/lib/active_record/fixture_set/table_row.rb +153 -0
  145. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  146. data/lib/active_record/fixtures.rb +291 -475
  147. data/lib/active_record/gem_version.rb +17 -0
  148. data/lib/active_record/inheritance.rb +219 -100
  149. data/lib/active_record/insert_all.rb +179 -0
  150. data/lib/active_record/integration.rb +175 -17
  151. data/lib/active_record/internal_metadata.rb +53 -0
  152. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  153. data/lib/active_record/locale/en.yml +9 -1
  154. data/lib/active_record/locking/optimistic.rb +106 -92
  155. data/lib/active_record/locking/pessimistic.rb +23 -11
  156. data/lib/active_record/log_subscriber.rb +80 -30
  157. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  158. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  159. data/lib/active_record/middleware/database_selector.rb +75 -0
  160. data/lib/active_record/migration/command_recorder.rb +235 -56
  161. data/lib/active_record/migration/compatibility.rb +244 -0
  162. data/lib/active_record/migration/join_table.rb +17 -0
  163. data/lib/active_record/migration.rb +917 -301
  164. data/lib/active_record/model_schema.rb +351 -175
  165. data/lib/active_record/nested_attributes.rb +366 -235
  166. data/lib/active_record/no_touching.rb +65 -0
  167. data/lib/active_record/null_relation.rb +68 -0
  168. data/lib/active_record/persistence.rb +761 -166
  169. data/lib/active_record/query_cache.rb +22 -44
  170. data/lib/active_record/querying.rb +55 -31
  171. data/lib/active_record/railtie.rb +185 -47
  172. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  173. data/lib/active_record/railties/console_sandbox.rb +5 -4
  174. data/lib/active_record/railties/controller_runtime.rb +35 -33
  175. data/lib/active_record/railties/databases.rake +366 -463
  176. data/lib/active_record/readonly_attributes.rb +4 -6
  177. data/lib/active_record/reflection.rb +736 -228
  178. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  179. data/lib/active_record/relation/batches.rb +252 -52
  180. data/lib/active_record/relation/calculations.rb +340 -270
  181. data/lib/active_record/relation/delegation.rb +117 -36
  182. data/lib/active_record/relation/finder_methods.rb +439 -286
  183. data/lib/active_record/relation/from_clause.rb +26 -0
  184. data/lib/active_record/relation/merger.rb +184 -0
  185. data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
  186. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  187. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  188. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  189. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  190. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  191. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder.rb +131 -39
  193. data/lib/active_record/relation/query_attribute.rb +50 -0
  194. data/lib/active_record/relation/query_methods.rb +1163 -221
  195. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  196. data/lib/active_record/relation/spawn_methods.rb +49 -120
  197. data/lib/active_record/relation/where_clause.rb +190 -0
  198. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  199. data/lib/active_record/relation.rb +671 -349
  200. data/lib/active_record/result.rb +149 -15
  201. data/lib/active_record/runtime_registry.rb +24 -0
  202. data/lib/active_record/sanitization.rb +153 -133
  203. data/lib/active_record/schema.rb +22 -19
  204. data/lib/active_record/schema_dumper.rb +178 -112
  205. data/lib/active_record/schema_migration.rb +60 -0
  206. data/lib/active_record/scoping/default.rb +107 -98
  207. data/lib/active_record/scoping/named.rb +130 -115
  208. data/lib/active_record/scoping.rb +77 -123
  209. data/lib/active_record/secure_token.rb +40 -0
  210. data/lib/active_record/serialization.rb +10 -6
  211. data/lib/active_record/statement_cache.rb +148 -0
  212. data/lib/active_record/store.rb +256 -16
  213. data/lib/active_record/suppressor.rb +61 -0
  214. data/lib/active_record/table_metadata.rb +75 -0
  215. data/lib/active_record/tasks/database_tasks.rb +506 -0
  216. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  217. data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
  218. data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
  219. data/lib/active_record/test_databases.rb +23 -0
  220. data/lib/active_record/test_fixtures.rb +224 -0
  221. data/lib/active_record/timestamp.rb +93 -39
  222. data/lib/active_record/touch_later.rb +66 -0
  223. data/lib/active_record/transactions.rb +260 -129
  224. data/lib/active_record/translation.rb +3 -1
  225. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  226. data/lib/active_record/type/date.rb +9 -0
  227. data/lib/active_record/type/date_time.rb +9 -0
  228. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  229. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  230. data/lib/active_record/type/internal/timezone.rb +17 -0
  231. data/lib/active_record/type/json.rb +30 -0
  232. data/lib/active_record/type/serialized.rb +71 -0
  233. data/lib/active_record/type/text.rb +11 -0
  234. data/lib/active_record/type/time.rb +21 -0
  235. data/lib/active_record/type/type_map.rb +62 -0
  236. data/lib/active_record/type/unsigned_integer.rb +17 -0
  237. data/lib/active_record/type.rb +78 -0
  238. data/lib/active_record/type_caster/connection.rb +34 -0
  239. data/lib/active_record/type_caster/map.rb +20 -0
  240. data/lib/active_record/type_caster.rb +9 -0
  241. data/lib/active_record/validations/absence.rb +25 -0
  242. data/lib/active_record/validations/associated.rb +35 -18
  243. data/lib/active_record/validations/length.rb +26 -0
  244. data/lib/active_record/validations/presence.rb +68 -0
  245. data/lib/active_record/validations/uniqueness.rb +123 -77
  246. data/lib/active_record/validations.rb +54 -43
  247. data/lib/active_record/version.rb +7 -7
  248. data/lib/active_record.rb +97 -49
  249. data/lib/arel/alias_predication.rb +9 -0
  250. data/lib/arel/attributes/attribute.rb +37 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/collectors/bind.rb +24 -0
  253. data/lib/arel/collectors/composite.rb +31 -0
  254. data/lib/arel/collectors/plain_string.rb +20 -0
  255. data/lib/arel/collectors/sql_string.rb +20 -0
  256. data/lib/arel/collectors/substitute_binds.rb +28 -0
  257. data/lib/arel/crud.rb +42 -0
  258. data/lib/arel/delete_manager.rb +18 -0
  259. data/lib/arel/errors.rb +9 -0
  260. data/lib/arel/expressions.rb +29 -0
  261. data/lib/arel/factory_methods.rb +49 -0
  262. data/lib/arel/insert_manager.rb +49 -0
  263. data/lib/arel/math.rb +45 -0
  264. data/lib/arel/nodes/and.rb +32 -0
  265. data/lib/arel/nodes/ascending.rb +23 -0
  266. data/lib/arel/nodes/binary.rb +52 -0
  267. data/lib/arel/nodes/bind_param.rb +36 -0
  268. data/lib/arel/nodes/case.rb +55 -0
  269. data/lib/arel/nodes/casted.rb +50 -0
  270. data/lib/arel/nodes/comment.rb +29 -0
  271. data/lib/arel/nodes/count.rb +12 -0
  272. data/lib/arel/nodes/delete_statement.rb +45 -0
  273. data/lib/arel/nodes/descending.rb +23 -0
  274. data/lib/arel/nodes/equality.rb +18 -0
  275. data/lib/arel/nodes/extract.rb +24 -0
  276. data/lib/arel/nodes/false.rb +16 -0
  277. data/lib/arel/nodes/full_outer_join.rb +8 -0
  278. data/lib/arel/nodes/function.rb +44 -0
  279. data/lib/arel/nodes/grouping.rb +8 -0
  280. data/lib/arel/nodes/in.rb +8 -0
  281. data/lib/arel/nodes/infix_operation.rb +80 -0
  282. data/lib/arel/nodes/inner_join.rb +8 -0
  283. data/lib/arel/nodes/insert_statement.rb +37 -0
  284. data/lib/arel/nodes/join_source.rb +20 -0
  285. data/lib/arel/nodes/matches.rb +18 -0
  286. data/lib/arel/nodes/named_function.rb +23 -0
  287. data/lib/arel/nodes/node.rb +50 -0
  288. data/lib/arel/nodes/node_expression.rb +13 -0
  289. data/lib/arel/nodes/outer_join.rb +8 -0
  290. data/lib/arel/nodes/over.rb +15 -0
  291. data/lib/arel/nodes/regexp.rb +16 -0
  292. data/lib/arel/nodes/right_outer_join.rb +8 -0
  293. data/lib/arel/nodes/select_core.rb +67 -0
  294. data/lib/arel/nodes/select_statement.rb +41 -0
  295. data/lib/arel/nodes/sql_literal.rb +16 -0
  296. data/lib/arel/nodes/string_join.rb +11 -0
  297. data/lib/arel/nodes/table_alias.rb +27 -0
  298. data/lib/arel/nodes/terminal.rb +16 -0
  299. data/lib/arel/nodes/true.rb +16 -0
  300. data/lib/arel/nodes/unary.rb +45 -0
  301. data/lib/arel/nodes/unary_operation.rb +20 -0
  302. data/lib/arel/nodes/unqualified_column.rb +22 -0
  303. data/lib/arel/nodes/update_statement.rb +41 -0
  304. data/lib/arel/nodes/values_list.rb +9 -0
  305. data/lib/arel/nodes/window.rb +126 -0
  306. data/lib/arel/nodes/with.rb +11 -0
  307. data/lib/arel/nodes.rb +68 -0
  308. data/lib/arel/order_predications.rb +13 -0
  309. data/lib/arel/predications.rb +257 -0
  310. data/lib/arel/select_manager.rb +271 -0
  311. data/lib/arel/table.rb +110 -0
  312. data/lib/arel/tree_manager.rb +72 -0
  313. data/lib/arel/update_manager.rb +34 -0
  314. data/lib/arel/visitors/depth_first.rb +204 -0
  315. data/lib/arel/visitors/dot.rb +297 -0
  316. data/lib/arel/visitors/ibm_db.rb +34 -0
  317. data/lib/arel/visitors/informix.rb +62 -0
  318. data/lib/arel/visitors/mssql.rb +157 -0
  319. data/lib/arel/visitors/mysql.rb +83 -0
  320. data/lib/arel/visitors/oracle.rb +159 -0
  321. data/lib/arel/visitors/oracle12.rb +66 -0
  322. data/lib/arel/visitors/postgresql.rb +110 -0
  323. data/lib/arel/visitors/sqlite.rb +39 -0
  324. data/lib/arel/visitors/to_sql.rb +889 -0
  325. data/lib/arel/visitors/visitor.rb +46 -0
  326. data/lib/arel/visitors/where_sql.rb +23 -0
  327. data/lib/arel/visitors.rb +20 -0
  328. data/lib/arel/window_predications.rb +9 -0
  329. data/lib/arel.rb +51 -0
  330. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  331. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +59 -9
  333. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  334. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  335. data/lib/rails/generators/active_record/migration.rb +41 -8
  336. data/lib/rails/generators/active_record/model/model_generator.rb +24 -22
  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} +1 -1
  339. data/lib/rails/generators/active_record.rb +10 -16
  340. metadata +285 -149
  341. data/examples/associations.png +0 -0
  342. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  343. data/lib/active_record/associations/join_helper.rb +0 -55
  344. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  345. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  346. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  347. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  348. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  349. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  350. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  351. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  352. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  353. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -188
  354. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -426
  355. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -579
  356. data/lib/active_record/dynamic_finder_match.rb +0 -68
  357. data/lib/active_record/dynamic_scope_match.rb +0 -23
  358. data/lib/active_record/fixtures/file.rb +0 -65
  359. data/lib/active_record/identity_map.rb +0 -162
  360. data/lib/active_record/observer.rb +0 -121
  361. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  362. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  363. data/lib/active_record/session_store.rb +0 -358
  364. data/lib/active_record/test_case.rb +0 -73
  365. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  366. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  367. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  368. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  369. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  370. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  371. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,354 +1,424 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/object/try'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module ActiveRecord
5
4
  module Calculations
6
- # Count operates using three different approaches.
7
- #
8
- # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
9
- # * Count using column: By passing a column name to count, it will return a count of all the
10
- # rows for the model with supplied column present.
11
- # * Count using options will find the row count matched by the options used.
12
- #
13
- # The third approach, count using options, accepts an option hash as the only parameter. The options are:
14
- #
15
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
16
- # See conditions in the intro to ActiveRecord::Base.
17
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
18
- # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
19
- # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
20
- # will be returned read-only since they will have attributes that do not correspond to the table's columns.
21
- # Pass <tt>:readonly => false</tt> to override.
22
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
23
- # The symbols named refer to already defined associations. When using named associations, count
24
- # returns the number of DISTINCT items for the model you're counting.
25
- # See eager loading under Associations.
26
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
27
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
28
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
29
- # want to do a join but not include the joined columns.
30
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
31
- # SELECT COUNT(DISTINCT posts.id) ...
32
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
33
- # alternate table name (or even the name of a database view).
34
- #
35
- # Examples for counting all:
36
- # Person.count # returns the total count of all people
37
- #
38
- # Examples for counting by column:
39
- # Person.count(:age) # returns the total count of all people whose age is present in database
40
- #
41
- # Examples for count with options:
42
- # Person.count(:conditions => "age > 26")
43
- #
44
- # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
45
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
46
- #
47
- # # finds the number of rows matching the conditions and joins.
48
- # Person.count(:conditions => "age > 26 AND job.salary > 60000",
49
- # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
50
- #
51
- # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
52
- # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
53
- #
54
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
55
- # Use Person.count instead.
56
- def count(column_name = nil, options = {})
57
- column_name, options = nil, column_name if column_name.is_a?(Hash)
58
- calculate(:count, column_name, options)
5
+ # Count the records.
6
+ #
7
+ # Person.count
8
+ # # => the total count of all people
9
+ #
10
+ # Person.count(:age)
11
+ # # => returns the total count of all people whose age is present in database
12
+ #
13
+ # Person.count(:all)
14
+ # # => performs a COUNT(*) (:all is an alias for '*')
15
+ #
16
+ # Person.distinct.count(:age)
17
+ # # => counts the number of different age values
18
+ #
19
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
20
+ # it returns a Hash whose keys represent the aggregated column,
21
+ # and the values are the respective amounts:
22
+ #
23
+ # Person.group(:city).count
24
+ # # => { 'Rome' => 5, 'Paris' => 3 }
25
+ #
26
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
27
+ # keys are an array containing the individual values of each column and the value
28
+ # of each key would be the #count.
29
+ #
30
+ # Article.group(:status, :category).count
31
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
32
+ # ["published", "business"]=>0, ["published", "technology"]=>2}
33
+ #
34
+ # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
35
+ #
36
+ # Person.select(:age).count
37
+ # # => counts the number of different age values
38
+ #
39
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
40
+ # between databases. In invalid cases, an error from the database is thrown.
41
+ def count(column_name = nil)
42
+ if block_given?
43
+ unless column_name.nil?
44
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
45
+ end
46
+
47
+ super()
48
+ else
49
+ calculate(:count, column_name)
50
+ end
59
51
  end
60
52
 
61
53
  # Calculates the average value on a given column. Returns +nil+ if there's
62
- # no row. See +calculate+ for examples with options.
54
+ # no row. See #calculate for examples with options.
63
55
  #
64
- # Person.average('age') # => 35.8
65
- def average(column_name, options = {})
66
- calculate(:average, column_name, options)
56
+ # Person.average(:age) # => 35.8
57
+ def average(column_name)
58
+ calculate(:average, column_name)
67
59
  end
68
60
 
69
61
  # Calculates the minimum value on a given column. The value is returned
70
62
  # with the same data type of the column, or +nil+ if there's no row. See
71
- # +calculate+ for examples with options.
63
+ # #calculate for examples with options.
72
64
  #
73
- # Person.minimum('age') # => 7
74
- def minimum(column_name, options = {})
75
- calculate(:minimum, column_name, options)
65
+ # Person.minimum(:age) # => 7
66
+ def minimum(column_name)
67
+ calculate(:minimum, column_name)
76
68
  end
77
69
 
78
70
  # Calculates the maximum value on a given column. The value is returned
79
71
  # with the same data type of the column, or +nil+ if there's no row. See
80
- # +calculate+ for examples with options.
72
+ # #calculate for examples with options.
81
73
  #
82
- # Person.maximum('age') # => 93
83
- def maximum(column_name, options = {})
84
- calculate(:maximum, column_name, options)
74
+ # Person.maximum(:age) # => 93
75
+ def maximum(column_name)
76
+ calculate(:maximum, column_name)
85
77
  end
86
78
 
87
79
  # Calculates the sum of values on a given column. The value is returned
88
- # with the same data type of the column, 0 if there's no row. See
89
- # +calculate+ for examples with options.
80
+ # with the same data type of the column, +0+ if there's no row. See
81
+ # #calculate for examples with options.
90
82
  #
91
- # Person.sum('age') # => 4562
92
- def sum(*args)
83
+ # Person.sum(:age) # => 4562
84
+ def sum(column_name = nil)
93
85
  if block_given?
94
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
86
+ unless column_name.nil?
87
+ raise ArgumentError, "Column name argument is not supported when a block is passed."
88
+ end
89
+
90
+ super()
95
91
  else
96
- calculate(:sum, *args)
92
+ calculate(:sum, column_name)
97
93
  end
98
94
  end
99
95
 
100
- # This calculates aggregate values in the given column. Methods for count, sum, average,
101
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
102
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
96
+ # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
97
+ # #minimum, and #maximum have been added as shortcuts.
103
98
  #
104
- # There are two basic forms of output:
105
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
106
- # for AVG, and the given column's type for everything else.
107
- # * Grouped values: This returns an ordered hash of the values and groups them by the
108
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
109
- #
110
- # values = Person.maximum(:age, :group => 'last_name')
111
- # puts values["Drake"]
112
- # => 43
113
- #
114
- # drake = Family.find_by_last_name('Drake')
115
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
116
- # puts values[drake]
117
- # => 43
118
- #
119
- # values.each do |family, max_age|
120
- # ...
121
- # end
122
- #
123
- # Options:
124
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
125
- # See conditions in the intro to ActiveRecord::Base.
126
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
127
- # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
128
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
129
- # (Rarely needed).
130
- # The records will be returned read-only since they will have attributes that do not correspond to the
131
- # table's columns.
132
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
133
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
134
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
135
- # want to do a join, but not include the joined columns.
136
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
137
- # SELECT COUNT(DISTINCT posts.id) ...
138
- #
139
- # Examples:
140
99
  # Person.calculate(:count, :all) # The same as Person.count
141
100
  # Person.average(:age) # SELECT AVG(age) FROM people...
142
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
143
- # # everyone with a last name other than 'Drake'
144
101
  #
145
102
  # # Selects the minimum age for any family without any minors
146
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
103
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
147
104
  #
148
105
  # Person.sum("2 * age")
149
- def calculate(operation, column_name, options = {})
150
- if options.except(:distinct).present?
151
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
152
- else
153
- relation = with_default_scope
154
-
155
- if relation.equal?(self)
156
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
157
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
158
- else
159
- perform_calculation(operation, column_name, options)
106
+ #
107
+ # There are two basic forms of output:
108
+ #
109
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
110
+ # for AVG, and the given column's type for everything else.
111
+ #
112
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
113
+ # takes either a column name, or the name of a belongs_to association.
114
+ #
115
+ # values = Person.group('last_name').maximum(:age)
116
+ # puts values["Drake"]
117
+ # # => 43
118
+ #
119
+ # drake = Family.find_by(last_name: 'Drake')
120
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
121
+ # puts values[drake]
122
+ # # => 43
123
+ #
124
+ # values.each do |family, max_age|
125
+ # ...
126
+ # end
127
+ def calculate(operation, column_name)
128
+ if has_include?(column_name)
129
+ relation = apply_join_dependency
130
+
131
+ if operation.to_s.downcase == "count"
132
+ unless distinct_value || distinct_select?(column_name || select_for_count)
133
+ relation.distinct!
134
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
160
135
  end
161
- else
162
- relation.calculate(operation, column_name, options)
136
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
137
+ relation.order_values = []
163
138
  end
139
+
140
+ relation.calculate(operation, column_name)
141
+ else
142
+ perform_calculation(operation, column_name)
164
143
  end
165
- rescue ThrowResult
166
- 0
167
144
  end
168
145
 
169
- # This method is designed to perform select by a single column as direct SQL query
170
- # Returns <tt>Array</tt> with values of the specified column name
171
- # The values has same data type as column.
146
+ # Use #pluck as a shortcut to select one or more attributes without
147
+ # loading a bunch of records just to grab the attributes you want.
148
+ #
149
+ # Person.pluck(:name)
150
+ #
151
+ # instead of
152
+ #
153
+ # Person.all.map(&:name)
154
+ #
155
+ # Pluck returns an Array of attribute values type-casted to match
156
+ # the plucked column names, if they can be deduced. Plucking an SQL fragment
157
+ # returns String values by default.
158
+ #
159
+ # Person.pluck(:name)
160
+ # # SELECT people.name FROM people
161
+ # # => ['David', 'Jeremy', 'Jose']
162
+ #
163
+ # Person.pluck(:id, :name)
164
+ # # SELECT people.id, people.name FROM people
165
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
172
166
  #
173
- # Examples:
167
+ # Person.distinct.pluck(:role)
168
+ # # SELECT DISTINCT role FROM people
169
+ # # => ['admin', 'member', 'guest']
174
170
  #
175
- # Person.pluck(:id) # SELECT people.id FROM people
176
- # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
177
- # Person.where(:confirmed => true).limit(5).pluck(:id)
171
+ # Person.where(age: 21).limit(5).pluck(:id)
172
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
173
+ # # => [2, 3]
178
174
  #
179
- def pluck(column_name)
180
- column_name = column_name.to_s
181
- klass.connection.select_all(select(column_name).arel).map! do |attributes|
182
- klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes))
175
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
176
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
177
+ # # => ['0', '27761', '173']
178
+ #
179
+ # See also #ids.
180
+ #
181
+ def pluck(*column_names)
182
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
183
+ return records.pluck(*column_names)
183
184
  end
184
- end
185
185
 
186
- private
186
+ if has_include?(column_names.first)
187
+ relation = apply_join_dependency
188
+ relation.pluck(*column_names)
189
+ else
190
+ klass.disallow_raw_sql!(column_names)
191
+ relation = spawn
192
+ relation.select_values = column_names
193
+ result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
194
+ result.cast_values(klass.attribute_types)
195
+ end
196
+ end
187
197
 
188
- def perform_calculation(operation, column_name, options = {})
189
- operation = operation.to_s.downcase
198
+ # Pick the value(s) from the named column(s) in the current relation.
199
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
200
+ # when you have a relation that's already narrowed down to a single row.
201
+ #
202
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
203
+ # more efficient. The value is, again like with pluck, typecast by the column type.
204
+ #
205
+ # Person.where(id: 1).pick(:name)
206
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
207
+ # # => 'David'
208
+ #
209
+ # Person.where(id: 1).pick(:name, :email_address)
210
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
211
+ # # => [ 'David', 'david@loudthinking.com' ]
212
+ def pick(*column_names)
213
+ limit(1).pluck(*column_names).first
214
+ end
190
215
 
191
- distinct = options[:distinct]
216
+ # Pluck all the ID's for the relation using the table's primary key
217
+ #
218
+ # Person.ids # SELECT people.id FROM people
219
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
220
+ def ids
221
+ pluck primary_key
222
+ end
192
223
 
193
- if operation == "count"
194
- column_name ||= (select_for_count || :all)
224
+ private
225
+ def has_include?(column_name)
226
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
227
+ end
195
228
 
196
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
197
- distinct = true
229
+ def perform_calculation(operation, column_name)
230
+ operation = operation.to_s.downcase
231
+
232
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
233
+ # considered distinct.
234
+ distinct = distinct_value
235
+
236
+ if operation == "count"
237
+ column_name ||= select_for_count
238
+ if column_name == :all
239
+ if !distinct
240
+ distinct = distinct_select?(select_for_count) if group_values.empty?
241
+ elsif group_values.any? || select_values.empty? && order_values.empty?
242
+ column_name = primary_key
243
+ end
244
+ elsif distinct_select?(column_name)
245
+ distinct = nil
246
+ end
198
247
  end
199
248
 
200
- column_name = primary_key if column_name == :all && distinct
201
-
202
- distinct = nil if column_name =~ /\s*DISTINCT\s+/i
249
+ if group_values.any?
250
+ execute_grouped_calculation(operation, column_name, distinct)
251
+ else
252
+ execute_simple_calculation(operation, column_name, distinct)
253
+ end
203
254
  end
204
255
 
205
- if @group_values.any?
206
- execute_grouped_calculation(operation, column_name, distinct)
207
- else
208
- execute_simple_calculation(operation, column_name, distinct)
256
+ def distinct_select?(column_name)
257
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
209
258
  end
210
- end
211
259
 
212
- def aggregate_column(column_name)
213
- if @klass.column_names.include?(column_name.to_s)
214
- Arel::Attribute.new(@klass.unscoped.table, column_name)
215
- else
216
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
217
- end
218
- end
260
+ def aggregate_column(column_name)
261
+ return column_name if Arel::Expressions === column_name
219
262
 
220
- def operation_over_aggregate_column(column, operation, distinct)
221
- operation == 'count' ? column.count(distinct) : column.send(operation)
222
- end
263
+ arel_column(column_name.to_s) do |name|
264
+ Arel.sql(column_name == :all ? "*" : name)
265
+ end
266
+ end
223
267
 
224
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
225
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
226
- relation = reorder(nil)
268
+ def operation_over_aggregate_column(column, operation, distinct)
269
+ operation == "count" ? column.count(distinct) : column.send(operation)
270
+ end
227
271
 
228
- if operation == "count" && (relation.limit_value || relation.offset_value)
229
- # Shortcut when limit is zero.
230
- return 0 if relation.limit_value == 0
272
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
273
+ column_alias = column_name
231
274
 
232
- query_builder = build_count_subquery(relation, column_name, distinct)
233
- else
234
- column = aggregate_column(column_name)
275
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
276
+ # Shortcut when limit is zero.
277
+ return 0 if limit_value == 0
235
278
 
236
- select_value = operation_over_aggregate_column(column, operation, distinct)
279
+ query_builder = build_count_subquery(spawn, column_name, distinct)
280
+ else
281
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
282
+ relation = unscope(:order).distinct!(false)
237
283
 
238
- relation.select_values = [select_value]
284
+ column = aggregate_column(column_name)
239
285
 
240
- query_builder = relation.arel
241
- end
286
+ select_value = operation_over_aggregate_column(column, operation, distinct)
287
+ if operation == "sum" && distinct
288
+ select_value.distinct = true
289
+ end
242
290
 
243
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
244
- end
291
+ column_alias = select_value.alias
292
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
293
+ relation.select_values = [select_value]
245
294
 
246
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
247
- group_attr = @group_values
248
- association = @klass.reflect_on_association(group_attr.first.to_sym)
249
- associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
250
- group_fields = Array(associated ? association.foreign_key : group_attr)
251
- group_aliases = group_fields.map { |field| column_alias_for(field) }
252
- group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
253
- [aliaz, column_for(field)]
254
- }
295
+ query_builder = relation.arel
296
+ end
255
297
 
256
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
298
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
299
+ row = result.first
300
+ value = row && row.values.first
301
+ type = result.column_types.fetch(column_alias) do
302
+ type_for(column_name)
303
+ end
257
304
 
258
- if operation == 'count' && column_name == :all
259
- aggregate_alias = 'count_all'
260
- else
261
- aggregate_alias = column_alias_for(operation, column_name)
305
+ type_cast_calculated_value(value, type, operation)
262
306
  end
263
307
 
264
- select_values = [
265
- operation_over_aggregate_column(
266
- aggregate_column(column_name),
267
- operation,
268
- distinct).as(aggregate_alias)
269
- ]
270
- select_values += @select_values unless @having_values.empty?
308
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
309
+ group_fields = group_values
271
310
 
272
- select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
273
- "#{field} AS #{aliaz}"
274
- }
311
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
312
+ association = klass._reflect_on_association(group_fields.first)
313
+ associated = association && association.belongs_to? # only count belongs_to associations
314
+ group_fields = Array(association.foreign_key) if associated
315
+ end
316
+ group_fields = arel_columns(group_fields)
275
317
 
276
- relation = except(:group).group(group.join(','))
277
- relation.select_values = select_values
318
+ group_aliases = group_fields.map { |field|
319
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
320
+ column_alias_for(field.to_s.downcase)
321
+ }
322
+ group_columns = group_aliases.zip(group_fields)
278
323
 
279
- calculated_data = @klass.connection.select_all(relation)
324
+ aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
280
325
 
281
- if association
282
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
283
- key_records = association.klass.base_class.find(key_ids)
284
- key_records = Hash[key_records.map { |r| [r.id, r] }]
285
- end
326
+ select_values = [
327
+ operation_over_aggregate_column(
328
+ aggregate_column(column_name),
329
+ operation,
330
+ distinct).as(aggregate_alias)
331
+ ]
332
+ select_values += self.select_values unless having_clause.empty?
286
333
 
287
- ActiveSupport::OrderedHash[calculated_data.map do |row|
288
- key = group_columns.map { |aliaz, column|
289
- type_cast_calculated_value(row[aliaz], column)
334
+ select_values.concat group_columns.map { |aliaz, field|
335
+ if field.respond_to?(:as)
336
+ field.as(aliaz)
337
+ else
338
+ "#{field} AS #{aliaz}"
339
+ end
290
340
  }
291
- key = key.first if key.size == 1
292
- key = key_records[key] if associated
293
- [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
294
- end]
295
- end
296
341
 
297
- # Converts the given keys to the value that the database adapter returns as
298
- # a usable column name:
299
- #
300
- # column_alias_for("users.id") # => "users_id"
301
- # column_alias_for("sum(id)") # => "sum_id"
302
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
303
- # column_alias_for("count(*)") # => "count_all"
304
- # column_alias_for("count", "id") # => "count_id"
305
- def column_alias_for(*keys)
306
- table_name = keys.join(' ')
307
- table_name.downcase!
308
- table_name.gsub!(/\*/, 'all')
309
- table_name.gsub!(/\W+/, ' ')
310
- table_name.strip!
311
- table_name.gsub!(/ +/, '_')
312
-
313
- @klass.connection.table_alias_for(table_name)
314
- end
342
+ relation = except(:group).distinct!(false)
343
+ relation.group_values = group_fields
344
+ relation.select_values = select_values
315
345
 
316
- def column_for(field)
317
- field_name = field.to_s.split('.').last
318
- @klass.columns.detect { |c| c.name.to_s == field_name }
319
- end
346
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
347
+
348
+ if association
349
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
350
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
351
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
352
+ end
320
353
 
321
- def type_cast_calculated_value(value, column, operation = nil)
322
- case operation
323
- when 'count' then value.to_i
324
- when 'sum' then type_cast_using_column(value || '0', column)
325
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
326
- else type_cast_using_column(value, column)
354
+ Hash[calculated_data.map do |row|
355
+ key = group_columns.map { |aliaz, col_name|
356
+ type = type_for(col_name) do
357
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
358
+ end
359
+ type_cast_calculated_value(row[aliaz], type)
360
+ }
361
+ key = key.first if key.size == 1
362
+ key = key_records[key] if associated
363
+
364
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
365
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
366
+ end]
327
367
  end
328
- end
329
368
 
330
- def type_cast_using_column(value, column)
331
- column ? column.type_cast(value) : value
332
- end
369
+ # Converts the given field to the value that the database adapter returns as
370
+ # a usable column name:
371
+ #
372
+ # column_alias_for("users.id") # => "users_id"
373
+ # column_alias_for("sum(id)") # => "sum_id"
374
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
375
+ # column_alias_for("count(*)") # => "count_all"
376
+ def column_alias_for(field)
377
+ column_alias = +field
378
+ column_alias.gsub!(/\*/, "all")
379
+ column_alias.gsub!(/\W+/, " ")
380
+ column_alias.strip!
381
+ column_alias.gsub!(/ +/, "_")
382
+
383
+ connection.table_alias_for(column_alias)
384
+ end
333
385
 
334
- def select_for_count
335
- if @select_values.present?
336
- select = @select_values.join(", ")
337
- select if select !~ /(,|\*)/
386
+ def type_for(field, &block)
387
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
388
+ @klass.type_for_attribute(field_name, &block)
389
+ end
390
+
391
+ def type_cast_calculated_value(value, type, operation = nil)
392
+ case operation
393
+ when "count" then value.to_i
394
+ when "sum" then type.deserialize(value || 0)
395
+ when "average" then value&.respond_to?(:to_d) ? value.to_d : value
396
+ else type.deserialize(value)
397
+ end
398
+ end
399
+
400
+ def select_for_count
401
+ if select_values.present?
402
+ return select_values.first if select_values.one?
403
+ select_values.join(", ")
404
+ else
405
+ :all
406
+ end
338
407
  end
339
- end
340
408
 
341
- def build_count_subquery(relation, column_name, distinct)
342
- column_alias = Arel.sql('count_column')
343
- subquery_alias = Arel.sql('subquery_for_count')
409
+ def build_count_subquery(relation, column_name, distinct)
410
+ if column_name == :all
411
+ column_alias = Arel.star
412
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
413
+ else
414
+ column_alias = Arel.sql("count_column")
415
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
416
+ end
344
417
 
345
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
346
- relation.select_values = [aliased_column]
347
- subquery = relation.arel.as(subquery_alias)
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
348
420
 
349
- sm = Arel::SelectManager.new relation.engine
350
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
351
- sm.project(select_value).from(subquery)
352
- end
421
+ relation.build_subquery(subquery_alias, select_value)
422
+ end
353
423
  end
354
424
  end