activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,77 +1,71 @@
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
 
69
- # Calculates the minimum value on a given column. The value is returned
61
+ # Calculates the minimum value on a given column. The value is returned
70
62
  # with the same data type of the column, or +nil+ if there's no row. See
71
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,100 +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
92
- def sum(column_name, options = {})
93
- calculate(:sum, column_name, options)
87
+ # Person.sum(:age) # => 4562
88
+ def sum(*args)
89
+ calculate(:sum, *args)
94
90
  end
95
91
 
96
- # This calculates aggregate values in the given column. Methods for count, sum, average,
97
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
98
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
92
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
93
+ # minimum, and maximum have been added as shortcuts.
99
94
  #
100
95
  # There are two basic forms of output:
101
- # * 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
102
98
  # for AVG, and the given column's type for everything else.
103
- # * Grouped values: This returns an ordered hash of the values and groups them by the
104
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
105
99
  #
106
- # 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)
107
104
  # puts values["Drake"]
108
- # => 43
105
+ # # => 43
109
106
  #
110
- # drake = Family.find_by_last_name('Drake')
111
- # 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
112
109
  # puts values[drake]
113
- # => 43
110
+ # # => 43
114
111
  #
115
112
  # values.each do |family, max_age|
116
113
  # ...
117
114
  # end
118
115
  #
119
- # Options:
120
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
121
- # See conditions in the intro to ActiveRecord::Base.
122
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
123
- # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
124
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
125
- # (Rarely needed).
126
- # The records will be returned read-only since they will have attributes that do not correspond to the
127
- # table's columns.
128
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
129
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
130
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
131
- # want to do a join, but not include the joined columns.
132
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
133
- # SELECT COUNT(DISTINCT posts.id) ...
134
- #
135
- # Examples:
136
116
  # Person.calculate(:count, :all) # The same as Person.count
137
117
  # Person.average(:age) # SELECT AVG(age) FROM people...
138
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
139
- # # everyone with a last name other than 'Drake'
140
118
  #
141
119
  # # Selects the minimum age for any family without any minors
142
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
120
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
143
121
  #
144
122
  # Person.sum("2 * age")
145
123
  def calculate(operation, column_name, options = {})
146
- if options.except(:distinct).present?
147
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
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
+
130
+ if has_include?(column_name)
131
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
148
132
  else
149
- relation = with_default_scope
133
+ perform_calculation(operation, column_name, options)
134
+ end
135
+ end
150
136
 
151
- if relation.equal?(self)
152
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
153
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
154
- else
155
- perform_calculation(operation, column_name, options)
156
- end
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)
145
+ #
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.
149
+ #
150
+ # Person.pluck(:id)
151
+ # # SELECT people.id FROM people
152
+ # # => [1, 2, 3]
153
+ #
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)
157
174
  else
158
- relation.calculate(operation, column_name, options)
175
+ column_name.to_s
159
176
  end
160
177
  end
161
- rescue ThrowResult
162
- 0
178
+
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)
188
+ end
189
+ end
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
163
197
  end
164
198
 
165
199
  private
166
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
+
167
205
  def perform_calculation(operation, column_name, options = {})
206
+ # TODO: Remove options argument as soon we remove support to
207
+ # activerecord-deprecated_finders.
168
208
  operation = operation.to_s.downcase
169
209
 
170
- distinct = options[:distinct]
210
+ # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
211
+ distinct = self.distinct_value
171
212
 
172
213
  if operation == "count"
173
- column_name ||= (select_for_count || :all)
214
+ column_name ||= select_for_count
174
215
 
175
216
  unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
176
217
  distinct = true
177
218
  end
178
219
 
179
220
  column_name = primary_key if column_name == :all && distinct
180
-
181
- distinct = nil if column_name =~ /\s*DISTINCT\s+/i
221
+ distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
182
222
  end
183
223
 
184
- if @group_values.any?
224
+ if group_values.any?
185
225
  execute_grouped_calculation(operation, column_name, distinct)
186
226
  else
187
227
  execute_simple_calculation(operation, column_name, distinct)
@@ -202,42 +242,66 @@ module ActiveRecord
202
242
 
203
243
  def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
204
244
  # Postgresql doesn't like ORDER BY when there are no GROUP BY
205
- relation = reorder(nil)
245
+ relation = unscope(:order)
246
+
247
+ column_alias = column_name
248
+
249
+ bind_values = nil
206
250
 
207
251
  if operation == "count" && (relation.limit_value || relation.offset_value)
208
252
  # Shortcut when limit is zero.
209
253
  return 0 if relation.limit_value == 0
210
254
 
211
255
  query_builder = build_count_subquery(relation, column_name, distinct)
