activerecord 3.1.10 → 4.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +279 -232
- data/lib/active_record/base.rb +84 -1969
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -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 +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,77 +1,71 @@
|
|
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
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# Person.count
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# Examples for count with options:
|
42
|
-
# Person.count(:conditions => "age > 26")
|
43
|
-
#
|
44
|
-
# # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
|
45
|
-
# Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
|
46
|
-
#
|
47
|
-
# # finds the number of rows matching the conditions and joins.
|
48
|
-
# Person.count(:conditions => "age > 26 AND job.salary > 60000",
|
49
|
-
# :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
|
50
|
-
#
|
51
|
-
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
|
52
|
-
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
|
53
|
-
#
|
54
|
-
# Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
|
55
|
-
# Use Person.count instead.
|
3
|
+
# Count the records.
|
4
|
+
#
|
5
|
+
# Person.count
|
6
|
+
# # => the total count of all people
|
7
|
+
#
|
8
|
+
# Person.count(:age)
|
9
|
+
# # => returns the total count of all people whose age is present in database
|
10
|
+
#
|
11
|
+
# Person.count(:all)
|
12
|
+
# # => performs a COUNT(*) (:all is an alias for '*')
|
13
|
+
#
|
14
|
+
# Person.distinct.count(:age)
|
15
|
+
# # => counts the number of different age values
|
16
|
+
#
|
17
|
+
# If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
|
18
|
+
# and the values are the respective amounts:
|
19
|
+
#
|
20
|
+
# Person.group(:city).count
|
21
|
+
# # => { 'Rome' => 5, 'Paris' => 3 }
|
22
|
+
#
|
23
|
+
# If +count+ is used with +group+ for multiple columns, it returns a Hash whose
|
24
|
+
# keys are an array containing the individual values of each column and the value
|
25
|
+
# of each key would be the +count+.
|
26
|
+
#
|
27
|
+
# Article.group(:status, :category).count
|
28
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
29
|
+
# ["published", "business"]=>0, ["published", "technology"]=>2}
|
30
|
+
#
|
31
|
+
# If +count+ is used with +select+, it will count the selected columns:
|
32
|
+
#
|
33
|
+
# Person.select(:age).count
|
34
|
+
# # => counts the number of different age values
|
35
|
+
#
|
36
|
+
# Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
|
37
|
+
# between databases. In invalid cases, an error from the database is thrown.
|
56
38
|
def count(column_name = nil, options = {})
|
57
|
-
|
39
|
+
if options.present? && !ActiveRecord.const_defined?(:DeprecatedFinders)
|
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.
|
58
48
|
calculate(:count, column_name, options)
|
59
49
|
end
|
60
50
|
|
61
51
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
62
52
|
# no row. See +calculate+ for examples with options.
|
63
53
|
#
|
64
|
-
# Person.average(
|
54
|
+
# Person.average(:age) # => 35.8
|
65
55
|
def average(column_name, options = {})
|
56
|
+
# TODO: Remove options argument as soon we remove support to
|
57
|
+
# activerecord-deprecated_finders.
|
66
58
|
calculate(:average, column_name, options)
|
67
59
|
end
|
68
60
|
|
69
|
-
# Calculates the minimum value on a given column.
|
61
|
+
# Calculates the minimum value on a given column. The value is returned
|
70
62
|
# with the same data type of the column, or +nil+ if there's no row. See
|
71
63
|
# +calculate+ for examples with options.
|
72
64
|
#
|
73
|
-
# Person.minimum(
|
65
|
+
# Person.minimum(:age) # => 7
|
74
66
|
def minimum(column_name, options = {})
|
67
|
+
# TODO: Remove options argument as soon we remove support to
|
68
|
+
# activerecord-deprecated_finders.
|
75
69
|
calculate(:minimum, column_name, options)
|
76
70
|
end
|
77
71
|
|
@@ -79,8 +73,10 @@ module ActiveRecord
|
|
79
73
|
# with the same data type of the column, or +nil+ if there's no row. See
|
80
74
|
# +calculate+ for examples with options.
|
81
75
|
#
|
82
|
-
# Person.maximum(
|
76
|
+
# Person.maximum(:age) # => 93
|
83
77
|
def maximum(column_name, options = {})
|
78
|
+
# TODO: Remove options argument as soon we remove support to
|
79
|
+
# activerecord-deprecated_finders.
|
84
80
|
calculate(:maximum, column_name, options)
|
85
81
|
end
|
86
82
|
|
@@ -88,100 +84,144 @@ module ActiveRecord
|
|
88
84
|
# with the same data type of the column, 0 if there's no row. See
|
89
85
|
# +calculate+ for examples with options.
|
90
86
|
#
|
91
|
-
# Person.sum(
|
92
|
-
def sum(
|
93
|
-
calculate(:sum,
|
87
|
+
# Person.sum(:age) # => 4562
|
88
|
+
def sum(*args)
|
89
|
+
calculate(:sum, *args)
|
94
90
|
end
|
95
91
|
|
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.
|
92
|
+
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
93
|
+
# minimum, and maximum have been added as shortcuts.
|
99
94
|
#
|
100
95
|
# There are two basic forms of output:
|
101
|
-
#
|
96
|
+
#
|
97
|
+
# * Single aggregate value: The single value is type cast to Integer for COUNT, Float
|
102
98
|
# 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
99
|
#
|
106
|
-
#
|
100
|
+
# * Grouped values: This returns an ordered hash of the values and groups them. It
|
101
|
+
# takes either a column name, or the name of a belongs_to association.
|
102
|
+
#
|
103
|
+
# values = Person.group('last_name').maximum(:age)
|
107
104
|
# puts values["Drake"]
|
108
|
-
# => 43
|
105
|
+
# # => 43
|
109
106
|
#
|
110
|
-
# drake = Family.
|
111
|
-
# values = Person.maximum(:age
|
107
|
+
# drake = Family.find_by(last_name: 'Drake')
|
108
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
112
109
|
# puts values[drake]
|
113
|
-
# => 43
|
110
|
+
# # => 43
|
114
111
|
#
|
115
112
|
# values.each do |family, max_age|
|
116
113
|
# ...
|
117
114
|
# end
|
118
115
|
#
|
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
116
|
# Person.calculate(:count, :all) # The same as Person.count
|
137
117
|
# 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
118
|
#
|
141
119
|
# # Selects the minimum age for any family without any minors
|
142
|
-
# Person.
|
120
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
143
121
|
#
|
144
122
|
# Person.sum("2 * age")
|
145
123
|
def calculate(operation, column_name, options = {})
|
146
|
-
|
147
|
-
|
124
|
+
# TODO: Remove options argument as soon we remove support to
|
125
|
+
# activerecord-deprecated_finders.
|
126
|
+
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
127
|
+
column_name = attribute_alias(column_name)
|
128
|
+
end
|
129
|
+
|
130
|
+
if has_include?(column_name)
|
131
|
+
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
148
132
|
else
|
149
|
-
|
133
|
+
perform_calculation(operation, column_name, options)
|
134
|
+
end
|
135
|
+
end
|
150
136
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
137
|
+
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
|
138
|
+
# loading a bunch of records just to grab the attributes you want.
|
139
|
+
#
|
140
|
+
# Person.pluck(:name)
|
141
|
+
#
|
142
|
+
# instead of
|
143
|
+
#
|
144
|
+
# Person.all.map(&:name)
|
145
|
+
#
|
146
|
+
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
|
147
|
+
# the plucked column names, if they can be deduced. Plucking an SQL fragment
|
148
|
+
# returns String values by default.
|
149
|
+
#
|
150
|
+
# Person.pluck(:id)
|
151
|
+
# # SELECT people.id FROM people
|
152
|
+
# # => [1, 2, 3]
|
153
|
+
#
|
154
|
+
# Person.pluck(:id, :name)
|
155
|
+
# # SELECT people.id, people.name FROM people
|
156
|
+
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
|
157
|
+
#
|
158
|
+
# Person.pluck('DISTINCT role')
|
159
|
+
# # SELECT DISTINCT role FROM people
|
160
|
+
# # => ['admin', 'member', 'guest']
|
161
|
+
#
|
162
|
+
# Person.where(age: 21).limit(5).pluck(:id)
|
163
|
+
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
164
|
+
# # => [2, 3]
|
165
|
+
#
|
166
|
+
# Person.pluck('DATEDIFF(updated_at, created_at)')
|
167
|
+
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
168
|
+
# # => ['0', '27761', '173']
|
169
|
+
#
|
170
|
+
def pluck(*column_names)
|
171
|
+
column_names.map! do |column_name|
|
172
|
+
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
173
|
+
attribute_alias(column_name)
|
157
174
|
else
|
158
|
-
|
175
|
+
column_name.to_s
|
159
176
|
end
|
160
177
|
end
|
161
|
-
|
162
|
-
|
178
|
+
|
179
|
+
if has_include?(column_names.first)
|
180
|
+
construct_relation_for_association_calculations.pluck(*column_names)
|
181
|
+
else
|
182
|
+
relation = spawn
|
183
|
+
relation.select_values = column_names.map { |cn|
|
184
|
+
columns_hash.key?(cn) ? arel_table[cn] : cn
|
185
|
+
}
|
186
|
+
result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
|
187
|
+
result.cast_values(klass.column_types)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Pluck all the ID's for the relation using the table's primary key
|
192
|
+
#
|
193
|
+
# Person.ids # SELECT people.id FROM people
|
194
|
+
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
|
195
|
+
def ids
|
196
|
+
pluck primary_key
|
163
197
|
end
|
164
198
|
|
165
199
|
private
|
166
200
|
|
201
|
+
def has_include?(column_name)
|
202
|
+
eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
|
203
|
+
end
|
204
|
+
|
167
205
|
def perform_calculation(operation, column_name, options = {})
|
206
|
+
# TODO: Remove options argument as soon we remove support to
|
207
|
+
# activerecord-deprecated_finders.
|
168
208
|
operation = operation.to_s.downcase
|
169
209
|
|
170
|
-
distinct
|
210
|
+
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
|
211
|
+
distinct = self.distinct_value
|
171
212
|
|
172
213
|
if operation == "count"
|
173
|
-
column_name ||=
|
214
|
+
column_name ||= select_for_count
|
174
215
|
|
175
216
|
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
176
217
|
distinct = true
|
177
218
|
end
|
178
219
|
|
179
220
|
column_name = primary_key if column_name == :all && distinct
|
180
|
-
|
181
|
-
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
|
221
|
+
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
|
182
222
|
end
|
183
223
|
|
184
|
-
if
|
224
|
+
if group_values.any?
|
185
225
|
execute_grouped_calculation(operation, column_name, distinct)
|
186
226
|
else
|
187
227
|
execute_simple_calculation(operation, column_name, distinct)
|
@@ -202,42 +242,66 @@ module ActiveRecord
|
|
202
242
|
|
203
243
|
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
204
244
|
# Postgresql doesn't like ORDER BY when there are no GROUP BY
|
205
|
-
relation =
|
245
|
+
relation = unscope(:order)
|
246
|
+
|
247
|
+
column_alias = column_name
|
248
|
+
|
249
|
+
bind_values = nil
|
206
250
|
|
207
251
|
if operation == "count" && (relation.limit_value || relation.offset_value)
|
208
252
|
# Shortcut when limit is zero.
|
209
253
|
return 0 if relation.limit_value == 0
|
210
254
|
|
211
255
|
query_builder = build_count_subquery(relation, column_name, distinct)
|
256
|
+
bind_values = query_builder.bind_values + relation.bind_values
|
212
257
|
else
|
213
258
|
column = aggregate_column(column_name)
|
214
259
|
|
215
260
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
216
261
|
|
262
|
+
column_alias = select_value.alias
|
263
|
+
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
217
264
|
relation.select_values = [select_value]
|
218
265
|
|
219
266
|
query_builder = relation.arel
|
267
|
+
bind_values = query_builder.bind_values + relation.bind_values
|
268
|
+
end
|
269
|
+
|
270
|
+
result = @klass.connection.select_all(query_builder, nil, bind_values)
|
271
|
+
row = result.first
|
272
|
+
value = row && row.values.first
|
273
|
+
column = result.column_types.fetch(column_alias) do
|
274
|
+
type_for(column_name)
|
220
275
|
end
|
221
276
|
|
222
|
-
type_cast_calculated_value(
|
277
|
+
type_cast_calculated_value(value, column, operation)
|
223
278
|
end
|
224
279
|
|
225
280
|
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
281
|
+
group_attrs = group_values
|
282
|
+
|
283
|
+
if group_attrs.first.respond_to?(:to_sym)
|
284
|
+
association = @klass._reflect_on_association(group_attrs.first)
|
285
|
+
associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
|
286
|
+
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
287
|
+
else
|
288
|
+
group_fields = group_attrs
|
289
|
+
end
|
290
|
+
group_fields = arel_columns(group_fields)
|
291
|
+
|
292
|
+
group_aliases = group_fields.map { |field|
|
293
|
+
column_alias_for(field)
|
294
|
+
}
|
231
295
|
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
232
|
-
[aliaz,
|
296
|
+
[aliaz, field]
|
233
297
|
}
|
234
298
|
|
235
|
-
group =
|
299
|
+
group = group_fields
|
236
300
|
|
237
301
|
if operation == 'count' && column_name == :all
|
238
302
|
aggregate_alias = 'count_all'
|
239
303
|
else
|
240
|
-
aggregate_alias = column_alias_for(operation, column_name)
|
304
|
+
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
241
305
|
end
|
242
306
|
|
243
307
|
select_values = [
|
@@ -246,16 +310,21 @@ module ActiveRecord
|
|
246
310
|
operation,
|
247
311
|
distinct).as(aggregate_alias)
|
248
312
|
]
|
249
|
-
select_values +=
|
313
|
+
select_values += self.select_values unless having_values.empty?
|
250
314
|
|
251
315
|
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
|
252
|
-
|
316
|
+
if field.respond_to?(:as)
|
317
|
+
field.as(aliaz)
|
318
|
+
else
|
319
|
+
"#{field} AS #{aliaz}"
|
320
|
+
end
|
253
321
|
}
|
254
322
|
|
255
|
-
relation = except(:group)
|
323
|
+
relation = except(:group)
|
324
|
+
relation.group_values = group
|
256
325
|
relation.select_values = select_values
|
257
326
|
|
258
|
-
calculated_data = @klass.connection.select_all(relation)
|
327
|
+
calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
|
259
328
|
|
260
329
|
if association
|
261
330
|
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
@@ -263,13 +332,18 @@ module ActiveRecord
|
|
263
332
|
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
264
333
|
end
|
265
334
|
|
266
|
-
|
267
|
-
key
|
335
|
+
Hash[calculated_data.map do |row|
|
336
|
+
key = group_columns.map { |aliaz, col_name|
|
337
|
+
column = calculated_data.column_types.fetch(aliaz) do
|
338
|
+
type_for(col_name)
|
339
|
+
end
|
268
340
|
type_cast_calculated_value(row[aliaz], column)
|
269
341
|
}
|
270
|
-
key
|
342
|
+
key = key.first if key.size == 1
|
271
343
|
key = key_records[key] if associated
|
272
|
-
|
344
|
+
|
345
|
+
column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
|
346
|
+
[key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
|
273
347
|
end]
|
274
348
|
end
|
275
349
|
|
@@ -281,9 +355,12 @@ module ActiveRecord
|
|
281
355
|
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
282
356
|
# column_alias_for("count(*)") # => "count_all"
|
283
357
|
# column_alias_for("count", "id") # => "count_id"
|
284
|
-
def column_alias_for(
|
285
|
-
|
286
|
-
|
358
|
+
def column_alias_for(keys)
|
359
|
+
if keys.respond_to? :name
|
360
|
+
keys = "#{keys.relation.name}.#{keys.name}"
|
361
|
+
end
|
362
|
+
|
363
|
+
table_name = keys.to_s.downcase
|
287
364
|
table_name.gsub!(/\*/, 'all')
|
288
365
|
table_name.gsub!(/\W+/, ' ')
|
289
366
|
table_name.strip!
|
@@ -292,28 +369,26 @@ module ActiveRecord
|
|
292
369
|
@klass.connection.table_alias_for(table_name)
|
293
370
|
end
|
294
371
|
|
295
|
-
def
|
296
|
-
field_name = field.to_s.split('.').last
|
297
|
-
@klass.
|
372
|
+
def type_for(field)
|
373
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
374
|
+
@klass.type_for_attribute(field_name)
|
298
375
|
end
|
299
376
|
|
300
|
-
def type_cast_calculated_value(value,
|
377
|
+
def type_cast_calculated_value(value, type, operation = nil)
|
301
378
|
case operation
|
302
379
|
when 'count' then value.to_i
|
303
|
-
when 'sum' then
|
380
|
+
when 'sum' then type.type_cast_from_database(value || 0)
|
304
381
|
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
|
305
|
-
else
|
382
|
+
else type.type_cast_from_database(value)
|
306
383
|
end
|
307
384
|
end
|
308
385
|
|
309
|
-
|
310
|
-
column ? column.type_cast(value) : value
|
311
|
-
end
|
312
|
-
|
386
|
+
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
|
313
387
|
def select_for_count
|
314
|
-
if
|
315
|
-
|
316
|
-
|
388
|
+
if select_values.present?
|
389
|
+
select_values.join(", ")
|
390
|
+
else
|
391
|
+
:all
|
317
392
|
end
|
318
393
|
end
|
319
394
|
|
@@ -323,9 +398,11 @@ module ActiveRecord
|
|
323
398
|
|
324
399
|
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
|
325
400
|
relation.select_values = [aliased_column]
|
326
|
-
|
401
|
+
arel = relation.arel
|
402
|
+
subquery = arel.as(subquery_alias)
|
327
403
|
|
328
404
|
sm = Arel::SelectManager.new relation.engine
|
405
|
+
sm.bind_values = arel.bind_values
|
329
406
|
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
|
330
407
|
sm.project(select_value).from(subquery)
|
331
408
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_support/deprecation'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Delegation # :nodoc:
|
7
|
+
module DelegateCache
|
8
|
+
def relation_delegate_class(klass) # :nodoc:
|
9
|
+
@relation_delegate_cache[klass]
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_relation_delegate_cache # :nodoc:
|
13
|
+
@relation_delegate_cache = cache = {}
|
14
|
+
[
|
15
|
+
ActiveRecord::Relation,
|
16
|
+
ActiveRecord::Associations::CollectionProxy,
|
17
|
+
ActiveRecord::AssociationRelation
|
18
|
+
].each do |klass|
|
19
|
+
delegate = Class.new(klass) {
|
20
|
+
include ClassSpecificRelation
|
21
|
+
}
|
22
|
+
const_set klass.name.gsub('::', '_'), delegate
|
23
|
+
cache[klass] = delegate
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def inherited(child_class)
|
28
|
+
child_class.initialize_relation_delegate_cache
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
extend ActiveSupport::Concern
|
34
|
+
|
35
|
+
# This module creates compiled delegation methods dynamically at runtime, which makes
|
36
|
+
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
37
|
+
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
38
|
+
# for each different klass, and the delegations are compiled into that subclass only.
|
39
|
+
|
40
|
+
BLACKLISTED_ARRAY_METHODS = [
|
41
|
+
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
|
42
|
+
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
|
43
|
+
:keep_if, :pop, :shift, :delete_at, :select!
|
44
|
+
].to_set # :nodoc:
|
45
|
+
|
46
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
|
47
|
+
|
48
|
+
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
|
49
|
+
:connection, :columns_hash, :to => :klass
|
50
|
+
|
51
|
+
module ClassSpecificRelation # :nodoc:
|
52
|
+
extend ActiveSupport::Concern
|
53
|
+
|
54
|
+
included do
|
55
|
+
@delegation_mutex = Mutex.new
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods # :nodoc:
|
59
|
+
def name
|
60
|
+
superclass.name
|
61
|
+
end
|
62
|
+
|
63
|
+
def delegate_to_scoped_klass(method)
|
64
|
+
@delegation_mutex.synchronize do
|
65
|
+
return if method_defined?(method)
|
66
|
+
|
67
|
+
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
|
68
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
69
|
+
def #{method}(*args, &block)
|
70
|
+
scoping { @klass.#{method}(*args, &block) }
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
else
|
74
|
+
define_method method do |*args, &block|
|
75
|
+
scoping { @klass.public_send(method, *args, &block) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def delegate(method, opts = {})
|
82
|
+
@delegation_mutex.synchronize do
|
83
|
+
return if method_defined?(method)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def method_missing(method, *args, &block)
|
92
|
+
if @klass.respond_to?(method)
|
93
|
+
self.class.delegate_to_scoped_klass(method)
|
94
|
+
scoping { @klass.public_send(method, *args, &block) }
|
95
|
+
elsif arel.respond_to?(method)
|
96
|
+
self.class.delegate method, :to => :arel
|
97
|
+
arel.public_send(method, *args, &block)
|
98
|
+
else
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module ClassMethods # :nodoc:
|
105
|
+
def create(klass, *args)
|
106
|
+
relation_class_for(klass).new(klass, *args)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def relation_class_for(klass)
|
112
|
+
klass.relation_delegate_class(self)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def respond_to?(method, include_private = false)
|
117
|
+
super || @klass.respond_to?(method, include_private) ||
|
118
|
+
array_delegable?(method) ||
|
119
|
+
arel.respond_to?(method, include_private)
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
def array_delegable?(method)
|
125
|
+
Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
|
126
|
+
end
|
127
|
+
|
128
|
+
def method_missing(method, *args, &block)
|
129
|
+
if @klass.respond_to?(method)
|
130
|
+
scoping { @klass.public_send(method, *args, &block) }
|
131
|
+
elsif array_delegable?(method)
|
132
|
+
to_a.public_send(method, *args, &block)
|
133
|
+
elsif arel.respond_to?(method)
|
134
|
+
arel.public_send(method, *args, &block)
|
135
|
+
else
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|