activerecord 4.2.9 → 5.2.8

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +614 -1572
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -89
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -593
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -188
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  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 +50 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  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 +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -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 +67 -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 +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +135 -88
  133. data/lib/active_record/errors.rb +179 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  139. data/lib/active_record/gem_version.rb +4 -2
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +94 -32
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -340
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +199 -124
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +24 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -50
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Calculations
3
5
  # Count the records.
@@ -14,127 +16,138 @@ module ActiveRecord
14
16
  # Person.distinct.count(:age)
15
17
  # # => counts the number of different age values
16
18
  #
17
- # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
19
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
20
+ # it returns a Hash whose keys represent the aggregated column,
18
21
  # and the values are the respective amounts:
19
22
  #
20
23
  # Person.group(:city).count
21
24
  # # => { 'Rome' => 5, 'Paris' => 3 }
22
25
  #
23
- # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
26
+ # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
24
27
  # keys are an array containing the individual values of each column and the value
25
- # of each key would be the +count+.
28
+ # of each key would be the #count.
26
29
  #
27
30
  # Article.group(:status, :category).count
28
31
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
29
32
  # ["published", "business"]=>0, ["published", "technology"]=>2}
30
33
  #
31
- # If +count+ is used with +select+, it will count the selected columns:
34
+ # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
32
35
  #
33
36
  # Person.select(:age).count
34
37
  # # => counts the number of different age values
35
38
  #
36
- # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
39
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
37
40
  # between databases. In invalid cases, an error from the database is thrown.
38
- def count(column_name = nil, options = {})
39
- if options.present? && !ActiveRecord.const_defined?(:DeprecatedFinders)
40
- raise ArgumentError, "Relation#count does not support finder options anymore. " \
41
- "Please build a scope and then call count on it or use the " \
42
- "activerecord-deprecated_finders gem to enable this functionality."
41
+ def count(column_name = nil)
42
+ if block_given?
43
+ unless column_name.nil?
44
+ ActiveSupport::Deprecation.warn \
45
+ "When `count' is called with a block, it ignores other arguments. " \
46
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
47
+ end
43
48
 
49
+ return super()
44
50
  end
45
51
 
46
- # TODO: Remove options argument as soon we remove support to
47
- # activerecord-deprecated_finders.
48
- calculate(:count, column_name, options)
52
+ calculate(:count, column_name)
49
53
  end
50
54
 
51
55
  # Calculates the average value on a given column. Returns +nil+ if there's
52
- # no row. See +calculate+ for examples with options.
56
+ # no row. See #calculate for examples with options.
53
57
  #
54
58
  # Person.average(:age) # => 35.8
55
- def average(column_name, options = {})
56
- # TODO: Remove options argument as soon we remove support to
57
- # activerecord-deprecated_finders.
58
- calculate(:average, column_name, options)
59
+ def average(column_name)
60
+ calculate(:average, column_name)
59
61
  end
60
62
 
61
63
  # Calculates the minimum value on a given column. The value is returned
62
64
  # with the same data type of the column, or +nil+ if there's no row. See
63
- # +calculate+ for examples with options.
65
+ # #calculate for examples with options.
64
66
  #
65
67
  # Person.minimum(:age) # => 7
66
- def minimum(column_name, options = {})
67
- # TODO: Remove options argument as soon we remove support to
68
- # activerecord-deprecated_finders.
69
- calculate(:minimum, column_name, options)
68
+ def minimum(column_name)
69
+ calculate(:minimum, column_name)
70
70
  end
71
71
 
72
72
  # Calculates the maximum value on a given column. The value is returned
73
73
  # with the same data type of the column, or +nil+ if there's no row. See
74
- # +calculate+ for examples with options.
74
+ # #calculate for examples with options.
75
75
  #
76
76
  # Person.maximum(:age) # => 93
77
- def maximum(column_name, options = {})
78
- # TODO: Remove options argument as soon we remove support to
79
- # activerecord-deprecated_finders.
80
- calculate(:maximum, column_name, options)
77
+ def maximum(column_name)
78
+ calculate(:maximum, column_name)
81
79
  end
82
80
 
83
81
  # Calculates the sum of values on a given column. The value is returned
84
- # with the same data type of the column, 0 if there's no row. See
85
- # +calculate+ for examples with options.
82
+ # with the same data type of the column, +0+ if there's no row. See
83
+ # #calculate for examples with options.
86
84
  #
87
85
  # Person.sum(:age) # => 4562