256
+ bind_values = query_builder.bind_values + relation.bind_values
212
257
  else
213
258
  column = aggregate_column(column_name)
214
259
 
215
260
  select_value = operation_over_aggregate_column(column, operation, distinct)
216
261
 
262
+ column_alias = select_value.alias
263
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
217
264
  relation.select_values = [select_value]
218
265
 
219
266
  query_builder = relation.arel
267
+ bind_values = query_builder.bind_values + relation.bind_values
268
+ end
269
+
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)
220
275
  end
221
276
 
222
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
277
+ type_cast_calculated_value(value, column, operation)
223
278
  end
224
279
 
225
280
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
226
- group_attr = @group_values
227
- association = @klass.reflect_on_association(group_attr.first.to_sym)
228
- associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
229
- group_fields = Array(associated ? association.foreign_key : group_attr)
230
- group_aliases = group_fields.map { |field| column_alias_for(field) }
281
+ group_attrs = group_values
282
+
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)
291
+
292
+ group_aliases = group_fields.map { |field|
293
+ column_alias_for(field)
294
+ }
231
295
  group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
232
- [aliaz, column_for(field)]
296
+ [aliaz, field]
233
297
  }
234
298
 
235
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
299
+ group = group_fields
236
300
 
237
301
  if operation == 'count' && column_name == :all
238
302
  aggregate_alias = 'count_all'
239
303
  else
240
- aggregate_alias = column_alias_for(operation, column_name)
304
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
241
305
  end
242
306
 
243
307
  select_values = [
@@ -246,16 +310,21 @@ module ActiveRecord
246
310
  operation,
247
311
  distinct).as(aggregate_alias)
248
312
  ]
249
- select_values += @select_values unless @having_values.empty?
313
+ select_values += self.select_values unless having_values.empty?
250
314
 
251
315
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
252
- "#{field} AS #{aliaz}"
316
+ if field.respond_to?(:as)
317
+ field.as(aliaz)
318
+ else
319
+ "#{field} AS #{aliaz}"
320
+ end
253
321
  }
254
322
 
255
- relation = except(:group).group(group.join(','))
323
+ relation = except(:group)
324
+ relation.group_values = group
256
325
  relation.select_values = select_values
257
326
 
258
- calculated_data = @klass.connection.select_all(relation)
327
+ calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
259
328
 
260
329
  if association
261
330
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -263,13 +332,18 @@ module ActiveRecord
263
332
  key_records = Hash[key_records.map { |r| [r.id, r] }]
264
333
  end
265
334
 
266
- ActiveSupport::OrderedHash[calculated_data.map do |row|
267
- 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
268
340
  type_cast_calculated_value(row[aliaz], column)
269
341
  }
270
- key = key.first if key.size == 1
342
+ key = key.first if key.size == 1
271
343
  key = key_records[key] if associated
272
- [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)]
273
347
  end]
274
348
  end
275
349
 
@@ -281,9 +355,12 @@ module ActiveRecord
281
355
  # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
282
356
  # column_alias_for("count(*)") # => "count_all"
283
357
  # column_alias_for("count", "id") # => "count_id"
284
- def column_alias_for(*keys)
285
- table_name = keys.join(' ')
286
- 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
287
364
  table_name.gsub!(/\*/, 'all')
288
365
  table_name.gsub!(/\W+/, ' ')
289
366
  table_name.strip!
@@ -292,28 +369,26 @@ module ActiveRecord
292
369
  @klass.connection.table_alias_for(table_name)
293
370
  end
294
371
 
295
- def column_for(field)
296
- field_name = field.to_s.split('.').last
297
- @klass.columns.detect { |c| c.name.to_s == field_name }
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)
298
375
  end
299
376
 
300
- def type_cast_calculated_value(value, column, operation = nil)
377
+ def type_cast_calculated_value(value, type, operation = nil)
301
378
  case operation
302
379
  when 'count' then value.to_i
303
- when 'sum' then type_cast_using_column(value || '0', column)
380
+ when 'sum' then type.type_cast_from_database(value || 0)
304
381
  when 'average' then value.respond_to?(:to_d) ? value.to_d : value
305
- else type_cast_using_column(value, column)
382
+ else type.type_cast_from_database(value)
306
383
  end
307
384
  end
308
385
 
309
- def type_cast_using_column(value, column)
310
- column ? column.type_cast(value) : value
311
- end
312
-
386
+ # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
313
387
  def select_for_count
314
- if @select_values.present?
315
- select = @select_values.join(", ")
316
- select if select !~ /(,|\*)/
388
+ if select_values.present?
389
+ select_values.join(", ")
390
+ else
391
+ :all
317
392
  end
