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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1372 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +218 -0
- data/examples/performance.rb +184 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +173 -0
- data/lib/active_record/aggregations.rb +266 -0
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations.rb +1724 -0
- data/lib/active_record/associations/alias_tracker.rb +87 -0
- data/lib/active_record/associations/association.rb +253 -0
- data/lib/active_record/associations/association_scope.rb +194 -0
- data/lib/active_record/associations/belongs_to_association.rb +111 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +149 -0
- data/lib/active_record/associations/builder/belongs_to.rb +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +91 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +23 -0
- data/lib/active_record/associations/builder/singular_association.rb +38 -0
- data/lib/active_record/associations/collection_association.rb +634 -0
- data/lib/active_record/associations/collection_proxy.rb +1027 -0
- data/lib/active_record/associations/has_many_association.rb +184 -0
- data/lib/active_record/associations/has_many_through_association.rb +238 -0
- data/lib/active_record/associations/has_one_association.rb +105 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +282 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +203 -0
- data/lib/active_record/associations/preloader/association.rb +162 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +96 -0
- data/lib/active_record/associations/singular_association.rb +86 -0
- data/lib/active_record/associations/through_association.rb +96 -0
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +439 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
- data/lib/active_record/attribute_methods/dirty.rb +181 -0
- data/lib/active_record/attribute_methods/primary_key.rb +128 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +103 -0
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +83 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +439 -0
- data/lib/active_record/base.rb +317 -0
- data/lib/active_record/callbacks.rb +313 -0
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
- data/lib/active_record/connection_adapters/column.rb +82 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +566 -0
- data/lib/active_record/counter_cache.rb +175 -0
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +198 -0
- data/lib/active_record/errors.rb +252 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +1007 -0
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +204 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +75 -0
- data/lib/active_record/migration.rb +1051 -0
- data/lib/active_record/migration/command_recorder.rb +197 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +340 -0
- data/lib/active_record/nested_attributes.rb +548 -0
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +532 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +162 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +391 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +881 -0
- data/lib/active_record/relation.rb +681 -0
- data/lib/active_record/relation/batches.rb +138 -0
- data/lib/active_record/relation/calculations.rb +403 -0
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +528 -0
- data/lib/active_record/relation/merger.rb +170 -0
- data/lib/active_record/relation/predicate_builder.rb +126 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +1176 -0
- data/lib/active_record/relation/spawn_methods.rb +75 -0
- data/lib/active_record/result.rb +131 -0
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +64 -0
- data/lib/active_record/schema_dumper.rb +251 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/scoping/default.rb +134 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +193 -0
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +296 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +121 -0
- data/lib/active_record/transactions.rb +417 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/validations.rb +90 -0
- data/lib/active_record/validations/associated.rb +51 -0
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +229 -0
- data/lib/active_record/version.rb +8 -0
- data/lib/rails/generators/active_record.rb +17 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- 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
|