activerecord 3.2.22.5 → 4.2.11.3

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