activerecord 4.2.0 → 5.2.8.1

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

Potentially problematic release.


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

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