activerecord 3.0.0 → 4.0.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 +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -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_and_belongs_to_many.rb +60 -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 +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- 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 +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,79 +1,83 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
|
-
module Batches
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
3
|
+
module Batches
|
4
|
+
# Looping through a collection of records from the database
|
5
|
+
# (using the +all+ method, for example) is very inefficient
|
6
|
+
# since it will try to instantiate all the objects at once.
|
8
7
|
#
|
9
|
-
#
|
8
|
+
# In that case, batch processing methods allow you to work
|
9
|
+
# with the records in batches, thereby greatly reducing memory consumption.
|
10
|
+
#
|
11
|
+
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
|
12
|
+
# specified by the +:batch_size+ option).
|
13
|
+
#
|
14
|
+
# Person.all.find_each do |person|
|
15
|
+
# person.do_awesome_stuff
|
16
|
+
# end
|
10
17
|
#
|
11
18
|
# Person.where("age > 21").find_each do |person|
|
12
19
|
# person.party_all_night!
|
13
20
|
# end
|
14
21
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# you just need to loop over less than 1000 records, it's probably
|
18
|
-
# better just to use the regular find methods.
|
22
|
+
# You can also pass the +:start+ option to specify
|
23
|
+
# an offset to control the starting point.
|
19
24
|
def find_each(options = {})
|
20
25
|
find_in_batches(options) do |records|
|
21
26
|
records.each { |record| yield record }
|
22
27
|
end
|
23
|
-
|
24
|
-
self
|
25
28
|
end
|
26
29
|
|
27
30
|
# Yields each batch of records that was found by the find +options+ as
|
28
|
-
# an array. The size of each batch is set by the
|
31
|
+
# an array. The size of each batch is set by the +:batch_size+
|
29
32
|
# option; the default is 1000.
|
30
33
|
#
|
31
34
|
# You can control the starting point for the batch processing by
|
32
|
-
# supplying the
|
35
|
+
# supplying the +:start+ option. This is especially useful if you
|
33
36
|
# want multiple workers dealing with the same processing queue. You can
|
34
37
|
# make worker 1 handle all the records between id 0 and 10,000 and
|
35
|
-
# worker 2 handle from 10,000 and beyond (by setting the
|
38
|
+
# worker 2 handle from 10,000 and beyond (by setting the +:start+
|
36
39
|
# option on that worker).
|
37
40
|
#
|
38
41
|
# It's not possible to set the order. That is automatically set to
|
39
42
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
40
|
-
# work. This also
|
43
|
+
# work. This also means that this method only works with integer-based
|
41
44
|
# primary keys. You can't set the limit either, that's used to control
|
42
|
-
# the
|
43
|
-
#
|
44
|
-
# Example:
|
45
|
+
# the batch sizes.
|
45
46
|
#
|
46
47
|
# Person.where("age > 21").find_in_batches do |group|
|
47
48
|
# sleep(50) # Make sure it doesn't get too crowded in there!
|
48
49
|
# group.each { |person| person.party_all_night! }
|
49
50
|
# end
|
51
|
+
#
|
52
|
+
# # Let's process the next 2000 records
|
53
|
+
# Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
|
54
|
+
# group.each { |person| person.party_all_night! }
|
55
|
+
# end
|
50
56
|
def find_in_batches(options = {})
|
57
|
+
options.assert_valid_keys(:start, :batch_size)
|
58
|
+
|
51
59
|
relation = self
|
52
60
|
|
53
|
-
|
61
|
+
unless arel.orders.blank? && arel.taken.blank?
|
54
62
|
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
55
63
|
end
|
56
64
|
|
57
|
-
|
58
|
-
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
|
59
|
-
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
|
60
|
-
|
61
|
-
relation = apply_finder_options(finder_options)
|
62
|
-
end
|
63
|
-
|
64
|
-
start = options.delete(:start).to_i
|
65
|
+
start = options.delete(:start)
|
65
66
|
batch_size = options.delete(:batch_size) || 1000
|
66
67
|
|
67
|
-
relation = relation.
|
68
|
-
records = relation.where(primary_key.gteq(start)).
|
68
|
+
relation = relation.reorder(batch_order).limit(batch_size)
|
69
|
+
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
|
69
70
|
|
70
71
|
while records.any?
|
72
|
+
records_size = records.size
|
73
|
+
primary_key_offset = records.last.id
|
74
|
+
|
71
75
|
yield records
|
72
76
|
|
73
|
-
break if
|
77
|
+
break if records_size < batch_size
|
74
78
|
|
75
|
-
if primary_key_offset
|
76
|
-
records = relation.where(primary_key.gt(primary_key_offset)).to_a
|
79
|
+
if primary_key_offset
|
80
|
+
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
|
77
81
|
else
|
78
82
|
raise "Primary key not included in the custom select clause"
|
79
83
|
end
|
@@ -83,7 +87,7 @@ module ActiveRecord
|
|
83
87
|
private
|
84
88
|
|
85
89
|
def batch_order
|
86
|
-
"#{
|
90
|
+
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
87
91
|
end
|
88
92
|
end
|
89
93
|
end
|
@@ -1,58 +1,24 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
|
-
require 'active_support/core_ext/object/try'
|
3
|
-
|
4
1
|
module ActiveRecord
|
5
2
|
module Calculations
|
6
|
-
# Count
|
7
|
-
#
|
8
|
-
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
|
9
|
-
# * Count using column: By passing a column name to count, it will return a count of all the
|
10
|
-
# rows for the model with supplied column present.
|
11
|
-
# * Count using options will find the row count matched by the options used.
|
12
|
-
#
|
13
|
-
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
|
14
|
-
#
|
15
|
-
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
|
16
|
-
# See conditions in the intro to ActiveRecord::Base.
|
17
|
-
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
|
18
|
-
# (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
|
19
|
-
# perform an INNER JOIN on the associated table(s). If the value is a string, then the records
|
20
|
-
# will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
21
|
-
# Pass <tt>:readonly => false</tt> to override.
|
22
|
-
# * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
|
23
|
-
# The symbols named refer to already defined associations. When using named associations, count
|
24
|
-
# returns the number of DISTINCT items for the model you're counting.
|
25
|
-
# See eager loading under Associations.
|
26
|
-
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
27
|
-
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
28
|
-
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
|
29
|
-
# want to do a join but not include the joined columns.
|
30
|
-
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
|
31
|
-
# SELECT COUNT(DISTINCT posts.id) ...
|
32
|
-
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
|
33
|
-
# alternate table name (or even the name of a database view).
|
34
|
-
#
|
35
|
-
# Examples for counting all:
|
36
|
-
# Person.count # returns the total count of all people
|
3
|
+
# Count the records.
|
37
4
|
#
|
38
|
-
#
|
39
|
-
#
|
5
|
+
# Person.count
|
6
|
+
# # => the total count of all people
|
40
7
|
#
|
41
|
-
#
|
42
|
-
#
|
8
|
+
# Person.count(:age)
|
9
|
+
# # => returns the total count of all people whose age is present in database
|
43
10
|
#
|
44
|
-
#
|
45
|
-
#
|
11
|
+
# Person.count(:all)
|
12
|
+
# # => performs a COUNT(*) (:all is an alias for '*')
|
46
13
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
# :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
|
14
|
+
# Person.distinct.count(:age)
|
15
|
+
# # => counts the number of different age values
|
50
16
|
#
|
51
|
-
#
|
52
|
-
#
|
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:
|
53
19
|
#
|
54
|
-
#
|
55
|
-
#
|
20
|
+
# Person.group(:city).count
|
21
|
+
# # => { 'Rome' => 5, 'Paris' => 3 }
|
56
22
|
def count(column_name = nil, options = {})
|
57
23
|
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
58
24
|
calculate(:count, column_name, options)
|
@@ -66,7 +32,7 @@ module ActiveRecord
|
|
66
32
|
calculate(:average, column_name, options)
|
67
33
|
end
|
68
34
|
|
69
|
-
# Calculates the minimum value on a given column.
|
35
|
+
# Calculates the minimum value on a given column. The value is returned
|
70
36
|
# with the same data type of the column, or +nil+ if there's no row. See
|
71
37
|
# +calculate+ for examples with options.
|
72
38
|
#
|
@@ -89,150 +55,285 @@ module ActiveRecord
|
|
89
55
|
# +calculate+ for examples with options.
|
90
56
|
#
|
91
57
|
# Person.sum('age') # => 4562
|
92
|
-
def sum(
|
93
|
-
|
58
|
+
def sum(*args)
|
59
|
+
if block_given?
|
60
|
+
ActiveSupport::Deprecation.warn(
|
61
|
+
"Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
|
62
|
+
"If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
|
63
|
+
)
|
64
|
+
self.to_a.sum(*args) {|*block_args| yield(*block_args)}
|
65
|
+
else
|
66
|
+
calculate(:sum, *args)
|
67
|
+
end
|
94
68
|
end
|
95
69
|
|
96
|
-
# This calculates aggregate values in the given column.
|
97
|
-
# minimum, and maximum have been added as shortcuts.
|
98
|
-
# <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
|
70
|
+
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
71
|
+
# minimum, and maximum have been added as shortcuts.
|
99
72
|
#
|
100
73
|
# There are two basic forms of output:
|
74
|
+
#
|
101
75
|
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
|
102
76
|
# for AVG, and the given column's type for everything else.
|
103
|
-
# * Grouped values: This returns an ordered hash of the values and groups them by the
|
104
|
-
# <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
|
105
77
|
#
|
106
|
-
#
|
78
|
+
# * Grouped values: This returns an ordered hash of the values and groups them. It
|
79
|
+
# takes either a column name, or the name of a belongs_to association.
|
80
|
+
#
|
81
|
+
# values = Person.group('last_name').maximum(:age)
|
107
82
|
# puts values["Drake"]
|
108
|
-
# => 43
|
83
|
+
# # => 43
|
109
84
|
#
|
110
|
-
# drake = Family.
|
111
|
-
# values = Person.maximum(:age
|
85
|
+
# drake = Family.find_by(last_name: 'Drake')
|
86
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
112
87
|
# puts values[drake]
|
113
|
-
# => 43
|
88
|
+
# # => 43
|
114
89
|
#
|
115
90
|
# values.each do |family, max_age|
|
116
91
|
# ...
|
117
92
|
# end
|
118
93
|
#
|
119
|
-
# Options:
|
120
|
-
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
|
121
|
-
# See conditions in the intro to ActiveRecord::Base.
|
122
|
-
# * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
|
123
|
-
# the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
|
124
|
-
# * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
|
125
|
-
# (Rarely needed).
|
126
|
-
# The records will be returned read-only since they will have attributes that do not correspond to the
|
127
|
-
# table's columns.
|
128
|
-
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
129
|
-
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
130
|
-
# * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
|
131
|
-
# want to do a join, but not include the joined columns.
|
132
|
-
# * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
|
133
|
-
# SELECT COUNT(DISTINCT posts.id) ...
|
134
|
-
#
|
135
|
-
# Examples:
|
136
94
|
# Person.calculate(:count, :all) # The same as Person.count
|
137
95
|
# Person.average(:age) # SELECT AVG(age) FROM people...
|
138
|
-
# Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
|
139
|
-
# # everyone with a last name other than 'Drake'
|
140
96
|
#
|
141
97
|
# # Selects the minimum age for any family without any minors
|
142
|
-
# Person.
|
98
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
143
99
|
#
|
144
100
|
# Person.sum("2 * age")
|
145
101
|
def calculate(operation, column_name, options = {})
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
if
|
102
|
+
relation = with_default_scope
|
103
|
+
|
104
|
+
if relation.equal?(self)
|
105
|
+
if has_include?(column_name)
|
150
106
|
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
151
107
|
else
|
152
108
|
perform_calculation(operation, column_name, options)
|
153
109
|
end
|
110
|
+
else
|
111
|
+
relation.calculate(operation, column_name, options)
|
154
112
|
end
|
155
113
|
rescue ThrowResult
|
156
114
|
0
|
157
115
|
end
|
158
116
|
|
117
|
+
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
|
118
|
+
# loading a bunch of records just to grab the attributes you want.
|
119
|
+
#
|
120
|
+
# Person.pluck(:name)
|
121
|
+
#
|
122
|
+
# instead of
|
123
|
+
#
|
124
|
+
# Person.all.map(&:name)
|
125
|
+
#
|
126
|
+
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
|
127
|
+
# the plucked column names, if they can be deduced. Plucking an SQL fragment
|
128
|
+
# returns String values by default.
|
129
|
+
#
|
130
|
+
# Person.pluck(:id)
|
131
|
+
# # SELECT people.id FROM people
|
132
|
+
# # => [1, 2, 3]
|
133
|
+
#
|
134
|
+
# Person.pluck(:id, :name)
|
135
|
+
# # SELECT people.id, people.name FROM people
|
136
|
+
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
|
137
|
+
#
|
138
|
+
# Person.pluck('DISTINCT role')
|
139
|
+
# # SELECT DISTINCT role FROM people
|
140
|
+
# # => ['admin', 'member', 'guest']
|
141
|
+
#
|
142
|
+
# Person.where(age: 21).limit(5).pluck(:id)
|
143
|
+
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
144
|
+
# # => [2, 3]
|
145
|
+
#
|
146
|
+
# Person.pluck('DATEDIFF(updated_at, created_at)')
|
147
|
+
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
148
|
+
# # => ['0', '27761', '173']
|
149
|
+
#
|
150
|
+
def pluck(*column_names)
|
151
|
+
column_names.map! do |column_name|
|
152
|
+
if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
|
153
|
+
"#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
|
154
|
+
else
|
155
|
+
column_name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if has_include?(column_names.first)
|
160
|
+
construct_relation_for_association_calculations.pluck(*column_names)
|
161
|
+
else
|
162
|
+
relation = spawn
|
163
|
+
relation.select_values = column_names
|
164
|
+
result = klass.connection.select_all(relation.arel, nil, bind_values)
|
165
|
+
columns = result.columns.map do |key|
|
166
|
+
klass.column_types.fetch(key) {
|
167
|
+
result.column_types.fetch(key) {
|
168
|
+
Class.new { def type_cast(v); v; end }.new
|
169
|
+
}
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
result = result.map do |attributes|
|
174
|
+
values = klass.initialize_attributes(attributes).values
|
175
|
+
|
176
|
+
columns.zip(values).map do |column, value|
|
177
|
+
column.type_cast(value)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
columns.one? ? result.map!(&:first) : result
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Pluck all the ID's for the relation using the table's primary key
|
185
|
+
#
|
186
|
+
# Person.ids # SELECT people.id FROM people
|
187
|
+
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
|
188
|
+
def ids
|
189
|
+
pluck primary_key
|
190
|
+
end
|
191
|
+
|
159
192
|
private
|
160
193
|
|
194
|
+
def has_include?(column_name)
|
195
|
+
eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
|
196
|
+
end
|
197
|
+
|
161
198
|
def perform_calculation(operation, column_name, options = {})
|
162
199
|
operation = operation.to_s.downcase
|
163
200
|
|
164
|
-
distinct
|
201
|
+
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
|
202
|
+
distinct = self.distinct_value
|
203
|
+
if options.has_key?(:distinct)
|
204
|
+
ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \
|
205
|
+
"Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)"
|
206
|
+
distinct = options[:distinct]
|
207
|
+
end
|
165
208
|
|
166
209
|
if operation == "count"
|
167
210
|
column_name ||= (select_for_count || :all)
|
168
211
|
|
169
|
-
|
212
|
+
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
170
213
|
distinct = true
|
171
|
-
column_name = @klass.primary_key if column_name == :all
|
172
214
|
end
|
173
215
|
|
216
|
+
column_name = primary_key if column_name == :all && distinct
|
217
|
+
|
174
218
|
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
|
175
219
|
end
|
176
220
|
|
177
|
-
|
178
|
-
|
221
|
+
if group_values.any?
|
222
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
223
|
+
else
|
224
|
+
execute_simple_calculation(operation, column_name, distinct)
|
225
|
+
end
|
226
|
+
end
|
179
227
|
|
180
|
-
|
181
|
-
|
228
|
+
def aggregate_column(column_name)
|
229
|
+
if @klass.column_names.include?(column_name.to_s)
|
230
|
+
Arel::Attribute.new(@klass.unscoped.table, column_name)
|
182
231
|
else
|
183
|
-
|
232
|
+
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
184
233
|
end
|
185
234
|
end
|
186
235
|
|
236
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
237
|
+
operation == 'count' ? column.count(distinct) : column.send(operation)
|
238
|
+
end
|
239
|
+
|
187
240
|
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
188
|
-
|
189
|
-
|
241
|
+
# Postgresql doesn't like ORDER BY when there are no GROUP BY
|
242
|
+
relation = reorder(nil)
|
243
|
+
|
244
|
+
column_alias = column_name
|
245
|
+
|
246
|
+
if operation == "count" && (relation.limit_value || relation.offset_value)
|
247
|
+
# Shortcut when limit is zero.
|
248
|
+
return 0 if relation.limit_value == 0
|
249
|
+
|
250
|
+
query_builder = build_count_subquery(relation, column_name, distinct)
|
190
251
|
else
|
191
|
-
|
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
|
+
relation.select_values = [select_value]
|
258
|
+
|
259
|
+
query_builder = relation.arel
|
192
260
|
end
|
193
261
|
|
194
|
-
|
195
|
-
|
196
|
-
|
262
|
+
result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
|
263
|
+
row = result.first
|
264
|
+
value = row && row.values.first
|
265
|
+
column = result.column_types.fetch(column_alias) do
|
266
|
+
column_for(column_name)
|
267
|
+
end
|
268
|
+
|
269
|
+
type_cast_calculated_value(value, column, operation)
|
197
270
|
end
|
198
271
|
|
199
|
-
def execute_grouped_calculation(operation, column_name) #:nodoc:
|
200
|
-
|
201
|
-
association = @klass.reflect_on_association(group_attr.to_sym)
|
202
|
-
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
203
|
-
group_field = associated ? association.primary_key_name : group_attr
|
204
|
-
group_alias = column_alias_for(group_field)
|
205
|
-
group_column = column_for(group_field)
|
272
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
273
|
+
group_attrs = group_values
|
206
274
|
|
207
|
-
|
275
|
+
if group_attrs.first.respond_to?(:to_sym)
|
276
|
+
association = @klass.reflect_on_association(group_attrs.first.to_sym)
|
277
|
+
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
278
|
+
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
279
|
+
else
|
280
|
+
group_fields = group_attrs
|
281
|
+
end
|
208
282
|
|
209
|
-
|
283
|
+
group_aliases = group_fields.map { |field|
|
284
|
+
column_alias_for(field)
|
285
|
+
}
|
286
|
+
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
287
|
+
[aliaz, field]
|
288
|
+
}
|
210
289
|
|
211
|
-
|
212
|
-
|
290
|
+
group = group_fields
|
291
|
+
|
292
|
+
if operation == 'count' && column_name == :all
|
293
|
+
aggregate_alias = 'count_all'
|
213
294
|
else
|
214
|
-
|
295
|
+
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
215
296
|
end
|
216
297
|
|
217
|
-
|
298
|
+
select_values = [
|
299
|
+
operation_over_aggregate_column(
|
300
|
+
aggregate_column(column_name),
|
301
|
+
operation,
|
302
|
+
distinct).as(aggregate_alias)
|
303
|
+
]
|
304
|
+
select_values += select_values unless having_values.empty?
|
305
|
+
|
306
|
+
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
|
307
|
+
if field.respond_to?(:as)
|
308
|
+
field.as(aliaz)
|
309
|
+
else
|
310
|
+
"#{field} AS #{aliaz}"
|
311
|
+
end
|
312
|
+
}
|
218
313
|
|
219
|
-
relation = except(:group)
|
314
|
+
relation = except(:group)
|
315
|
+
relation.group_values = group
|
316
|
+
relation.select_values = select_values
|
220
317
|
|
221
|
-
calculated_data = @klass.connection.select_all(relation
|
318
|
+
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
|
222
319
|
|
223
320
|
if association
|
224
|
-
key_ids = calculated_data.collect { |row| row[
|
321
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
225
322
|
key_records = association.klass.base_class.find(key_ids)
|
226
|
-
key_records = key_records.
|
323
|
+
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
227
324
|
end
|
228
325
|
|
229
|
-
calculated_data.
|
230
|
-
key
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
326
|
+
Hash[calculated_data.map do |row|
|
327
|
+
key = group_columns.map { |aliaz, col_name|
|
328
|
+
column = calculated_data.column_types.fetch(aliaz) do
|
329
|
+
column_for(col_name)
|
330
|
+
end
|
331
|
+
type_cast_calculated_value(row[aliaz], column)
|
332
|
+
}
|
333
|
+
key = key.first if key.size == 1
|
334
|
+
key = key_records[key] if associated
|
335
|
+
[key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
|
336
|
+
end]
|
236
337
|
end
|
237
338
|
|
238
339
|
# Converts the given keys to the value that the database adapter returns as
|
@@ -243,9 +344,12 @@ module ActiveRecord
|
|
243
344
|
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
244
345
|
# column_alias_for("count(*)") # => "count_all"
|
245
346
|
# column_alias_for("count", "id") # => "count_id"
|
246
|
-
def column_alias_for(
|
247
|
-
|
248
|
-
|
347
|
+
def column_alias_for(keys)
|
348
|
+
if keys.respond_to? :name
|
349
|
+
keys = "#{keys.relation.name}.#{keys.name}"
|
350
|
+
end
|
351
|
+
|
352
|
+
table_name = keys.to_s.downcase
|
249
353
|
table_name.gsub!(/\*/, 'all')
|
250
354
|
table_name.gsub!(/\W+/, ' ')
|
251
355
|
table_name.strip!
|
@@ -255,20 +359,16 @@ module ActiveRecord
|
|
255
359
|
end
|
256
360
|
|
257
361
|
def column_for(field)
|
258
|
-
field_name = field.to_s.split('.').last
|
259
|
-
@klass.
|
362
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
363
|
+
@klass.columns_hash[field_name]
|
260
364
|
end
|
261
365
|
|
262
366
|
def type_cast_calculated_value(value, column, operation = nil)
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
else type_cast_using_column(value, column)
|
269
|
-
end
|
270
|
-
else
|
271
|
-
value
|
367
|
+
case operation
|
368
|
+
when 'count' then value.to_i
|
369
|
+
when 'sum' then type_cast_using_column(value || 0, column)
|
370
|
+
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
|
371
|
+
else type_cast_using_column(value, column)
|
272
372
|
end
|
273
373
|
end
|
274
374
|
|
@@ -277,10 +377,23 @@ module ActiveRecord
|
|
277
377
|
end
|
278
378
|
|
279
379
|
def select_for_count
|
280
|
-
if
|
281
|
-
select =
|
282
|
-
select if select !~ /
|
380
|
+
if select_values.present?
|
381
|
+
select = select_values.join(", ")
|
382
|
+
select if select !~ /[,*]/
|
283
383
|
end
|
284
384
|
end
|
385
|
+
|
386
|
+
def build_count_subquery(relation, column_name, distinct)
|
387
|
+
column_alias = Arel.sql('count_column')
|
388
|
+
subquery_alias = Arel.sql('subquery_for_count')
|
389
|
+
|
390
|
+
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
|
391
|
+
relation.select_values = [aliased_column]
|
392
|
+
subquery = relation.arel.as(subquery_alias)
|
393
|
+
|
394
|
+
sm = Arel::SelectManager.new relation.engine
|
395
|
+
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
|
396
|
+
sm.project(select_value).from(subquery)
|
397
|
+
end
|
285
398
|
end
|
286
399
|
end
|