activerecord 4.2.11.3 → 5.0.7.2
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 +4 -4
- data/CHANGELOG.md +1638 -1132
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record.rb +7 -2
- data/lib/active_record/aggregations.rb +34 -21
- data/lib/active_record/association_relation.rb +7 -4
- data/lib/active_record/associations.rb +347 -218
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +22 -10
- data/lib/active_record/associations/association_scope.rb +75 -104
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +13 -11
- data/lib/active_record/associations/collection_association.rb +85 -69
- data/lib/active_record/associations/collection_proxy.rb +104 -46
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +21 -78
- data/lib/active_record/associations/has_many_through_association.rb +6 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +38 -22
- data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +52 -71
- data/lib/active_record/associations/preloader/collection_association.rb +0 -7
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +0 -1
- data/lib/active_record/associations/preloader/through_association.rb +36 -17
- data/lib/active_record/associations/singular_association.rb +13 -1
- data/lib/active_record/associations/through_association.rb +12 -4
- data/lib/active_record/attribute.rb +69 -19
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +69 -44
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +16 -3
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +32 -3
- data/lib/active_record/attribute_set/builder.rb +42 -16
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +54 -17
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +92 -108
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +116 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain.rb +20 -9
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +77 -41
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +17 -14
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +48 -24
- data/lib/active_record/migration.rb +362 -111
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/model_schema.rb +270 -73
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +152 -90
- data/lib/active_record/query_cache.rb +18 -23
- data/lib/active_record/querying.rb +12 -11
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +52 -41
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +302 -115
- data/lib/active_record/relation.rb +187 -120
- data/lib/active_record/relation/batches.rb +141 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +92 -117
- data/lib/active_record/relation/delegation.rb +8 -20
- data/lib/active_record/relation/finder_methods.rb +173 -89
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/result.rb +11 -4
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +105 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +54 -37
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +34 -16
- data/lib/active_record/scoping/default.rb +28 -10
- data/lib/active_record/scoping/named.rb +59 -26
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +3 -5
- data/lib/active_record/statement_cache.rb +17 -15
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +69 -0
- data/lib/active_record/tasks/database_tasks.rb +66 -49
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +63 -0
- data/lib/active_record/transactions.rb +139 -57
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +33 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +15 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +33 -33
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +58 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -1,8 +1,12 @@
|
|
1
|
+
require "active_record/relation/batches/batch_enumerator"
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Batches
|
5
|
+
ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
|
6
|
+
|
3
7
|
# Looping through a collection of records from the database
|
4
|
-
# (using the
|
5
|
-
# since it will try to instantiate all the objects at once.
|
8
|
+
# (using the Scoping::Named::ClassMethods.all method, for example)
|
9
|
+
# is very inefficient since it will try to instantiate all the objects at once.
|
6
10
|
#
|
7
11
|
# In that case, batch processing methods allow you to work
|
8
12
|
# with the records in batches, thereby greatly reducing memory consumption.
|
@@ -26,12 +30,16 @@ module ActiveRecord
|
|
26
30
|
# end
|
27
31
|
#
|
28
32
|
# ==== Options
|
29
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
30
|
-
# * <tt>:start</tt> - Specifies the
|
33
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
34
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
35
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
36
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
37
|
+
# the order and limit have to be ignored due to batching.
|
38
|
+
#
|
31
39
|
# This is especially useful if you want multiple workers dealing with
|
32
40
|
# the same processing queue. You can make worker 1 handle all the records
|
33
41
|
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
34
|
-
# (by setting the +:start+ option on
|
42
|
+
# (by setting the +:start+ and +:finish+ option on each worker).
|
35
43
|
#
|
36
44
|
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
|
37
45
|
# Person.find_each(start: 2000, batch_size: 2000) do |person|
|
@@ -40,24 +48,25 @@ module ActiveRecord
|
|
40
48
|
#
|
41
49
|
# NOTE: It's not possible to set the order. That is automatically set to
|
42
50
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
43
|
-
# work. This also means that this method only works
|
44
|
-
#
|
51
|
+
# work. This also means that this method only works when the primary key is
|
52
|
+
# orderable (e.g. an integer or string).
|
45
53
|
#
|
46
54
|
# NOTE: You can't set the limit either, that's used to control
|
47
55
|
# the batch sizes.
|
48
|
-
def find_each(
|
56
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
49
57
|
if block_given?
|
50
|
-
find_in_batches(
|
58
|
+
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
|
51
59
|
records.each { |record| yield record }
|
52
60
|
end
|
53
61
|
else
|
54
|
-
enum_for
|
55
|
-
|
62
|
+
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
63
|
+
relation = self
|
64
|
+
apply_limits(relation, start, finish).size
|
56
65
|
end
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
60
|
-
# Yields each batch of records that was found by the find
|
69
|
+
# Yields each batch of records that was found by the find options as
|
61
70
|
# an array.
|
62
71
|
#
|
63
72
|
# Person.where("age > 21").find_in_batches do |group|
|
@@ -76,12 +85,16 @@ module ActiveRecord
|
|
76
85
|
# To be yielded each record one by one, use #find_each instead.
|
77
86
|
#
|
78
87
|
# ==== Options
|
79
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
80
|
-
# * <tt>:start</tt> - Specifies the
|
88
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
89
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
90
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
91
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
92
|
+
# the order and limit have to be ignored due to batching.
|
93
|
+
#
|
81
94
|
# This is especially useful if you want multiple workers dealing with
|
82
95
|
# the same processing queue. You can make worker 1 handle all the records
|
83
96
|
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
84
|
-
# (by setting the +:start+ option on
|
97
|
+
# (by setting the +:start+ and +:finish+ option on each worker).
|
85
98
|
#
|
86
99
|
# # Let's process the next 2000 records
|
87
100
|
# Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
|
@@ -90,49 +103,141 @@ module ActiveRecord
|
|
90
103
|
#
|
91
104
|
# NOTE: It's not possible to set the order. That is automatically set to
|
92
105
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
93
|
-
# work. This also means that this method only works
|
94
|
-
#
|
106
|
+
# work. This also means that this method only works when the primary key is
|
107
|
+
# orderable (e.g. an integer or string).
|
95
108
|
#
|
96
109
|
# NOTE: You can't set the limit either, that's used to control
|
97
110
|
# the batch sizes.
|
98
|
-
def find_in_batches(
|
99
|
-
options.assert_valid_keys(:start, :batch_size)
|
100
|
-
|
111
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
101
112
|
relation = self
|
102
|
-
start = options[:start]
|
103
|
-
batch_size = options[:batch_size] || 1000
|
104
|
-
|
105
113
|
unless block_given?
|
106
|
-
return to_enum(:find_in_batches,
|
107
|
-
total = start
|
114
|
+
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
115
|
+
total = apply_limits(relation, start, finish).size
|
108
116
|
(total - 1).div(batch_size) + 1
|
109
117
|
end
|
110
118
|
end
|
111
119
|
|
112
|
-
|
113
|
-
|
120
|
+
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
|
121
|
+
yield batch.to_a
|
114
122
|
end
|
123
|
+
end
|
115
124
|
|
116
|
-
|
117
|
-
|
125
|
+
# Yields ActiveRecord::Relation objects to work with a batch of records.
|
126
|
+
#
|
127
|
+
# Person.where("age > 21").in_batches do |relation|
|
128
|
+
# relation.delete_all
|
129
|
+
# sleep(10) # Throttle the delete queries
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# If you do not provide a block to #in_batches, it will return a
|
133
|
+
# BatchEnumerator which is enumerable.
|
134
|
+
#
|
135
|
+
# Person.in_batches.each_with_index do |relation, batch_index|
|
136
|
+
# puts "Processing relation ##{batch_index}"
|
137
|
+
# relation.delete_all
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# Examples of calling methods on the returned BatchEnumerator object:
|
141
|
+
#
|
142
|
+
# Person.in_batches.delete_all
|
143
|
+
# Person.in_batches.update_all(awesome: true)
|
144
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
145
|
+
#
|
146
|
+
# ==== Options
|
147
|
+
# * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
|
148
|
+
# * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
|
149
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
150
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
151
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
152
|
+
# the order and limit have to be ignored due to batching.
|
153
|
+
#
|
154
|
+
# This is especially useful if you want to work with the
|
155
|
+
# ActiveRecord::Relation object instead of the array of records, or if
|
156
|
+
# you want multiple workers dealing with the same processing queue. You can
|
157
|
+
# make worker 1 handle all the records between id 0 and 10,000 and worker 2
|
158
|
+
# handle from 10,000 and beyond (by setting the +:start+ and +:finish+
|
159
|
+
# option on each worker).
|
160
|
+
#
|
161
|
+
# # Let's process the next 2000 records
|
162
|
+
# Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
|
163
|
+
#
|
164
|
+
# An example of calling where query method on the relation:
|
165
|
+
#
|
166
|
+
# Person.in_batches.each do |relation|
|
167
|
+
# relation.update_all('age = age + 1')
|
168
|
+
# relation.where('age > 21').update_all(should_party: true)
|
169
|
+
# relation.where('age <= 21').delete_all
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# NOTE: If you are going to iterate through each record, you should call
|
173
|
+
# #each_record on the yielded BatchEnumerator:
|
174
|
+
#
|
175
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
176
|
+
#
|
177
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
178
|
+
# ascending on the primary key ("id ASC") to make the batch ordering
|
179
|
+
# consistent. Therefore the primary key must be orderable, e.g. an integer
|
180
|
+
# or a string.
|
181
|
+
#
|
182
|
+
# NOTE: You can't set the limit either, that's used to control the batch
|
183
|
+
# sizes.
|
184
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
185
|
+
relation = self
|
186
|
+
unless block_given?
|
187
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
188
|
+
end
|
189
|
+
|
190
|
+
if arel.orders.present? || arel.taken.present?
|
191
|
+
act_on_order_or_limit_ignored(error_on_ignore)
|
192
|
+
end
|
193
|
+
|
194
|
+
relation = relation.reorder(batch_order).limit(of)
|
195
|
+
relation = apply_limits(relation, start, finish)
|
196
|
+
batch_relation = relation
|
118
197
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
198
|
+
loop do
|
199
|
+
if load
|
200
|
+
records = batch_relation.records
|
201
|
+
ids = records.map(&:id)
|
202
|
+
yielded_relation = self.where(primary_key => ids)
|
203
|
+
yielded_relation.load_records(records)
|
204
|
+
else
|
205
|
+
ids = batch_relation.pluck(primary_key)
|
206
|
+
yielded_relation = self.where(primary_key => ids)
|
207
|
+
end
|
123
208
|
|
124
|
-
|
209
|
+
break if ids.empty?
|
125
210
|
|
126
|
-
|
211
|
+
primary_key_offset = ids.last
|
212
|
+
raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
|
127
213
|
|
128
|
-
|
214
|
+
yield yielded_relation
|
215
|
+
|
216
|
+
break if ids.length < of
|
217
|
+
batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
|
129
218
|
end
|
130
219
|
end
|
131
220
|
|
132
221
|
private
|
133
222
|
|
223
|
+
def apply_limits(relation, start, finish)
|
224
|
+
relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
|
225
|
+
relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
|
226
|
+
relation
|
227
|
+
end
|
228
|
+
|
134
229
|
def batch_order
|
135
230
|
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
136
231
|
end
|
232
|
+
|
233
|
+
def act_on_order_or_limit_ignored(error_on_ignore)
|
234
|
+
raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
|
235
|
+
|
236
|
+
if raise_error
|
237
|
+
raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
|
238
|
+
elsif logger
|
239
|
+
logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
|
240
|
+
end
|
241
|
+
end
|
137
242
|
end
|
138
243
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Batches
|
3
|
+
class BatchEnumerator
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
|
7
|
+
@of = of
|
8
|
+
@relation = relation
|
9
|
+
@start = start
|
10
|
+
@finish = finish
|
11
|
+
end
|
12
|
+
|
13
|
+
# Looping through a collection of records from the database (using the
|
14
|
+
# +all+ method, for example) is very inefficient since it will try to
|
15
|
+
# instantiate all the objects at once.
|
16
|
+
#
|
17
|
+
# In that case, batch processing methods allow you to work with the
|
18
|
+
# records in batches, thereby greatly reducing memory consumption.
|
19
|
+
#
|
20
|
+
# Person.in_batches.each_record do |person|
|
21
|
+
# person.do_awesome_stuff
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Person.where("age > 21").in_batches(of: 10).each_record do |person|
|
25
|
+
# person.party_all_night!
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# If you do not provide a block to #each_record, it will return an Enumerator
|
29
|
+
# for chaining with other methods:
|
30
|
+
#
|
31
|
+
# Person.in_batches.each_record.with_index do |person, index|
|
32
|
+
# person.award_trophy(index + 1)
|
33
|
+
# end
|
34
|
+
def each_record
|
35
|
+
return to_enum(:each_record) unless block_given?
|
36
|
+
|
37
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
|
38
|
+
relation.records.each { |record| yield record }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
|
43
|
+
#
|
44
|
+
# People.in_batches.delete_all
|
45
|
+
# People.where('age < 10').in_batches.destroy_all
|
46
|
+
# People.in_batches.update_all('age = age + 1')
|
47
|
+
[:delete_all, :update_all, :destroy_all].each do |method|
|
48
|
+
define_method(method) do |*args, &block|
|
49
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
|
50
|
+
relation.send(method, *args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Yields an ActiveRecord::Relation object for each batch of records.
|
56
|
+
#
|
57
|
+
# Person.in_batches.each do |relation|
|
58
|
+
# relation.update_all(awesome: true)
|
59
|
+
# end
|
60
|
+
def each
|
61
|
+
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
|
62
|
+
return enum.each { |relation| yield relation } if block_given?
|
63
|
+
enum
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -14,127 +14,115 @@ module ActiveRecord
|
|
14
14
|
# Person.distinct.count(:age)
|
15
15
|
# # => counts the number of different age values
|
16
16
|
#
|
17
|
-
# If
|
17
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
18
|
+
# it returns a Hash whose keys represent the aggregated column,
|
18
19
|
# and the values are the respective amounts:
|
19
20
|
#
|
20
21
|
# Person.group(:city).count
|
21
22
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
22
23
|
#
|
23
|
-
# If
|
24
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
24
25
|
# keys are an array containing the individual values of each column and the value
|
25
|
-
# of each key would be the
|
26
|
+
# of each key would be the #count.
|
26
27
|
#
|
27
28
|
# Article.group(:status, :category).count
|
28
29
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
29
30
|
# ["published", "business"]=>0, ["published", "technology"]=>2}
|
30
31
|
#
|
31
|
-
# If
|
32
|
+
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
32
33
|
#
|
33
34
|
# Person.select(:age).count
|
34
35
|
# # => counts the number of different age values
|
35
36
|
#
|
36
|
-
# Note: not all valid
|
37
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
37
38
|
# between databases. In invalid cases, an error from the database is thrown.
|
38
|
-
def count(column_name = nil
|
39
|
-
|
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.
|
48
|
-
calculate(:count, column_name, options)
|
39
|
+
def count(column_name = nil)
|
40
|
+
calculate(:count, column_name)
|
49
41
|
end
|
50
42
|
|
51
43
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
52
|
-
# no row. See
|
44
|
+
# no row. See #calculate for examples with options.
|
53
45
|
#
|
54
46
|
# Person.average(:age) # => 35.8
|
55
|
-
def average(column_name
|
56
|
-
|
57
|
-
# activerecord-deprecated_finders.
|
58
|
-
calculate(:average, column_name, options)
|
47
|
+
def average(column_name)
|
48
|
+
calculate(:average, column_name)
|
59
49
|
end
|
60
50
|
|
61
51
|
# Calculates the minimum value on a given column. The value is returned
|
62
52
|
# with the same data type of the column, or +nil+ if there's no row. See
|
63
|
-
#
|
53
|
+
# #calculate for examples with options.
|
64
54
|
#
|
65
55
|
# Person.minimum(:age) # => 7
|
66
|
-
def minimum(column_name
|
67
|
-
|
68
|
-
# activerecord-deprecated_finders.
|
69
|
-
calculate(:minimum, column_name, options)
|
56
|
+
def minimum(column_name)
|
57
|
+
calculate(:minimum, column_name)
|
70
58
|
end
|
71
59
|
|
72
60
|
# Calculates the maximum value on a given column. The value is returned
|
73
61
|
# with the same data type of the column, or +nil+ if there's no row. See
|
74
|
-
#
|
62
|
+
# #calculate for examples with options.
|
75
63
|
#
|
76
64
|
# Person.maximum(:age) # => 93
|
77
|
-
def maximum(column_name
|
78
|
-
|
79
|
-
# activerecord-deprecated_finders.
|
80
|
-
calculate(:maximum, column_name, options)
|
65
|
+
def maximum(column_name)
|
66
|
+
calculate(:maximum, column_name)
|
81
67
|
end
|
82
68
|
|
83
69
|
# Calculates the sum of values on a given column. The value is returned
|
84
|
-
# with the same data type of the column, 0 if there's no row. See
|
85
|
-
#
|
70
|
+
# with the same data type of the column, +0+ if there's no row. See
|
71
|
+
# #calculate for examples with options.
|
86
72
|
#
|
87
73
|
# Person.sum(:age) # => 4562
|
88
|
-
def sum(
|
89
|
-
|
74
|
+
def sum(column_name = nil, &block)
|
75
|
+
return super(&block) if block_given?
|
76
|
+
calculate(:sum, column_name)
|
90
77
|
end
|
91
78
|
|
92
|
-
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
93
|
-
# minimum, and maximum have been added as shortcuts.
|
79
|
+
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
80
|
+
# #minimum, and #maximum have been added as shortcuts.
|
94
81
|
#
|
95
|
-
#
|
82
|
+
# Person.calculate(:count, :all) # The same as Person.count
|
83
|
+
# Person.average(:age) # SELECT AVG(age) FROM people...
|
96
84
|
#
|
97
|
-
#
|
98
|
-
#
|
85
|
+
# # Selects the minimum age for any family without any minors
|
86
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
99
87
|
#
|
100
|
-
# *
|
101
|
-
# takes either a column name, or the name of a belongs_to association.
|
88
|
+
# Person.sum("2 * age")
|
102
89
|
#
|
103
|
-
#
|
104
|
-
# puts values["Drake"]
|
105
|
-
# # => 43
|
90
|
+
# There are two basic forms of output:
|
106
91
|
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# puts values[drake]
|
110
|
-
# # => 43
|
92
|
+
# * Single aggregate value: The single value is type cast to Integer for COUNT, Float
|
93
|
+
# for AVG, and the given column's type for everything else.
|
111
94
|
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# end
|
95
|
+
# * Grouped values: This returns an ordered hash of the values and groups them. It
|
96
|
+
# takes either a column name, or the name of a belongs_to association.
|
115
97
|
#
|
116
|
-
#
|
117
|
-
#
|
98
|
+
# values = Person.group('last_name').maximum(:age)
|
99
|
+
# puts values["Drake"]
|
100
|
+
# # => 43
|
118
101
|
#
|
119
|
-
#
|
120
|
-
#
|
102
|
+
# drake = Family.find_by(last_name: 'Drake')
|
103
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
104
|
+
# puts values[drake]
|
105
|
+
# # => 43
|
121
106
|
#
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
107
|
+
# values.each do |family, max_age|
|
108
|
+
# ...
|
109
|
+
# end
|
110
|
+
def calculate(operation, column_name)
|
126
111
|
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
127
112
|
column_name = attribute_alias(column_name)
|
128
113
|
end
|
129
114
|
|
130
115
|
if has_include?(column_name)
|
131
|
-
|
116
|
+
relation = construct_relation_for_association_calculations
|
117
|
+
relation = relation.distinct if operation.to_s.downcase == "count"
|
118
|
+
|
119
|
+
relation.calculate(operation, column_name)
|
132
120
|
else
|
133
|
-
perform_calculation(operation, column_name
|
121
|
+
perform_calculation(operation, column_name)
|
134
122
|
end
|
135
123
|
end
|
136
124
|
|
137
|
-
# Use
|
125
|
+
# Use #pluck as a shortcut to select one or more attributes without
|
138
126
|
# loading a bunch of records just to grab the attributes you want.
|
139
127
|
#
|
140
128
|
# Person.pluck(:name)
|
@@ -143,19 +131,19 @@ module ActiveRecord
|
|
143
131
|
#
|
144
132
|
# Person.all.map(&:name)
|
145
133
|
#
|
146
|
-
# Pluck returns an
|
134
|
+
# Pluck returns an Array of attribute values type-casted to match
|
147
135
|
# the plucked column names, if they can be deduced. Plucking an SQL fragment
|
148
136
|
# returns String values by default.
|
149
137
|
#
|
150
|
-
# Person.pluck(:
|
151
|
-
# # SELECT people.
|
152
|
-
# # => [
|
138
|
+
# Person.pluck(:name)
|
139
|
+
# # SELECT people.name FROM people
|
140
|
+
# # => ['David', 'Jeremy', 'Jose']
|
153
141
|
#
|
154
142
|
# Person.pluck(:id, :name)
|
155
143
|
# # SELECT people.id, people.name FROM people
|
156
144
|
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
|
157
145
|
#
|
158
|
-
# Person.pluck(
|
146
|
+
# Person.distinct.pluck(:role)
|
159
147
|
# # SELECT DISTINCT role FROM people
|
160
148
|
# # => ['admin', 'member', 'guest']
|
161
149
|
#
|
@@ -167,13 +155,11 @@ module ActiveRecord
|
|
167
155
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
168
156
|
# # => ['0', '27761', '173']
|
169
157
|
#
|
158
|
+
# See also #ids.
|
159
|
+
#
|
170
160
|
def pluck(*column_names)
|
171
|
-
column_names.map
|
172
|
-
|
173
|
-
attribute_alias(column_name)
|
174
|
-
else
|
175
|
-
column_name.to_s
|
176
|
-
end
|
161
|
+
if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
162
|
+
return records.pluck(*column_names)
|
177
163
|
end
|
178
164
|
|
179
165
|
if has_include?(column_names.first)
|
@@ -181,10 +167,10 @@ module ActiveRecord
|
|
181
167
|
else
|
182
168
|
relation = spawn
|
183
169
|
relation.select_values = column_names.map { |cn|
|
184
|
-
|
170
|
+
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
|
185
171
|
}
|
186
|
-
result = klass.connection.select_all(relation.arel, nil,
|
187
|
-
result.cast_values(klass.
|
172
|
+
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
|
173
|
+
result.cast_values(klass.attribute_types)
|
188
174
|
end
|
189
175
|
end
|
190
176
|
|
@@ -199,24 +185,18 @@ module ActiveRecord
|
|
199
185
|
private
|
200
186
|
|
201
187
|
def has_include?(column_name)
|
202
|
-
eager_loading? || (includes_values.present? &&
|
188
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
203
189
|
end
|
204
190
|
|
205
|
-
def perform_calculation(operation, column_name
|
206
|
-
# TODO: Remove options argument as soon we remove support to
|
207
|
-
# activerecord-deprecated_finders.
|
191
|
+
def perform_calculation(operation, column_name)
|
208
192
|
operation = operation.to_s.downcase
|
209
193
|
|
210
|
-
# If #count is used with #distinct
|
194
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
195
|
+
# considered distinct.
|
211
196
|
distinct = self.distinct_value
|
212
197
|
|
213
198
|
if operation == "count"
|
214
199
|
column_name ||= select_for_count
|
215
|
-
|
216
|
-
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
217
|
-
distinct = true
|
218
|
-
end
|
219
|
-
|
220
200
|
column_name = primary_key if column_name == :all && distinct
|
221
201
|
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
|
222
202
|
end
|
@@ -229,6 +209,8 @@ module ActiveRecord
|
|
229
209
|
end
|
230
210
|
|
231
211
|
def aggregate_column(column_name)
|
212
|
+
return column_name if Arel::Expressions === column_name
|
213
|
+
|
232
214
|
if @klass.column_names.include?(column_name.to_s)
|
233
215
|
Arel::Attribute.new(@klass.unscoped.table, column_name)
|
234
216
|
else
|
@@ -241,33 +223,33 @@ module ActiveRecord
|
|
241
223
|
end
|
242
224
|
|
243
225
|
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
244
|
-
#
|
226
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
245
227
|
relation = unscope(:order)
|
246
228
|
|
247
229
|
column_alias = column_name
|
248
230
|
|
249
|
-
bind_values = nil
|
250
|
-
|
251
231
|
if operation == "count" && (relation.limit_value || relation.offset_value)
|
252
232
|
# Shortcut when limit is zero.
|
253
233
|
return 0 if relation.limit_value == 0
|
254
234
|
|
255
235
|
query_builder = build_count_subquery(relation, column_name, distinct)
|
256
|
-
bind_values = query_builder.bind_values + relation.bind_values
|
257
236
|
else
|
258
237
|
column = aggregate_column(column_name)
|
259
238
|
|
260
239
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
261
240
|
|
241
|
+
if operation == "sum" && distinct
|
242
|
+
select_value.distinct = true
|
243
|
+
end
|
244
|
+
|
262
245
|
column_alias = select_value.alias
|
263
246
|
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
264
247
|
relation.select_values = [select_value]
|
265
248
|
|
266
249
|
query_builder = relation.arel
|
267
|
-
bind_values = query_builder.bind_values + relation.bind_values
|
268
250
|
end
|
269
251
|
|
270
|
-
result = @klass.connection.select_all(query_builder, nil,
|
252
|
+
result = @klass.connection.select_all(query_builder, nil, bound_attributes)
|
271
253
|
row = result.first
|
272
254
|
value = row && row.values.first
|
273
255
|
column = result.column_types.fetch(column_alias) do
|
@@ -289,14 +271,8 @@ module ActiveRecord
|
|
289
271
|
end
|
290
272
|
group_fields = arel_columns(group_fields)
|
291
273
|
|
292
|
-
group_aliases = group_fields.map { |field|
|
293
|
-
|
294
|
-
}
|
295
|
-
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
296
|
-
[aliaz, field]
|
297
|
-
}
|
298
|
-
|
299
|
-
group = group_fields
|
274
|
+
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
275
|
+
group_columns = group_aliases.zip(group_fields)
|
300
276
|
|
301
277
|
if operation == 'count' && column_name == :all
|
302
278
|
aggregate_alias = 'count_all'
|
@@ -310,9 +286,9 @@ module ActiveRecord
|
|
310
286
|
operation,
|
311
287
|
distinct).as(aggregate_alias)
|
312
288
|
]
|
313
|
-
select_values += self.select_values unless
|
289
|
+
select_values += self.select_values unless having_clause.empty?
|
314
290
|
|
315
|
-
select_values.concat
|
291
|
+
select_values.concat group_columns.map { |aliaz, field|
|
316
292
|
if field.respond_to?(:as)
|
317
293
|
field.as(aliaz)
|
318
294
|
else
|
@@ -321,21 +297,23 @@ module ActiveRecord
|
|
321
297
|
}
|
322
298
|
|
323
299
|
relation = except(:group)
|
324
|
-
relation.group_values =
|
300
|
+
relation.group_values = group_fields
|
325
301
|
relation.select_values = select_values
|
326
302
|
|
327
|
-
calculated_data = @klass.connection.select_all(relation, nil, relation.
|
303
|
+
calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
|
328
304
|
|
329
305
|
if association
|
330
306
|
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
331
|
-
key_records = association.klass.base_class.
|
307
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
332
308
|
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
333
309
|
end
|
334
310
|
|
335
311
|
Hash[calculated_data.map do |row|
|
336
312
|
key = group_columns.map { |aliaz, col_name|
|
337
|
-
column =
|
338
|
-
|
313
|
+
column = type_for(col_name) do
|
314
|
+
calculated_data.column_types.fetch(aliaz) do
|
315
|
+
Type::Value.new
|
316
|
+
end
|
339
317
|
end
|
340
318
|
type_cast_calculated_value(row[aliaz], column)
|
341
319
|
}
|
@@ -354,7 +332,6 @@ module ActiveRecord
|
|
354
332
|
# column_alias_for("sum(id)") # => "sum_id"
|
355
333
|
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
356
334
|
# column_alias_for("count(*)") # => "count_all"
|
357
|
-
# column_alias_for("count", "id") # => "count_id"
|
358
335
|
def column_alias_for(keys)
|
359
336
|
if keys.respond_to? :name
|
360
337
|
keys = "#{keys.relation.name}.#{keys.name}"
|
@@ -369,23 +346,23 @@ module ActiveRecord
|
|
369
346
|
@klass.connection.table_alias_for(table_name)
|
370
347
|
end
|
371
348
|
|
372
|
-
def type_for(field)
|
349
|
+
def type_for(field, &block)
|
373
350
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
374
|
-
@klass.type_for_attribute(field_name)
|
351
|
+
@klass.type_for_attribute(field_name, &block)
|
375
352
|
end
|
376
353
|
|
377
354
|
def type_cast_calculated_value(value, type, operation = nil)
|
378
355
|
case operation
|
379
356
|
when 'count' then value.to_i
|
380
|
-
when 'sum' then type.
|
357
|
+
when 'sum' then type.deserialize(value || 0)
|
381
358
|
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
|
382
|
-
else type.
|
359
|
+
else type.deserialize(value)
|
383
360
|
end
|
384
361
|
end
|
385
362
|
|
386
|
-
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
|
387
363
|
def select_for_count
|
388
364
|
if select_values.present?
|
365
|
+
return select_values.first if select_values.one?
|
389
366
|
select_values.join(", ")
|
390
367
|
else
|
391
368
|
:all
|
@@ -398,11 +375,9 @@ module ActiveRecord
|
|
398
375
|
|
399
376
|
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
|
400
377
|
relation.select_values = [aliased_column]
|
401
|
-
|
402
|
-
subquery = arel.as(subquery_alias)
|
378
|
+
subquery = relation.arel.as(subquery_alias)
|
403
379
|
|
404
380
|
sm = Arel::SelectManager.new relation.engine
|
405
|
-
sm.bind_values = arel.bind_values
|
406
381
|
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
|
407
382
|
sm.project(select_value).from(subquery)
|
408
383
|
end
|