activerecord 4.2.0

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

Potentially problematic release.


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

Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,138 @@
1
+ module ActiveRecord
2
+ module Batches
3
+ # Looping through a collection of records from the database
4
+ # (using the +all+ method, for example) is very inefficient
5
+ # since it will try to instantiate all the objects at once.
6
+ #
7
+ # In that case, batch processing methods allow you to work
8
+ # with the records in batches, thereby greatly reducing memory consumption.
9
+ #
10
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
11
+ # specified by the +:batch_size+ option).
12
+ #
13
+ # Person.find_each do |person|
14
+ # person.do_awesome_stuff
15
+ # end
16
+ #
17
+ # Person.where("age > 21").find_each do |person|
18
+ # person.party_all_night!
19
+ # end
20
+ #
21
+ # If you do not provide a block to #find_each, it will return an Enumerator
22
+ # for chaining with other methods:
23
+ #
24
+ # Person.find_each.with_index do |person, index|
25
+ # person.award_trophy(index + 1)
26
+ # end
27
+ #
28
+ # ==== Options
29
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
30
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
31
+ # This is especially useful if you want multiple workers dealing with
32
+ # the same processing queue. You can make worker 1 handle all the records
33
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
34
+ # (by setting the +:start+ option on that worker).
35
+ #
36
+ # # Let's process for a batch of 2000 records, skipping the first 2000 rows
37
+ # Person.find_each(start: 2000, batch_size: 2000) do |person|
38
+ # person.party_all_night!
39
+ # end
40
+ #
41
+ # NOTE: It's not possible to set the order. That is automatically set to
42
+ # ascending on the primary key ("id ASC") to make the batch ordering
43
+ # work. This also means that this method only works with integer-based
44
+ # primary keys.
45
+ #
46
+ # NOTE: You can't set the limit either, that's used to control
47
+ # the batch sizes.
48
+ def find_each(options = {})
49
+ if block_given?
50
+ find_in_batches(options) do |records|
51
+ records.each { |record| yield record }
52
+ end
53
+ else
54
+ enum_for :find_each, options do
55
+ options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
56
+ end
57
+ end
58
+ end
59
+
60
+ # Yields each batch of records that was found by the find +options+ as
61
+ # an array.
62
+ #
63
+ # Person.where("age > 21").find_in_batches do |group|
64
+ # sleep(50) # Make sure it doesn't get too crowded in there!
65
+ # group.each { |person| person.party_all_night! }
66
+ # end
67
+ #
68
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
69
+ # for chaining with other methods:
70
+ #
71
+ # Person.find_in_batches.with_index do |group, batch|
72
+ # puts "Processing group ##{batch}"
73
+ # group.each(&:recover_from_last_night!)
74
+ # end
75
+ #
76
+ # To be yielded each record one by one, use #find_each instead.
77
+ #
78
+ # ==== Options
79
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
80
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
81
+ # This is especially useful if you want multiple workers dealing with
82
+ # the same processing queue. You can make worker 1 handle all the records
83
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
84
+ # (by setting the +:start+ option on that worker).
85
+ #
86
+ # # Let's process the next 2000 records
87
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
88
+ # group.each { |person| person.party_all_night! }
89
+ # end
90
+ #
91
+ # NOTE: It's not possible to set the order. That is automatically set to
92
+ # ascending on the primary key ("id ASC") to make the batch ordering
93
+ # work. This also means that this method only works with integer-based
94
+ # primary keys.
95
+ #
96
+ # NOTE: You can't set the limit either, that's used to control
97
+ # the batch sizes.
98
+ def find_in_batches(options = {})
99
+ options.assert_valid_keys(:start, :batch_size)
100
+
101
+ relation = self
102
+ start = options[:start]
103
+ batch_size = options[:batch_size] || 1000
104
+
105
+ unless block_given?
106
+ return to_enum(:find_in_batches, options) do
107
+ total = start ? where(table[primary_key].gteq(start)).size : size
108
+ (total - 1).div(batch_size) + 1
109
+ end
110
+ end
111
+
112
+ if logger && (arel.orders.present? || arel.taken.present?)
113
+ logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
114
+ end
115
+
116
+ relation = relation.reorder(batch_order).limit(batch_size)
117
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
118
+
119
+ while records.any?
120
+ records_size = records.size
121
+ primary_key_offset = records.last.id
122
+ raise "Primary key not included in the custom select clause" unless primary_key_offset
123
+
124
+ yield records
125
+
126
+ break if records_size < batch_size
127
+
128
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def batch_order
135
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,403 @@
1
+ module ActiveRecord
2
+ module Calculations
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.
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)
43
+ end
44
+
45
+ # Calculates the average value on a given column. Returns +nil+ if there's
46
+ # no row. See +calculate+ for examples with options.
47
+ #
48
+ # 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)
53
+ end
54
+
55
+ # Calculates the minimum value on a given column. The value is returned
56
+ # with the same data type of the column, or +nil+ if there's no row. See
57
+ # +calculate+ for examples with options.
58
+ #
59
+ # 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)
64
+ end
65
+
66
+ # Calculates the maximum value on a given column. The value is returned
67
+ # with the same data type of the column, or +nil+ if there's no row. See
68
+ # +calculate+ for examples with options.
69
+ #
70
+ # 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)
75
+ end
76
+
77
+ # 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.
80
+ #
81
+ # Person.sum(:age) # => 4562
82
+ def sum(*args)
83
+ calculate(:sum, *args)
84
+ end
85
+
86
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
87
+ # minimum, and maximum have been added as shortcuts.
88
+ #
89
+ # There are two basic forms of output:
90
+ #
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.
93
+ #
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.
96
+ #
97
+ # values = Person.group('last_name').maximum(:age)
98
+ # puts values["Drake"]
99
+ # # => 43
100
+ #
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
105
+ #
106
+ # values.each do |family, max_age|
107
+ # ...
108
+ # end
109
+ #
110
+ # Person.calculate(:count, :all) # The same as Person.count
111
+ # Person.average(:age) # SELECT AVG(age) FROM people...
112
+ #
113
+ # # Selects the minimum age for any family without any minors
114
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
115
+ #
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
+
124
+ if has_include?(column_name)
125
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
126
+ else
127
+ perform_calculation(operation, column_name, options)
128
+ end
129
+ end
130
+
131
+ # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
132
+ # loading a bunch of records just to grab the attributes you want.
133
+ #
134
+ # Person.pluck(:name)
135
+ #
136
+ # instead of
137
+ #
138
+ # Person.all.map(&:name)
139
+ #
140
+ # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
141
+ # the plucked column names, if they can be deduced. Plucking an SQL fragment
142
+ # returns String values by default.
143
+ #
144
+ # Person.pluck(:id)
145
+ # # SELECT people.id FROM people
146
+ # # => [1, 2, 3]
147
+ #
148
+ # Person.pluck(:id, :name)
149
+ # # SELECT people.id, people.name FROM people
150
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
151
+ #
152
+ # Person.pluck('DISTINCT role')
153
+ # # SELECT DISTINCT role FROM people
154
+ # # => ['admin', 'member', 'guest']
155
+ #
156
+ # Person.where(age: 21).limit(5).pluck(:id)
157
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
158
+ # # => [2, 3]
159
+ #
160
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
161
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
162
+ # # => ['0', '27761', '173']
163
+ #
164
+ 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
171
+ end
172
+
173
+ if has_include?(column_names.first)
174
+ construct_relation_for_association_calculations.pluck(*column_names)
175
+ else
176
+ 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)
182
+ end
183
+ end
184
+
185
+ # Pluck all the ID's for the relation using the table's primary key
186
+ #
187
+ # Person.ids # SELECT people.id FROM people
188
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
189
+ def ids
190
+ pluck primary_key
191
+ end
192
+
193
+ private
194
+
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
209
+
210
+ unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
211
+ distinct = true
212
+ end
213
+
214
+ column_name = primary_key if column_name == :all && distinct
215
+ distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
216
+ end
217
+
218
+ if group_values.any?
219
+ execute_grouped_calculation(operation, column_name, distinct)
220
+ else
221
+ execute_simple_calculation(operation, column_name, distinct)
222
+ end
223
+ end
224
+
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)
230
+ end
231
+ end
232
+
233
+ def operation_over_aggregate_column(column, operation, distinct)
234
+ operation == 'count' ? column.count(distinct) : column.send(operation)
235
+ end
236
+
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)
240
+
241
+ column_alias = column_name
242
+
243
+ bind_values = nil
244
+
245
+ if operation == "count" && (relation.limit_value || relation.offset_value)
246
+ # Shortcut when limit is zero.
247
+ return 0 if relation.limit_value == 0
248
+
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)
253
+
254
+ select_value = operation_over_aggregate_column(column, operation, distinct)
255
+
256
+ column_alias = select_value.alias
257
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
258
+ relation.select_values = [select_value]
259
+
260
+ query_builder = relation.arel
261
+ bind_values = query_builder.bind_values + relation.bind_values
262
+ end
263
+
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
270
+
271
+ type_cast_calculated_value(value, column, operation)
272
+ end
273
+
274
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
275
+ group_attrs = group_values
276
+
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
284
+
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
+ }
291
+
292
+ group = group_fields
293
+
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
299
+
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}"
313
+ 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
+
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] }]
326
+ end
327
+
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
337
+
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
342
+
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}"
354
+ end
355
+
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
369
+
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)
376
+ end
377
+ end
378
+
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
385
+ end
386
+ end
387
+
388
+ def build_count_subquery(relation, column_name, distinct)
389
+ column_alias = Arel.sql('count_column')
390
+ subquery_alias = Arel.sql('subquery_for_count')
391
+
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)
396
+
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
402
+ end
403
+ end