88
- def sum(*args)
89
- calculate(:sum, *args)
86
+ def sum(column_name = nil)
87
+ if block_given?
88
+ unless column_name.nil?
89
+ ActiveSupport::Deprecation.warn \
90
+ "When `sum' is called with a block, it ignores other arguments. " \
91
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
92
+ end
93
+
94
+ return super()
95
+ end
96
+
97
+ calculate(:sum, column_name)
90
98
  end
91
99
 
92
- # This calculates aggregate values in the given column. Methods for count, sum, average,
93
- # minimum, and maximum have been added as shortcuts.
100
+ # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
101
+ # #minimum, and #maximum have been added as shortcuts.
94
102
  #
95
- # There are two basic forms of output:
103
+ # Person.calculate(:count, :all) # The same as Person.count
104
+ # Person.average(:age) # SELECT AVG(age) FROM people...
96
105
  #
97
- # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
98
- # for AVG, and the given column's type for everything else.
106
+ # # Selects the minimum age for any family without any minors
107
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
99
108
  #
100
- # * Grouped values: This returns an ordered hash of the values and groups them. It
101
- # takes either a column name, or the name of a belongs_to association.
109
+ # Person.sum("2 * age")
102
110
  #
103
- # values = Person.group('last_name').maximum(:age)
104
- # puts values["Drake"]
105
- # # => 43
111
+ # There are two basic forms of output:
106
112
  #
107
- # drake = Family.find_by(last_name: 'Drake')
108
- # values = Person.group(:family).maximum(:age) # Person belongs_to :family
109
- # puts values[drake]
110
- # # => 43
113
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
114
+ # for AVG, and the given column's type for everything else.
111
115
  #
112
- # values.each do |family, max_age|
113
- # ...
114
- # end
116
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
117
+ # takes either a column name, or the name of a belongs_to association.
115
118
  #
116
- # Person.calculate(:count, :all) # The same as Person.count
117
- # Person.average(:age) # SELECT AVG(age) FROM people...
119
+ # values = Person.group('last_name').maximum(:age)
120
+ # puts values["Drake"]
121
+ # # => 43
118
122
  #
119
- # # Selects the minimum age for any family without any minors
120
- # Person.group(:last_name).having("min(age) > 17").minimum(:age)
123
+ # drake = Family.find_by(last_name: 'Drake')
124
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
125
+ # puts values[drake]
126
+ # # => 43
121
127
  #
122
- # Person.sum("2 * age")
123
- def calculate(operation, column_name, options = {})
124
- # TODO: Remove options argument as soon we remove support to
125
- # activerecord-deprecated_finders.
126
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
127
- column_name = attribute_alias(column_name)
128
- end
129
-
128
+ # values.each do |family, max_age|
129
+ # ...
130
+ # end
131
+ def calculate(operation, column_name)
130
132
  if has_include?(column_name)
131
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
133
+ relation = apply_join_dependency
134
+
135
+ if operation.to_s.downcase == "count"
136
+ unless distinct_value || distinct_select?(column_name || select_for_count)
137
+ relation.distinct!
138
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
139
+ end
140
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
141
+ relation.order_values = []
142
+ end
143
+
144
+ relation.calculate(operation, column_name)
132
145
  else
133
- perform_calculation(operation, column_name, options)
146
+ perform_calculation(operation, column_name)
134
147
  end
135
148
  end
136
149
 
137
- # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
150
+ # Use #pluck as a shortcut to select one or more attributes without
138
151
  # loading a bunch of records just to grab the attributes you want.
139
152
  #
140
153
  # Person.pluck(:name)
@@ -143,19 +156,19 @@ module ActiveRecord
143
156
  #
144
157
  # Person.all.map(&:name)
145
158
  #
146
- # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
159
+ # Pluck returns an Array of attribute values type-casted to match
147
160
  # the plucked column names, if they can be deduced. Plucking an SQL fragment
148
161
  # returns String values by default.
149
162
  #
150
- # Person.pluck(:id)
151
- # # SELECT people.id FROM people
152
- # # => [1, 2, 3]
163
+ # Person.pluck(:name)
164
+ # # SELECT people.name FROM people
165
+ # # => ['David', 'Jeremy', 'Jose']
153
166
  #
154
167
  # Person.pluck(:id, :name)
155
168
  # # SELECT people.id, people.name FROM people
156
169
  # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
157
170
  #
158
- # Person.pluck('DISTINCT role')
171
+ # Person.distinct.pluck(:role)
159
172
  # # SELECT DISTINCT role FROM people
