activerecord 1.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 +213 -0
- data/examples/performance.rb +172 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +180 -84
- 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 +92 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
- 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 +58 -39
- data/lib/active_record/associations/has_many_association.rb +116 -85
- data/lib/active_record/associations/has_many_through_association.rb +197 -0
- data/lib/active_record/associations/has_one_association.rb +102 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- 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 +1437 -431
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
- data/lib/active_record/attribute_methods/dirty.rb +118 -0
- data/lib/active_record/attribute_methods/primary_key.rb +122 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +107 -0
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
- data/lib/active_record/attribute_methods/write.rb +63 -0
- data/lib/active_record/attribute_methods.rb +393 -0
- data/lib/active_record/autosave_association.rb +426 -0
- data/lib/active_record/base.rb +268 -930
- data/lib/active_record/callbacks.rb +203 -230
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
- 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 +517 -90
- 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 +911 -138
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
- 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 +122 -0
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +213 -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 +55 -0
- data/lib/active_record/fixtures.rb +892 -138
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +181 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +82 -0
- 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 +1015 -0
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +546 -0
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +509 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +205 -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 +402 -0
- 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 +544 -87
- data/lib/active_record/relation/batches.rb +93 -0
- data/lib/active_record/relation/calculations.rb +399 -0
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +349 -0
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +106 -0
- data/lib/active_record/relation/query_methods.rb +1044 -0
- data/lib/active_record/relation/spawn_methods.rb +73 -0
- data/lib/active_record/relation.rb +655 -0
- 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 +65 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- 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 +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +197 -0
- 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 +96 -0
- data/lib/active_record/timestamp.rb +119 -0
- data/lib/active_record/transactions.rb +366 -69
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +49 -0
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +225 -0
- data/lib/active_record/validations.rb +64 -185
- data/lib/active_record/version.rb +11 -0
- data/lib/active_record.rb +149 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -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
- data/lib/rails/generators/active_record.rb +23 -0
- metadata +261 -161
- data/CHANGELOG +0 -581
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.png +0 -0
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/associations/association_collection.rb +0 -70
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/observer.rb +0 -71
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
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.
|
7
|
+
#
|
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
|
17
|
+
#
|
18
|
+
# Person.where("age > 21").find_each do |person|
|
19
|
+
# person.party_all_night!
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# You can also pass the +:start+ option to specify
|
23
|
+
# an offset to control the starting point.
|
24
|
+
def find_each(options = {})
|
25
|
+
find_in_batches(options) do |records|
|
26
|
+
records.each { |record| yield record }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Yields each batch of records that was found by the find +options+ as
|
31
|
+
# an array. The size of each batch is set by the +:batch_size+
|
32
|
+
# option; the default is 1000.
|
33
|
+
#
|
34
|
+
# You can control the starting point for the batch processing by
|
35
|
+
# supplying the +:start+ option. This is especially useful if you
|
36
|
+
# want multiple workers dealing with the same processing queue. You can
|
37
|
+
# make worker 1 handle all the records between id 0 and 10,000 and
|
38
|
+
# worker 2 handle from 10,000 and beyond (by setting the +:start+
|
39
|
+
# option on that worker).
|
40
|
+
#
|
41
|
+
# 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. You can't set the limit either, that's used to control
|
45
|
+
# the batch sizes.
|
46
|
+
#
|
47
|
+
# Person.where("age > 21").find_in_batches do |group|
|
48
|
+
# sleep(50) # Make sure it doesn't get too crowded in there!
|
49
|
+
# group.each { |person| person.party_all_night! }
|
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
|
56
|
+
def find_in_batches(options = {})
|
57
|
+
options.assert_valid_keys(:start, :batch_size)
|
58
|
+
|
59
|
+
relation = self
|
60
|
+
|
61
|
+
unless arel.orders.blank? && arel.taken.blank?
|
62
|
+
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
63
|
+
end
|
64
|
+
|
65
|
+
start = options.delete(:start)
|
66
|
+
batch_size = options.delete(:batch_size) || 1000
|
67
|
+
|
68
|
+
relation = relation.reorder(batch_order).limit(batch_size)
|
69
|
+
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
|
70
|
+
|
71
|
+
while records.any?
|
72
|
+
records_size = records.size
|
73
|
+
primary_key_offset = records.last.id
|
74
|
+
|
75
|
+
yield records
|
76
|
+
|
77
|
+
break if records_size < batch_size
|
78
|
+
|
79
|
+
if primary_key_offset
|
80
|
+
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
|
81
|
+
else
|
82
|
+
raise "Primary key not included in the custom select clause"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def batch_order
|
90
|
+
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,399 @@
|
|
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
|
+
def count(column_name = nil, options = {})
|
23
|
+
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
24
|
+
calculate(:count, column_name, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Calculates the average value on a given column. Returns +nil+ if there's
|
28
|
+
# no row. See +calculate+ for examples with options.
|
29
|
+
#
|
30
|
+
# Person.average('age') # => 35.8
|
31
|
+
def average(column_name, options = {})
|
32
|
+
calculate(:average, column_name, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Calculates the minimum value on a given column. The value is returned
|
36
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
37
|
+
# +calculate+ for examples with options.
|
38
|
+
#
|
39
|
+
# Person.minimum('age') # => 7
|
40
|
+
def minimum(column_name, options = {})
|
41
|
+
calculate(:minimum, column_name, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Calculates the maximum value on a given column. The value is returned
|
45
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
46
|
+
# +calculate+ for examples with options.
|
47
|
+
#
|
48
|
+
# Person.maximum('age') # => 93
|
49
|
+
def maximum(column_name, options = {})
|
50
|
+
calculate(:maximum, column_name, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Calculates the sum of values on a given column. The value is returned
|
54
|
+
# with the same data type of the column, 0 if there's no row. See
|
55
|
+
# +calculate+ for examples with options.
|
56
|
+
#
|
57
|
+
# Person.sum('age') # => 4562
|
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
|
68
|
+
end
|
69
|
+
|
70
|
+
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
71
|
+
# minimum, and maximum have been added as shortcuts.
|
72
|
+
#
|
73
|
+
# There are two basic forms of output:
|
74
|
+
#
|
75
|
+
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
|
76
|
+
# for AVG, and the given column's type for everything else.
|
77
|
+
#
|
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)
|
82
|
+
# puts values["Drake"]
|
83
|
+
# # => 43
|
84
|
+
#
|
85
|
+
# drake = Family.find_by(last_name: 'Drake')
|
86
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
87
|
+
# puts values[drake]
|
88
|
+
# # => 43
|
89
|
+
#
|
90
|
+
# values.each do |family, max_age|
|
91
|
+
# ...
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Person.calculate(:count, :all) # The same as Person.count
|
95
|
+
# Person.average(:age) # SELECT AVG(age) FROM people...
|
96
|
+
#
|
97
|
+
# # Selects the minimum age for any family without any minors
|
98
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
99
|
+
#
|
100
|
+
# Person.sum("2 * age")
|
101
|
+
def calculate(operation, column_name, options = {})
|
102
|
+
relation = with_default_scope
|
103
|
+
|
104
|
+
if relation.equal?(self)
|
105
|
+
if has_include?(column_name)
|
106
|
+
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
107
|
+
else
|
108
|
+
perform_calculation(operation, column_name, options)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
relation.calculate(operation, column_name, options)
|
112
|
+
end
|
113
|
+
rescue ThrowResult
|
114
|
+
0
|
115
|
+
end
|
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
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def has_include?(column_name)
|
195
|
+
eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
|
196
|
+
end
|
197
|
+
|
198
|
+
def perform_calculation(operation, column_name, options = {})
|
199
|
+
operation = operation.to_s.downcase
|
200
|
+
|
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
|
208
|
+
|
209
|
+
if operation == "count"
|
210
|
+
column_name ||= (select_for_count || :all)
|
211
|
+
|
212
|
+
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
213
|
+
distinct = true
|
214
|
+
end
|
215
|
+
|
216
|
+
column_name = primary_key if column_name == :all && distinct
|
217
|
+
|
218
|
+
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
|
219
|
+
end
|
220
|
+
|
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
|
227
|
+
|
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)
|
231
|
+
else
|
232
|
+
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
237
|
+
operation == 'count' ? column.count(distinct) : column.send(operation)
|
238
|
+
end
|
239
|
+
|
240
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
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)
|
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
|
+
relation.select_values = [select_value]
|
258
|
+
|
259
|
+
query_builder = relation.arel
|
260
|
+
end
|
261
|
+
|
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)
|
270
|
+
end
|
271
|
+
|
272
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
273
|
+
group_attrs = group_values
|
274
|
+
|
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
|
282
|
+
|
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
|
+
}
|
289
|
+
|
290
|
+
group = group_fields
|
291
|
+
|
292
|
+
if operation == 'count' && column_name == :all
|
293
|
+
aggregate_alias = 'count_all'
|
294
|
+
else
|
295
|
+
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
296
|
+
end
|
297
|
+
|
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
|
+
}
|
313
|
+
|
314
|
+
relation = except(:group)
|
315
|
+
relation.group_values = group
|
316
|
+
relation.select_values = select_values
|
317
|
+
|
318
|
+
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
|
319
|
+
|
320
|
+
if association
|
321
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
322
|
+
key_records = association.klass.base_class.find(key_ids)
|
323
|
+
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
324
|
+
end
|
325
|
+
|
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]
|
337
|
+
end
|
338
|
+
|
339
|
+
# Converts the given keys to the value that the database adapter returns as
|
340
|
+
# a usable column name:
|
341
|
+
#
|
342
|
+
# column_alias_for("users.id") # => "users_id"
|
343
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
344
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
345
|
+
# column_alias_for("count(*)") # => "count_all"
|
346
|
+
# column_alias_for("count", "id") # => "count_id"
|
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
|
353
|
+
table_name.gsub!(/\*/, 'all')
|
354
|
+
table_name.gsub!(/\W+/, ' ')
|
355
|
+
table_name.strip!
|
356
|
+
table_name.gsub!(/ +/, '_')
|
357
|
+
|
358
|
+
@klass.connection.table_alias_for(table_name)
|
359
|
+
end
|
360
|
+
|
361
|
+
def column_for(field)
|
362
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
363
|
+
@klass.columns_hash[field_name]
|
364
|
+
end
|
365
|
+
|
366
|
+
def type_cast_calculated_value(value, column, operation = nil)
|
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)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def type_cast_using_column(value, column)
|
376
|
+
column ? column.type_cast(value) : value
|
377
|
+
end
|
378
|
+
|
379
|
+
def select_for_count
|
380
|
+
if select_values.present?
|
381
|
+
select = select_values.join(", ")
|
382
|
+
select if select !~ /[,*]/
|
383
|
+
end
|
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
|
398
|
+
end
|
399
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'thread_safe'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module Delegation # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# This module creates compiled delegation methods dynamically at runtime, which makes
|
9
|
+
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
10
|
+
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
11
|
+
# for each different klass, and the delegations are compiled into that subclass only.
|
12
|
+
|
13
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
|
14
|
+
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
|
15
|
+
:connection, :columns_hash, :to => :klass
|
16
|
+
|
17
|
+
module ClassSpecificRelation # :nodoc:
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
included do
|
21
|
+
@delegation_mutex = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods # :nodoc:
|
25
|
+
def name
|
26
|
+
superclass.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def delegate_to_scoped_klass(method)
|
30
|
+
@delegation_mutex.synchronize do
|
31
|
+
return if method_defined?(method)
|
32
|
+
|
33
|
+
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
|
34
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
35
|
+
def #{method}(*args, &block)
|
36
|
+
scoping { @klass.#{method}(*args, &block) }
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
else
|
40
|
+
define_method method do |*args, &block|
|
41
|
+
scoping { @klass.send(method, *args, &block) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def delegate(method, opts = {})
|
48
|
+
@delegation_mutex.synchronize do
|
49
|
+
return if method_defined?(method)
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def method_missing(method, *args, &block)
|
58
|
+
if @klass.respond_to?(method)
|
59
|
+
self.class.delegate_to_scoped_klass(method)
|
60
|
+
scoping { @klass.send(method, *args, &block) }
|
61
|
+
elsif Array.method_defined?(method)
|
62
|
+
self.class.delegate method, :to => :to_a
|
63
|
+
to_a.send(method, *args, &block)
|
64
|
+
elsif arel.respond_to?(method)
|
65
|
+
self.class.delegate method, :to => :arel
|
66
|
+
arel.send(method, *args, &block)
|
67
|
+
else
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods # :nodoc:
|
74
|
+
@@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
|
75
|
+
|
76
|
+
def new(klass, *args)
|
77
|
+
relation = relation_class_for(klass).allocate
|
78
|
+
relation.__send__(:initialize, klass, *args)
|
79
|
+
relation
|
80
|
+
end
|
81
|
+
|
82
|
+
# This doesn't have to be thread-safe. relation_class_for guarantees that this will only be
|
83
|
+
# called exactly once for a given const name.
|
84
|
+
def const_missing(name)
|
85
|
+
const_set(name, Class.new(self) { include ClassSpecificRelation })
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
# Cache the constants in @@subclasses because looking them up via const_get
|
90
|
+
# make instantiation significantly slower.
|
91
|
+
def relation_class_for(klass)
|
92
|
+
if klass && (klass_name = klass.name)
|
93
|
+
my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new }
|
94
|
+
# This hash is keyed by klass.name to avoid memory leaks in development mode
|
95
|
+
my_cache.compute_if_absent(klass_name) do
|
96
|
+
# Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name
|
97
|
+
const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
ActiveRecord::Relation
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to?(method, include_private = false)
|
106
|
+
super || Array.method_defined?(method) ||
|
107
|
+
@klass.respond_to?(method, include_private) ||
|
108
|
+
arel.respond_to?(method, include_private)
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
if @klass.respond_to?(method)
|
115
|
+
scoping { @klass.send(method, *args, &block) }
|
116
|
+
elsif Array.method_defined?(method)
|
117
|
+
to_a.send(method, *args, &block)
|
118
|
+
elsif arel.respond_to?(method)
|
119
|
+
arel.send(method, *args, &block)
|
120
|
+
else
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|