318
393
  end
319
394
 
@@ -323,9 +398,11 @@ module ActiveRecord
323
398
 
324
399
  aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
325
400
  relation.select_values = [aliased_column]
326
- subquery = relation.arel.as(subquery_alias)
401
+ arel = relation.arel
402
+ subquery = arel.as(subquery_alias)
327
403
 
328
404
  sm = Arel::SelectManager.new relation.engine
405
+ sm.bind_values = arel.bind_values
329
406
  select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
330
407
  sm.project(select_value).from(subquery)
331
408
  end
@@ -0,0 +1,140 @@
1
+ require 'set'
2
+ require 'active_support/concern'
3
+ require 'active_support/deprecation'
4
+
5
+ module ActiveRecord
6
+ module Delegation # :nodoc:
7
+ module DelegateCache
8
+ def relation_delegate_class(klass) # :nodoc:
9
+ @relation_delegate_cache[klass]
10
+ end
11
+
12
+ def initialize_relation_delegate_cache # :nodoc:
13
+ @relation_delegate_cache = cache = {}
14
+ [
15
+ ActiveRecord::Relation,
16
+ ActiveRecord::Associations::CollectionProxy,
17
+ ActiveRecord::AssociationRelation
18
+ ].each do |klass|
19
+ delegate = Class.new(klass) {
20
+ include ClassSpecificRelation
21
+ }
22
+ const_set klass.name.gsub('::', '_'), delegate
23
+ cache[klass] = delegate
24
+ end
25
+ end
26
+
27
+ def inherited(child_class)
28
+ child_class.initialize_relation_delegate_cache
29
+ super
30
+ end
31
+ end
32
+
33
+ extend ActiveSupport::Concern
34
+
35
+ # This module creates compiled delegation methods dynamically at runtime, which makes
36
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
37
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
38
+ # for each different klass, and the delegations are compiled into that subclass only.
39
+
40
+ BLACKLISTED_ARRAY_METHODS = [
41
+ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
42
+ :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
43
+ :keep_if, :pop, :shift, :delete_at, :select!
44
+ ].to_set # :nodoc:
45
+
46
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
47
+
48
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
49
+ :connection, :columns_hash, :to => :klass
50
+
51
+ module ClassSpecificRelation # :nodoc:
52
+ extend ActiveSupport::Concern
53
+
54
+ included do
55
+ @delegation_mutex = Mutex.new
56
+ end
57
+
58
+ module ClassMethods # :nodoc:
59
+ def name
60
+ superclass.name
61
+ end
62
+
63
+ def delegate_to_scoped_klass(method)
64
+ @delegation_mutex.synchronize do
65
+ return if method_defined?(method)
66
+
67
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
68
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
69
+ def #{method}(*args, &block)
70
+ scoping { @klass.#{method}(*args, &block) }
71
+ end
72
+ RUBY
73
+ else
74
+ define_method method do |*args, &block|
75
+ scoping { @klass.public_send(method, *args, &block) }
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def delegate(method, opts = {})
82
+ @delegation_mutex.synchronize do
83
+ return if method_defined?(method)
84
+ super
85
+ end
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ def method_missing(method, *args, &block)
92
+ if @klass.respond_to?(method)
93
+ self.class.delegate_to_scoped_klass(method)
94
+ scoping { @klass.public_send(method, *args, &block) }
95
+ elsif arel.respond_to?(method)
96
+ self.class.delegate method, :to => :arel
97
+ arel.public_send(method, *args, &block)
98
+ else
99
+ super
100
+ end
101
+ end
102
+ end
103
+
104
+ module ClassMethods # :nodoc:
105
+ def create(klass, *args)
106
+ relation_class_for(klass).new(klass, *args)
107
+ end
108
+
109
+ private
110
+
111
+ def relation_class_for(klass)
112
+ klass.relation_delegate_class(self)
113
+ end
114
+ end
115
+
116
+ def respond_to?(method, include_private = false)
117
+ super || @klass.respond_to?(method, include_private) ||
118
+ array_delegable?(method) ||
119
+ arel.respond_to?(method, include_private)
120
+ end
121
+
122
+ protected
123
+
124
+ def array_delegable?(method)
125
+ Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
126
+ end
127
+
128
+ def method_missing(method, *args, &block)
129
+ if @klass.respond_to?(method)
130
+ scoping { @klass.public_send(method, *args, &block) }
131
+ elsif array_delegable?(method)
132
+ to_a.public_send(method, *args, &block)
133
+ elsif arel.respond_to?(method)
134
+ arel.public_send(method, *args, &block)
135
+ else
136
+ super
137
+ end
138
+ end
139
+ end
140
+ end