160
173
  # # => ['admin', 'member', 'guest']
161
174
  #
@@ -167,24 +180,22 @@ module ActiveRecord
167
180
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
168
181
  # # => ['0', '27761', '173']
169
182
  #
183
+ # See also #ids.
184
+ #
170
185
  def pluck(*column_names)
171
- column_names.map! do |column_name|
172
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
173
- attribute_alias(column_name)
174
- else
175
- column_name.to_s
176
- end
186
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
187
+ return records.pluck(*column_names)
177
188
  end
178
189
 
179
190
  if has_include?(column_names.first)
180
- construct_relation_for_association_calculations.pluck(*column_names)
191
+ relation = apply_join_dependency
192
+ relation.pluck(*column_names)
181
193
  else
194
+ klass.enforce_raw_sql_whitelist(column_names)
182
195
  relation = spawn
183
- relation.select_values = column_names.map { |cn|
184
- columns_hash.key?(cn) ? arel_table[cn] : cn
185
- }
186
- result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
187
- result.cast_values(klass.column_types)
196
+ relation.select_values = column_names
197
+ result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
198
+ result.cast_values(klass.attribute_types)
188
199
  end
189
200
  end
190
201
 
@@ -197,214 +208,211 @@ module ActiveRecord
197
208
  end
198
209
 
199
210
  private
211
+ def has_include?(column_name)
212
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
213
+ end
200
214
 
201
- def has_include?(column_name)
202
- eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
203
- end
215
+ def perform_calculation(operation, column_name)
216
+ operation = operation.to_s.downcase
217
+
218
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
219
+ # considered distinct.
220
+ distinct = distinct_value
221
+
222
+ if operation == "count"
223
+ column_name ||= select_for_count
224
+ if column_name == :all
225
+ if !distinct
226
+ distinct = distinct_select?(select_for_count) if group_values.empty?
227
+ elsif group_values.any? || select_values.empty? && order_values.empty?
228
+ column_name = primary_key
229
+ end
230
+ elsif distinct_select?(column_name)
231
+ distinct = nil
232
+ end
233
+ end
204
234
 
205
- def perform_calculation(operation, column_name, options = {})
206
- # TODO: Remove options argument as soon we remove support to
207
- # activerecord-deprecated_finders.
208
- operation = operation.to_s.downcase
235
+ if group_values.any?
236
+ execute_grouped_calculation(operation, column_name, distinct)
237
+ else
238
+ execute_simple_calculation(operation, column_name, distinct)
239
+ end
240
+ end
209
241
 
210
- # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
211
- distinct = self.distinct_value
242
+ def distinct_select?(column_name)
243
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
244
+ end
212
245
 
213
- if operation == "count"
214
- column_name ||= select_for_count
246
+ def aggregate_column(column_name)
247
+ return column_name if Arel::Expressions === column_name
215
248
 
216
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
217
- distinct = true
249
+ if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
250
+ @klass.arel_attribute(column_name)
251
+ else
252
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
218
253
  end
219
-
220
- column_name = primary_key if column_name == :all && distinct
221
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
222
254
  end
223
255
 
224
- if group_values.any?
225
- execute_grouped_calculation(operation, column_name, distinct)
226
- else
227
- execute_simple_calculation(operation, column_name, distinct)
256
+ def operation_over_aggregate_column(column, operation, distinct)
257
+ operation == "count" ? column.count(distinct) : column.send(operation)
228
258
  end
229
- end
230
259
 
231
- def aggregate_column(column_name)
232
- if @klass.column_names.include?(column_name.to_s)
233
- Arel::Attribute.new(@klass.unscoped.table, column_name)
234
- else
235
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
236
- end
237
- end
238
-
239
- def operation_over_aggregate_column(column, operation, distinct)
240
- operation == 'count' ? column.count(distinct) : column.send(operation)
241
- end
260
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
261
+ column_alias = column_name
242
262
 
243
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
244
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
245
- relation = unscope(:order)
263
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
264
+ # Shortcut when limit is zero.
265
+ return 0 if limit_value == 0
246
266
 
247
- column_alias = column_name
267
+ query_builder = build_count_subquery(spawn, column_name, distinct)
268
+ else
269
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
270
+ relation = unscope(:order).distinct!(false)
248
271
 
249
- bind_values = nil
272
+ column = aggregate_column(column_name)
250
273
 
