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
@@ -4,10 +4,14 @@ module ActiveRecord
|
|
4
4
|
class HasManyThrough < CollectionAssociation #:nodoc:
|
5
5
|
include ThroughAssociation
|
6
6
|
|
7
|
-
def associated_records_by_owner
|
8
|
-
|
9
|
-
|
7
|
+
def associated_records_by_owner(preloader)
|
8
|
+
records_by_owner = super
|
9
|
+
|
10
|
+
if reflection_scope.distinct_value
|
11
|
+
records_by_owner.each_value { |records| records.uniq! }
|
10
12
|
end
|
13
|
+
|
14
|
+
records_by_owner
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -5,13 +5,13 @@ module ActiveRecord
|
|
5
5
|
|
6
6
|
private
|
7
7
|
|
8
|
-
def preload
|
9
|
-
associated_records_by_owner.each do |owner, associated_records|
|
8
|
+
def preload(preloader)
|
9
|
+
associated_records_by_owner(preloader).each do |owner, associated_records|
|
10
10
|
record = associated_records.first
|
11
11
|
|
12
12
|
association = owner.association(reflection.name)
|
13
13
|
association.target = record
|
14
|
-
association.set_inverse_instance(record)
|
14
|
+
association.set_inverse_instance(record) if record
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
module ThroughAssociation #:nodoc:
|
5
|
-
|
6
5
|
def through_reflection
|
7
6
|
reflection.through_reflection
|
8
7
|
end
|
@@ -11,55 +10,85 @@ module ActiveRecord
|
|
11
10
|
reflection.source_reflection
|
12
11
|
end
|
13
12
|
|
14
|
-
def associated_records_by_owner
|
15
|
-
|
13
|
+
def associated_records_by_owner(preloader)
|
14
|
+
preloader.preload(owners,
|
15
|
+
through_reflection.name,
|
16
|
+
through_scope)
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
source_reflection.name, options
|
20
|
-
).run
|
18
|
+
through_records = owners.map do |owner|
|
19
|
+
association = owner.association through_reflection.name
|
21
20
|
|
22
|
-
|
23
|
-
records.map! { |r| r.send(source_reflection.name) }.flatten!
|
24
|
-
records.compact!
|
21
|
+
[owner, Array(association.reader)]
|
25
22
|
end
|
26
|
-
end
|
27
23
|
|
28
|
-
|
24
|
+
reset_association owners, through_reflection.name
|
25
|
+
|
26
|
+
middle_records = through_records.flat_map { |(_,rec)| rec }
|
27
|
+
|
28
|
+
preloaders = preloader.preload(middle_records,
|
29
|
+
source_reflection.name,
|
30
|
+
reflection_scope)
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
@preloaded_records = preloaders.flat_map(&:preloaded_records)
|
33
|
+
|
34
|
+
middle_to_pl = preloaders.each_with_object({}) do |pl,h|
|
35
|
+
pl.owners.each { |middle|
|
36
|
+
h[middle] = pl
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
record_offset = {}
|
41
|
+
@preloaded_records.each_with_index do |record,i|
|
42
|
+
record_offset[record] = i
|
43
|
+
end
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
through_records.each_with_object({}) { |(lhs,center),records_by_owner|
|
46
|
+
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
|
38
47
|
|
39
|
-
|
40
|
-
|
41
|
-
|
48
|
+
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
|
49
|
+
rhs_records = middles.flat_map { |r|
|
50
|
+
association = r.association source_reflection.name
|
51
|
+
|
52
|
+
association.reader
|
53
|
+
}.compact
|
54
|
+
|
55
|
+
rhs_records.sort_by { |rhs| record_offset[rhs] }
|
42
56
|
end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def reset_association(owners, association_name)
|
63
|
+
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
|
+
(reflection.options[:source_type] && through_reflection.collection?)
|
43
65
|
|
44
|
-
|
45
|
-
|
66
|
+
# Don't cache the association - we would only be caching a subset
|
67
|
+
if should_reset
|
68
|
+
owners.each { |owner|
|
69
|
+
owner.association(association_name).reset
|
70
|
+
}
|
71
|
+
end
|
46
72
|
end
|
47
73
|
|
48
|
-
|
49
|
-
|
74
|
+
|
75
|
+
def through_scope
|
76
|
+
scope = through_reflection.klass.unscoped
|
50
77
|
|
51
78
|
if options[:source_type]
|
52
|
-
|
79
|
+
scope.where! reflection.foreign_type => options[:source_type]
|
53
80
|
else
|
54
|
-
|
55
|
-
|
56
|
-
|
81
|
+
unless reflection_scope.where_values.empty?
|
82
|
+
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
83
|
+
scope.where_values = reflection_scope.values[:where]
|
84
|
+
scope.bind_values = reflection_scope.bind_values
|
57
85
|
end
|
58
86
|
|
59
|
-
|
87
|
+
scope.references! reflection_scope.values[:references]
|
88
|
+
scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
|
60
89
|
end
|
61
90
|
|
62
|
-
|
91
|
+
scope
|
63
92
|
end
|
64
93
|
end
|
65
94
|
end
|
@@ -2,47 +2,57 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
# Implements the details of eager loading of Active Record associations.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# However, there are two different eager loading strategies.
|
5
|
+
# Suppose that you have the following two Active Record models:
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# and all of its books via a single query:
|
7
|
+
# class Author < ActiveRecord::Base
|
8
|
+
# # columns: name, age
|
9
|
+
# has_many :books
|
10
|
+
# end
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# # columns: title, sales, author_id
|
14
|
+
# end
|
17
15
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# '
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
16
|
+
# When you load an author with all associated books Active Record will make
|
17
|
+
# multiple queries like this:
|
18
|
+
#
|
19
|
+
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
20
|
+
#
|
21
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
23
|
+
#
|
24
|
+
# Active Record saves the ids of the records from the first query to use in
|
25
|
+
# the second. Depending on the number of associations involved there can be
|
26
|
+
# arbitrarily many SQL queries made.
|
27
|
+
#
|
28
|
+
# However, if there is a WHERE clause that spans across tables Active
|
29
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
30
|
+
#
|
31
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
32
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
33
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
34
|
+
# FROM `authors`
|
35
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
36
|
+
# WHERE `books`.`title` = 'Illiad'
|
37
|
+
#
|
38
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
39
|
+
# and is therefore only used when necessary.
|
26
40
|
#
|
27
|
-
# The second strategy is to use multiple database queries, one for each
|
28
|
-
# level of association. Since Rails 2.1, this is the default strategy. In
|
29
|
-
# situations where a table join is necessary (e.g. when the +:conditions+
|
30
|
-
# option references an association's column), it will fallback to the table
|
31
|
-
# join strategy.
|
32
41
|
class Preloader #:nodoc:
|
33
|
-
|
34
|
-
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
35
|
-
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
36
|
-
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
42
|
+
extend ActiveSupport::Autoload
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
44
|
+
eager_autoload do
|
45
|
+
autoload :Association, 'active_record/associations/preloader/association'
|
46
|
+
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
47
|
+
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
48
|
+
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
44
49
|
|
45
|
-
|
50
|
+
autoload :HasMany, 'active_record/associations/preloader/has_many'
|
51
|
+
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
52
|
+
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
53
|
+
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
54
|
+
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
55
|
+
end
|
46
56
|
|
47
57
|
# Eager loads the named associations for the given Active Record record(s).
|
48
58
|
#
|
@@ -68,7 +78,7 @@ module ActiveRecord
|
|
68
78
|
# books.
|
69
79
|
# - a Hash which specifies multiple association names, as well as
|
70
80
|
# association names for the to-be-preloaded association objects. For
|
71
|
-
# example, specifying <tt>{ :
|
81
|
+
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
72
82
|
# book's author, as well as that author's avatar.
|
73
83
|
#
|
74
84
|
# +:associations+ has the same format as the +:include+ option for
|
@@ -76,43 +86,50 @@ module ActiveRecord
|
|
76
86
|
#
|
77
87
|
# :books
|
78
88
|
# [ :books, :author ]
|
79
|
-
# { :
|
80
|
-
# [ :books, { :
|
81
|
-
#
|
82
|
-
# +options+ contains options that will be passed to ActiveRecord::Base#find
|
83
|
-
# (which is called under the hood for preloading records). But it is passed
|
84
|
-
# only one level deep in the +associations+ argument, i.e. it's not passed
|
85
|
-
# to the child associations when +associations+ is a Hash.
|
86
|
-
def initialize(records, associations, options = {})
|
87
|
-
@records = Array.wrap(records).compact.uniq
|
88
|
-
@associations = Array.wrap(associations)
|
89
|
-
@options = options
|
90
|
-
end
|
89
|
+
# { author: :avatar }
|
90
|
+
# [ :books, { author: :avatar } ]
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
|
93
|
+
|
94
|
+
def preload(records, associations, preload_scope = nil)
|
95
|
+
records = Array.wrap(records).compact.uniq
|
96
|
+
associations = Array.wrap(associations)
|
97
|
+
preload_scope = preload_scope || NULL_RELATION
|
98
|
+
|
99
|
+
if records.empty?
|
100
|
+
[]
|
101
|
+
else
|
102
|
+
associations.flat_map { |association|
|
103
|
+
preloaders_on association, records, preload_scope
|
104
|
+
}
|
95
105
|
end
|
96
106
|
end
|
97
107
|
|
98
108
|
private
|
99
109
|
|
100
|
-
def
|
110
|
+
def preloaders_on(association, records, scope)
|
101
111
|
case association
|
102
112
|
when Hash
|
103
|
-
|
104
|
-
when
|
105
|
-
|
113
|
+
preloaders_for_hash(association, records, scope)
|
114
|
+
when Symbol
|
115
|
+
preloaders_for_one(association, records, scope)
|
116
|
+
when String
|
117
|
+
preloaders_for_one(association.to_sym, records, scope)
|
106
118
|
else
|
107
119
|
raise ArgumentError, "#{association.inspect} was not recognised for preload"
|
108
120
|
end
|
109
121
|
end
|
110
122
|
|
111
|
-
def
|
112
|
-
association.
|
113
|
-
|
114
|
-
|
115
|
-
|
123
|
+
def preloaders_for_hash(association, records, scope)
|
124
|
+
association.flat_map { |parent, child|
|
125
|
+
loaders = preloaders_for_one parent, records, scope
|
126
|
+
|
127
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
128
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
129
|
+
preloaders_on assoc, recs, scope
|
130
|
+
}
|
131
|
+
loaders
|
132
|
+
}
|
116
133
|
end
|
117
134
|
|
118
135
|
# Not all records have the same class, so group then preload group on the reflection
|
@@ -122,52 +139,61 @@ module ActiveRecord
|
|
122
139
|
# Additionally, polymorphic belongs_to associations can have multiple associated
|
123
140
|
# classes, depending on the polymorphic_type field. So we group by the classes as
|
124
141
|
# well.
|
125
|
-
def
|
126
|
-
grouped_records(association).
|
127
|
-
klasses.
|
128
|
-
preloader_for(reflection).new(
|
142
|
+
def preloaders_for_one(association, records, scope)
|
143
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
144
|
+
klasses.map do |rhs_klass, rs|
|
145
|
+
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
|
146
|
+
loader.run self
|
147
|
+
loader
|
129
148
|
end
|
130
149
|
end
|
131
150
|
end
|
132
151
|
|
133
|
-
def grouped_records(association)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
152
|
+
def grouped_records(association, records)
|
153
|
+
h = {}
|
154
|
+
records.each do |record|
|
155
|
+
next unless record
|
156
|
+
assoc = record.association(association)
|
157
|
+
klasses = h[assoc.reflection] ||= {}
|
158
|
+
(klasses[assoc.klass] ||= []) << record
|
159
|
+
end
|
160
|
+
h
|
139
161
|
end
|
140
162
|
|
141
|
-
|
142
|
-
|
143
|
-
reflection = record.class.reflections[association]
|
163
|
+
class AlreadyLoaded # :nodoc:
|
164
|
+
attr_reader :owners, :reflection
|
144
165
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
166
|
+
def initialize(klass, owners, reflection, preload_scope)
|
167
|
+
@owners = owners
|
168
|
+
@reflection = reflection
|
169
|
+
end
|
149
170
|
|
150
|
-
|
171
|
+
def run(preloader); end
|
172
|
+
|
173
|
+
def preloaded_records
|
174
|
+
owners.flat_map { |owner| owner.association(reflection.name).target }
|
151
175
|
end
|
152
176
|
end
|
153
177
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
else
|
159
|
-
reflection.klass
|
160
|
-
end
|
178
|
+
class NullPreloader # :nodoc:
|
179
|
+
def self.new(klass, owners, reflection, preload_scope); self; end
|
180
|
+
def self.run(preloader); end
|
181
|
+
def self.preloaded_records; []; end
|
161
182
|
end
|
162
183
|
|
163
|
-
def preloader_for(reflection)
|
184
|
+
def preloader_for(reflection, owners, rhs_klass)
|
185
|
+
return NullPreloader unless rhs_klass
|
186
|
+
|
187
|
+
if owners.first.association(reflection.name).loaded?
|
188
|
+
return AlreadyLoaded
|
189
|
+
end
|
190
|
+
reflection.check_preloadable!
|
191
|
+
|
164
192
|
case reflection.macro
|
165
193
|
when :has_many
|
166
194
|
reflection.options[:through] ? HasManyThrough : HasMany
|
167
195
|
when :has_one
|
168
196
|
reflection.options[:through] ? HasOneThrough : HasOne
|
169
|
-
when :has_and_belongs_to_many
|
170
|
-
HasAndBelongsToMany
|
171
197
|
when :belongs_to
|
172
198
|
BelongsTo
|
173
199
|
end
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class SingularAssociation < Association #:nodoc:
|
4
4
|
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
5
|
def reader(force_reload = false)
|
6
|
-
if force_reload
|
6
|
+
if force_reload && klass
|
7
7
|
klass.uncached { reload }
|
8
8
|
elsif !loaded? || stale_target?
|
9
9
|
reload
|
@@ -12,21 +12,21 @@ module ActiveRecord
|
|
12
12
|
target
|
13
13
|
end
|
14
14
|
|
15
|
-
# Implements the writer method, e.g. foo.
|
15
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
16
16
|
def writer(record)
|
17
17
|
replace(record)
|
18
18
|
end
|
19
19
|
|
20
|
-
def create(attributes = {},
|
21
|
-
|
20
|
+
def create(attributes = {}, &block)
|
21
|
+
_create_record(attributes, &block)
|
22
22
|
end
|
23
23
|
|
24
|
-
def create!(attributes = {},
|
25
|
-
|
24
|
+
def create!(attributes = {}, &block)
|
25
|
+
_create_record(attributes, true, &block)
|
26
26
|
end
|
27
27
|
|
28
|
-
def build(attributes = {}
|
29
|
-
record = build_record(attributes
|
28
|
+
def build(attributes = {})
|
29
|
+
record = build_record(attributes)
|
30
30
|
yield(record) if block_given?
|
31
31
|
set_new_record(record)
|
32
32
|
record
|
@@ -35,14 +35,30 @@ module ActiveRecord
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def create_scope
|
38
|
-
|
38
|
+
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_records
|
42
|
+
return scope.limit(1).to_a if skip_statement_cache?
|
43
|
+
|
44
|
+
conn = klass.connection
|
45
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
46
|
+
StatementCache.create(conn) { |params|
|
47
|
+
as = AssociationScope.create { params.bind }
|
48
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
53
|
+
sc.execute binds, klass, klass.connection
|
39
54
|
end
|
40
55
|
|
41
56
|
def find_target
|
42
|
-
|
57
|
+
if record = get_records.first
|
58
|
+
set_inverse_instance record
|
59
|
+
end
|
43
60
|
end
|
44
61
|
|
45
|
-
# Implemented by subclasses
|
46
62
|
def replace(record)
|
47
63
|
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
48
64
|
end
|
@@ -51,8 +67,8 @@ module ActiveRecord
|
|
51
67
|
replace(record)
|
52
68
|
end
|
53
69
|
|
54
|
-
def
|
55
|
-
record = build_record(attributes
|
70
|
+
def _create_record(attributes, raise_error = false)
|
71
|
+
record = build_record(attributes)
|
56
72
|
yield(record) if block_given?
|
57
73
|
saved = record.save
|
58
74
|
set_new_record(record)
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
module Associations
|
4
4
|
module ThroughAssociation #:nodoc:
|
5
5
|
|
6
|
-
delegate :source_reflection, :through_reflection, :
|
6
|
+
delegate :source_reflection, :through_reflection, :to => :reflection
|
7
7
|
|
8
8
|
protected
|
9
9
|
|
@@ -13,10 +13,10 @@ module ActiveRecord
|
|
13
13
|
# 2. To get the type conditions for any STI models in the chain
|
14
14
|
def target_scope
|
15
15
|
scope = super
|
16
|
-
chain
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
reflection.chain.drop(1).each do |reflection|
|
17
|
+
relation = reflection.klass.all
|
18
|
+
scope.merge!(
|
19
|
+
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
20
20
|
)
|
21
21
|
end
|
22
22
|
scope
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
# methods which create and delete records on the association.
|
29
29
|
#
|
30
30
|
# We only support indirectly modifying through associations which has a belongs_to source.
|
31
|
-
# This is the "has_many :tags, :
|
31
|
+
# This is the "has_many :tags, through: :taggings" situation, where the join model
|
32
32
|
# typically has a belongs_to on both side. In other words, associations which could also
|
33
33
|
# be represented as has_and_belongs_to_many associations.
|
34
34
|
#
|
@@ -37,16 +37,18 @@ module ActiveRecord
|
|
37
37
|
# situation it is more natural for the user to just create or modify their join records
|
38
38
|
# directly as required.
|
39
39
|
def construct_join_attributes(*records)
|
40
|
-
|
41
|
-
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
42
|
-
end
|
40
|
+
ensure_mutable
|
43
41
|
|
44
|
-
|
45
|
-
source_reflection.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
|
43
|
+
join_attributes = { source_reflection.name => records }
|
44
|
+
else
|
45
|
+
join_attributes = {
|
46
|
+
source_reflection.foreign_key =>
|
47
|
+
records.map { |record|
|
48
|
+
record.send(source_reflection.association_primary_key(reflection.klass))
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
50
52
|
|
51
53
|
if options[:source_type]
|
52
54
|
join_attributes[source_reflection.foreign_type] =
|
@@ -63,14 +65,19 @@ module ActiveRecord
|
|
63
65
|
# Note: this does not capture all cases, for example it would be crazy to try to
|
64
66
|
# properly support stale-checking for nested associations.
|
65
67
|
def stale_state
|
66
|
-
if through_reflection.
|
67
|
-
owner[through_reflection.foreign_key].to_s
|
68
|
+
if through_reflection.belongs_to?
|
69
|
+
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
73
|
def foreign_key_present?
|
72
|
-
through_reflection.
|
73
|
-
|
74
|
+
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def ensure_mutable
|
78
|
+
unless source_reflection.belongs_to?
|
79
|
+
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
80
|
+
end
|
74
81
|
end
|
75
82
|
|
76
83
|
def ensure_not_nested
|
@@ -78,6 +85,17 @@ module ActiveRecord
|
|
78
85
|
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
|
79
86
|
end
|
80
87
|
end
|
88
|
+
|
89
|
+
def build_record(attributes)
|
90
|
+
inverse = source_reflection.inverse_of
|
91
|
+
target = through_association.target
|
92
|
+
|
93
|
+
if inverse && target && !target.is_a?(Array)
|
94
|
+
attributes[inverse.foreign_key] = target.id
|
95
|
+
end
|
96
|
+
|
97
|
+
super(attributes)
|
98
|
+
end
|
81
99
|
end
|
82
100
|
end
|
83
101
|
end
|