251
- if operation == "count" && (relation.limit_value || relation.offset_value)
252
- # Shortcut when limit is zero.
253
- return 0 if relation.limit_value == 0
274
+ select_value = operation_over_aggregate_column(column, operation, distinct)
275
+ if operation == "sum" && distinct
276
+ select_value.distinct = true
277
+ end
254
278
 
255
- query_builder = build_count_subquery(relation, column_name, distinct)
256
- bind_values = query_builder.bind_values + relation.bind_values
257
- else
258
- column = aggregate_column(column_name)
279
+ column_alias = select_value.alias
280
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
281
+ relation.select_values = [select_value]
259
282
 
260
- select_value = operation_over_aggregate_column(column, operation, distinct)
283
+ query_builder = relation.arel
284
+ end
261
285
 
262
- column_alias = select_value.alias
263
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
264
- relation.select_values = [select_value]
286
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
287
+ row = result.first
288
+ value = row && row.values.first
289
+ type = result.column_types.fetch(column_alias) do
290
+ type_for(column_name)
291
+ end
265
292
 
266
- query_builder = relation.arel
267
- bind_values = query_builder.bind_values + relation.bind_values
293
+ type_cast_calculated_value(value, type, operation)
268
294
  end
269
295
 
270
- result = @klass.connection.select_all(query_builder, nil, bind_values)
271
- row = result.first
272
- value = row && row.values.first
273
- column = result.column_types.fetch(column_alias) do
274
- type_for(column_name)
275
- end
296
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
297
+ group_attrs = group_values
276
298
 
277
- type_cast_calculated_value(value, column, operation)
278
- end
299
+ if group_attrs.first.respond_to?(:to_sym)
300
+ association = @klass._reflect_on_association(group_attrs.first)
301
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
302
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
303
+ else
304
+ group_fields = group_attrs
305
+ end
306
+ group_fields = arel_columns(group_fields)
279
307
 
280
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
281
- group_attrs = group_values
308
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
309
+ group_columns = group_aliases.zip(group_fields)
282
310
 
283
- if group_attrs.first.respond_to?(:to_sym)
284
- association = @klass._reflect_on_association(group_attrs.first)
285
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
286
- group_fields = Array(associated ? association.foreign_key : group_attrs)
287
- else
288
- group_fields = group_attrs
289
- end
290
- group_fields = arel_columns(group_fields)
311
+ if operation == "count" && column_name == :all
312
+ aggregate_alias = "count_all"
313
+ else
314
+ aggregate_alias = column_alias_for([operation, column_name].join(" "))
315
+ end
291
316
 
292
- group_aliases = group_fields.map { |field|
293
- column_alias_for(field)
294
- }
295
- group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
296
- [aliaz, field]
297
- }
317
+ select_values = [
318
+ operation_over_aggregate_column(
319
+ aggregate_column(column_name),
320
+ operation,
321
+ distinct).as(aggregate_alias)
322
+ ]
323
+ select_values += self.select_values unless having_clause.empty?
324
+
325
+ select_values.concat group_columns.map { |aliaz, field|
326
+ if field.respond_to?(:as)
327
+ field.as(aliaz)
328
+ else
329
+ "#{field} AS #{aliaz}"
330
+ end
331
+ }
298
332
 
299
- group = group_fields
333
+ relation = except(:group).distinct!(false)
334
+ relation.group_values = group_fields
335
+ relation.select_values = select_values
300
336
 
301
- if operation == 'count' && column_name == :all
302
- aggregate_alias = 'count_all'
303
- else
304
- aggregate_alias = column_alias_for([operation, column_name].join(' '))
305
- end
337
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
306
338
 
307
- select_values = [
308
- operation_over_aggregate_column(
309
- aggregate_column(column_name),
310
- operation,
311
- distinct).as(aggregate_alias)
312
- ]
313
- select_values += self.select_values unless having_values.empty?
314
-
315
- select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
316
- if field.respond_to?(:as)
317
- field.as(aliaz)
318
- else
319
- "#{field} AS #{aliaz}"
339
+ if association
340
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
341
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
342
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
320
343
  end
321
- }
322
344
 
323
- relation = except(:group)
324
- relation.group_values = group
325
- relation.select_values = select_values
326
-
327
- calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
328
-
329
- if association
330
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
331
- key_records = association.klass.base_class.find(key_ids)
332
- key_records = Hash[key_records.map { |r| [r.id, r] }]
345
+ Hash[calculated_data.map do |row|
346
+ key = group_columns.map { |aliaz, col_name|
347
+ type = type_for(col_name) do
348
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
349
+ end
350
+ type_cast_calculated_value(row[aliaz], type)
351
+ }
352
+ key = key.first if key.size == 1
353
+ key = key_records[key] if associated
354
+
355
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
356
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
357
+ end]
333
358
  end
334
359
 
335
- Hash[calculated_data.map do |row|
336
- key = group_columns.map { |aliaz, col_name|
337
- column = calculated_data.column_types.fetch(aliaz) do
338
- type_for(col_name)
339
- end
340
- type_cast_calculated_value(row[aliaz], column)
341
- }
342
- key = key.first if key.size == 1
343
- key = key_records[key] if associated
360
+ # Converts the given keys to the value that the database adapter returns as
361
+ # a usable column name:
362
+ #
363
+ # column_alias_for("users.id") # => "users_id"
364
+ # column_alias_for("sum(id)") # => "sum_id"
365
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
366
+ # column_alias_for("count(*)") # => "count_all"
367
+ def column_alias_for(keys)
368
+ if keys.respond_to? :name
369
+ keys = "#{keys.relation.name}.#{keys.name}"
370
+ end
344
371
 
345
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
346
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
347
- end]
348
- end
372
+ table_name = keys.to_s.downcase
373
+ table_name.gsub!(/\*/, "all")
374
+ table_name.gsub!(/\W+/, " ")
375
+ table_name.strip!
376
+ table_name.gsub!(/ +/, "_")
349
377
 
350
- # Converts the given keys to the value that the database adapter returns as
351
- # a usable column name:
352
- #
353
- # column_alias_for("users.id") # => "users_id"
354
- # column_alias_for("sum(id)") # => "sum_id"
355
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
356
- # column_alias_for("count(*)") # => "count_all"
357
- # column_alias_for("count", "id") # => "count_id"
358
- def column_alias_for(keys)
359
- if keys.respond_to? :name
360
- keys = "#{keys.relation.name}.#{keys.name}"
378
+ @klass.connection.table_alias_for(table_name)
361
379
  end
362
380
 
363
- table_name = keys.to_s.downcase
364
- table_name.gsub!(/\*/, 'all')
365
- table_name.gsub!(/\W+/, ' ')
366
- table_name.strip!
367
- table_name.gsub!(/ +/, '_')
368
-
369
- @klass.connection.table_alias_for(table_name)
370
- end
371
-
372
- def type_for(field)
373
- field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
374
- @klass.type_for_attribute(field_name)
375
- end
381
+ def type_for(field, &block)
382
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
383
+ @klass.type_for_attribute(field_name, &block)
384
+ end
376
385
 
377
- def type_cast_calculated_value(value, type, operation = nil)
378
- case operation
379
- when 'count' then value.to_i
380
- when 'sum' then type.type_cast_from_database(value || 0)
381
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
382
- else type.type_cast_from_database(value)
386
+ def type_cast_calculated_value(value, type, operation = nil)
387
+ case operation
388
+ when "count" then value.to_i
389
+ when "sum" then type.deserialize(value || 0)
390
+ when "average" then value && value.respond_to?(:to_d) ? value.to_d : value
391
+ else type.deserialize(value)
392
+ end
383
393
  end
384
- end
385
394
 
386
- # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
387
- def select_for_count
388
- if select_values.present?
389
- select_values.join(", ")
390
- else
391
- :all
395
+ def select_for_count
396
+ if select_values.present?
397
+ return select_values.first if select_values.one?
398
+ select_values.join(", ")
399
+ else
400
+ :all
401
+ end
392
402
  end
393
- end
394
403
 
395
- def build_count_subquery(relation, column_name, distinct)
396
- column_alias = Arel.sql('count_column')
397
- subquery_alias = Arel.sql('subquery_for_count')
404
+ def build_count_subquery(relation, column_name, distinct)
405
+ if column_name == :all
406
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
407
+ else
408
+ column_alias = Arel.sql("count_column")
409
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
410
+ end
398
411
 
399
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
400
- relation.select_values = [aliased_column]
401
- arel = relation.arel
402
- subquery = arel.as(subquery_alias)
412
+ subquery = relation.arel.as(Arel.sql("subquery_for_count"))
413
+ select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
403
414
 
404
- sm = Arel::SelectManager.new relation.engine
405
- sm.bind_values = arel.bind_values
406
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
407
- sm.project(select_value).from(subquery)
408
- end
415
+ Arel::SelectManager.new(subquery).project(select_value)
416
+ end
409
417
  end
410
418
  end