activerecord 4.2.0 → 5.2.8.1
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 +5 -5
- data/CHANGELOG.md +640 -928
- data/MIT-LICENSE +2 -2
- data/README.rdoc +10 -11
- data/examples/performance.rb +32 -31
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +264 -247
- data/lib/active_record/association_relation.rb +24 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +87 -41
- data/lib/active_record/associations/association_scope.rb +106 -132
- data/lib/active_record/associations/belongs_to_association.rb +55 -36
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
- data/lib/active_record/associations/builder/association.rb +29 -38
- data/lib/active_record/associations/builder/belongs_to.rb +77 -30
- data/lib/active_record/associations/builder/collection_association.rb +14 -23
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
- data/lib/active_record/associations/builder/has_many.rb +6 -4
- data/lib/active_record/associations/builder/has_one.rb +13 -6
- data/lib/active_record/associations/builder/singular_association.rb +15 -11
- data/lib/active_record/associations/collection_association.rb +145 -266
- data/lib/active_record/associations/collection_proxy.rb +242 -138
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +35 -75
- data/lib/active_record/associations/has_many_through_association.rb +51 -69
- data/lib/active_record/associations/has_one_association.rb +39 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/join_dependency.rb +134 -154
- data/lib/active_record/associations/preloader/association.rb +85 -116
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +83 -93
- data/lib/active_record/associations/singular_association.rb +27 -40
- data/lib/active_record/associations/through_association.rb +48 -23
- data/lib/active_record/associations.rb +1732 -1596
- data/lib/active_record/attribute_assignment.rb +58 -182
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
- data/lib/active_record/attribute_methods/dirty.rb +94 -125
- data/lib/active_record/attribute_methods/primary_key.rb +86 -71
- data/lib/active_record/attribute_methods/query.rb +4 -2
- data/lib/active_record/attribute_methods/read.rb +45 -63
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
- data/lib/active_record/attribute_methods/write.rb +31 -46
- data/lib/active_record/attribute_methods.rb +170 -117
- data/lib/active_record/attributes.rb +201 -74
- data/lib/active_record/autosave_association.rb +118 -45
- data/lib/active_record/base.rb +60 -48
- data/lib/active_record/callbacks.rb +97 -57
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +37 -13
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
- data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
- data/lib/active_record/connection_adapters/column.rb +50 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +40 -27
- data/lib/active_record/core.rb +205 -202
- data/lib/active_record/counter_cache.rb +80 -37
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +136 -90
- data/lib/active_record/errors.rb +180 -52
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +193 -135
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +148 -112
- data/lib/active_record/integration.rb +70 -28
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +92 -98
- data/lib/active_record/locking/pessimistic.rb +15 -3
- data/lib/active_record/log_subscriber.rb +95 -33
- data/lib/active_record/migration/command_recorder.rb +133 -90
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/migration.rb +594 -267
- data/lib/active_record/model_schema.rb +292 -111
- data/lib/active_record/nested_attributes.rb +266 -214
- data/lib/active_record/no_touching.rb +8 -2
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +350 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +117 -35
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +9 -3
- data/lib/active_record/railties/databases.rake +160 -174
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +447 -288
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +204 -55
- data/lib/active_record/relation/calculations.rb +259 -244
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +290 -253
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +91 -68
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +118 -92
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +446 -389
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -16
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +287 -339
- data/lib/active_record/result.rb +54 -36
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +155 -124
- data/lib/active_record/schema.rb +30 -24
- data/lib/active_record/schema_dumper.rb +91 -87
- data/lib/active_record/schema_migration.rb +19 -19
- data/lib/active_record/scoping/default.rb +102 -84
- data/lib/active_record/scoping/named.rb +81 -32
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +45 -35
- data/lib/active_record/store.rb +42 -36
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +136 -95
- data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +208 -123
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +4 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +30 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type.rb +79 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +41 -32
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +36 -21
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
- data/lib/rails/generators/active_record/migration.rb +18 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +77 -53
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -149
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -30
- data/lib/active_record/type/decimal.rb +0 -40
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -55
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Associations
|
3
5
|
class Preloader
|
4
6
|
class Association #:nodoc:
|
5
|
-
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
7
|
attr_reader :preloaded_records
|
7
8
|
|
8
9
|
def initialize(klass, owners, reflection, preload_scope)
|
@@ -11,151 +12,119 @@ module ActiveRecord
|
|
11
12
|
@reflection = reflection
|
12
13
|
@preload_scope = preload_scope
|
13
14
|
@model = owners.first && owners.first.class
|
14
|
-
@scope = nil
|
15
|
-
@owners_by_key = nil
|
16
15
|
@preloaded_records = []
|
17
16
|
end
|
18
17
|
|
19
18
|
def run(preloader)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def scope
|
28
|
-
@scope ||= build_scope
|
29
|
-
end
|
30
|
-
|
31
|
-
def records_for(ids)
|
32
|
-
query_scope(ids)
|
33
|
-
end
|
34
|
-
|
35
|
-
def query_scope(ids)
|
36
|
-
scope.where(association_key.in(ids))
|
37
|
-
end
|
38
|
-
|
39
|
-
def table
|
40
|
-
klass.arel_table
|
41
|
-
end
|
42
|
-
|
43
|
-
# The name of the key on the associated records
|
44
|
-
def association_key_name
|
45
|
-
raise NotImplementedError
|
46
|
-
end
|
47
|
-
|
48
|
-
# This is overridden by HABTM as the condition should be on the foreign_key column in
|
49
|
-
# the join table
|
50
|
-
def association_key
|
51
|
-
table[association_key_name]
|
52
|
-
end
|
53
|
-
|
54
|
-
# The name of the key on the model which declares the association
|
55
|
-
def owner_key_name
|
56
|
-
raise NotImplementedError
|
57
|
-
end
|
19
|
+
records = load_records do |record|
|
20
|
+
owner = owners_by_key[convert_key(record[association_key_name])]
|
21
|
+
association = owner.association(reflection.name)
|
22
|
+
association.set_inverse_instance(record)
|
23
|
+
end
|
58
24
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
owner[owner_key_name].to_s
|
63
|
-
end
|
64
|
-
else
|
65
|
-
owners.group_by do |owner|
|
66
|
-
owner[owner_key_name]
|
67
|
-
end
|
68
|
-
end
|
25
|
+
owners.each do |owner|
|
26
|
+
associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
|
27
|
+
end
|
69
28
|
end
|
70
29
|
|
71
|
-
|
72
|
-
reflection
|
73
|
-
end
|
30
|
+
protected
|
31
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
74
32
|
|
75
33
|
private
|
34
|
+
# The name of the key on the associated records
|
35
|
+
def association_key_name
|
36
|
+
reflection.join_primary_key(klass)
|
37
|
+
end
|
76
38
|
|
77
|
-
|
78
|
-
|
79
|
-
|
39
|
+
# The name of the key on the model which declares the association
|
40
|
+
def owner_key_name
|
41
|
+
reflection.join_foreign_key
|
42
|
+
end
|
80
43
|
|
81
|
-
|
82
|
-
|
83
|
-
|
44
|
+
def associate_records_to_owner(owner, records)
|
45
|
+
association = owner.association(reflection.name)
|
46
|
+
association.loaded!
|
47
|
+
if reflection.collection?
|
48
|
+
association.target.concat(records)
|
49
|
+
else
|
50
|
+
association.target = records.first unless records.empty?
|
51
|
+
end
|
84
52
|
end
|
85
53
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
54
|
+
def owner_keys
|
55
|
+
@owner_keys ||= owners_by_key.keys
|
56
|
+
end
|
90
57
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
58
|
+
def owners_by_key
|
59
|
+
unless defined?(@owners_by_key)
|
60
|
+
@owners_by_key = owners.each_with_object({}) do |owner, h|
|
61
|
+
key = convert_key(owner[owner_key_name])
|
62
|
+
h[key] = owner if key
|
95
63
|
end
|
96
64
|
end
|
65
|
+
@owners_by_key
|
97
66
|
end
|
98
67
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
association_key_type != owner_key_type
|
104
|
-
end
|
105
|
-
|
106
|
-
def association_key_type
|
107
|
-
@klass.type_for_attribute(association_key_name.to_s).type
|
108
|
-
end
|
68
|
+
def key_conversion_required?
|
69
|
+
unless defined?(@key_conversion_required)
|
70
|
+
@key_conversion_required = (association_key_type != owner_key_type)
|
71
|
+
end
|
109
72
|
|
110
|
-
|
111
|
-
|
112
|
-
end
|
73
|
+
@key_conversion_required
|
74
|
+
end
|
113
75
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
76
|
+
def convert_key(key)
|
77
|
+
if key_conversion_required?
|
78
|
+
key.to_s
|
79
|
+
else
|
80
|
+
key
|
81
|
+
end
|
82
|
+
end
|
118
83
|
|
119
|
-
|
120
|
-
|
121
|
-
|
84
|
+
def association_key_type
|
85
|
+
@klass.type_for_attribute(association_key_name).type
|
86
|
+
end
|
122
87
|
|
123
|
-
|
124
|
-
|
125
|
-
|
88
|
+
def owner_key_type
|
89
|
+
@model.type_for_attribute(owner_key_name).type
|
90
|
+
end
|
126
91
|
|
127
|
-
|
128
|
-
|
129
|
-
|
92
|
+
def load_records(&block)
|
93
|
+
return {} if owner_keys.empty?
|
94
|
+
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
95
|
+
# Make several smaller queries if necessary or make one query if the adapter supports it
|
96
|
+
slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
97
|
+
@preloaded_records = slices.flat_map do |slice|
|
98
|
+
records_for(slice, &block)
|
99
|
+
end
|
100
|
+
@preloaded_records.group_by do |record|
|
101
|
+
convert_key(record[association_key_name])
|
102
|
+
end
|
103
|
+
end
|
130
104
|
|
131
|
-
|
132
|
-
|
105
|
+
def records_for(ids, &block)
|
106
|
+
scope.where(association_key_name => ids).load(&block)
|
107
|
+
end
|
133
108
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
preload_binds = preload_scope.bind_values
|
109
|
+
def scope
|
110
|
+
@scope ||= build_scope
|
111
|
+
end
|
138
112
|
|
139
|
-
|
140
|
-
|
141
|
-
|
113
|
+
def reflection_scope
|
114
|
+
@reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
|
115
|
+
end
|
142
116
|
|
143
|
-
|
144
|
-
|
145
|
-
scope.joins! preload_values[:joins] || values[:joins]
|
146
|
-
scope.order! preload_values[:order] || values[:order]
|
117
|
+
def build_scope
|
118
|
+
scope = klass.scope_for_association
|
147
119
|
|
148
|
-
|
149
|
-
|
150
|
-
|
120
|
+
if reflection.type
|
121
|
+
scope.where!(reflection.type => model.polymorphic_name)
|
122
|
+
end
|
151
123
|
|
152
|
-
|
153
|
-
scope.
|
124
|
+
scope.merge!(reflection_scope) if reflection.scope
|
125
|
+
scope.merge!(preload_scope) if preload_scope
|
126
|
+
scope
|
154
127
|
end
|
155
|
-
|
156
|
-
scope.unscope_values = Array(values[:unscope])
|
157
|
-
klass.default_scoped.merge(scope)
|
158
|
-
end
|
159
128
|
end
|
160
129
|
end
|
161
130
|
end
|
@@ -1,95 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Associations
|
3
5
|
class Preloader
|
4
|
-
|
5
|
-
def
|
6
|
-
|
7
|
-
|
6
|
+
class ThroughAssociation < Association # :nodoc:
|
7
|
+
def run(preloader)
|
8
|
+
already_loaded = owners.first.association(through_reflection.name).loaded?
|
9
|
+
through_scope = through_scope()
|
10
|
+
reflection_scope = target_reflection_scope
|
11
|
+
through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
|
12
|
+
middle_records = through_preloaders.flat_map(&:preloaded_records)
|
13
|
+
preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
|
14
|
+
@preloaded_records = preloaders.flat_map(&:preloaded_records)
|
8
15
|
|
9
|
-
|
10
|
-
|
16
|
+
owners.each do |owner|
|
17
|
+
through_records = Array(owner.association(through_reflection.name).target)
|
18
|
+
if already_loaded
|
19
|
+
if source_type = reflection.options[:source_type]
|
20
|
+
through_records = through_records.select do |record|
|
21
|
+
record[reflection.foreign_type] == source_type
|
22
|
+
end
|
23
|
+
end
|
24
|
+
else
|
25
|
+
owner.association(through_reflection.name).reset if through_scope
|
26
|
+
end
|
27
|
+
result = through_records.flat_map do |record|
|
28
|
+
association = record.association(source_reflection.name)
|
29
|
+
target = association.target
|
30
|
+
association.reset if preload_scope
|
31
|
+
target
|
32
|
+
end
|
33
|
+
result.compact!
|
34
|
+
if reflection_scope
|
35
|
+
result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
|
36
|
+
result.uniq! if reflection_scope.distinct_value
|
37
|
+
end
|
38
|
+
associate_records_to_owner(owner, result)
|
39
|
+
end
|
11
40
|
end
|
12
41
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
through_scope)
|
17
|
-
|
18
|
-
through_records = owners.map do |owner|
|
19
|
-
association = owner.association through_reflection.name
|
20
|
-
|
21
|
-
[owner, Array(association.reader)]
|
42
|
+
private
|
43
|
+
def through_reflection
|
44
|
+
reflection.through_reflection
|
22
45
|
end
|
23
46
|
|
24
|
-
|
25
|
-
|
26
|
-
middle_records = through_records.flat_map { |(_,rec)| rec }
|
27
|
-
|
28
|
-
preloaders = preloader.preload(middle_records,
|
29
|
-
source_reflection.name,
|
30
|
-
reflection_scope)
|
31
|
-
|
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
|
-
}
|
47
|
+
def source_reflection
|
48
|
+
reflection.source_reflection
|
38
49
|
end
|
39
50
|
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
def preload_index
|
52
|
+
@preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
|
53
|
+
result[id] = index
|
54
|
+
end
|
43
55
|
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
def through_scope
|
58
|
+
scope = through_reflection.klass.unscoped
|
59
|
+
options = reflection.options
|
60
|
+
|
61
|
+
if options[:source_type]
|
62
|
+
scope.where! reflection.foreign_type => options[:source_type]
|
63
|
+
elsif !reflection_scope.where_clause.empty?
|
64
|
+
scope.where_clause = reflection_scope.where_clause
|
65
|
+
values = reflection_scope.values
|
66
|
+
|
67
|
+
if includes = values[:includes]
|
68
|
+
scope.includes!(source_reflection.name => includes)
|
69
|
+
else
|
70
|
+
scope.includes!(source_reflection.name)
|
71
|
+
end
|
72
|
+
|
73
|
+
if values[:references] && !values[:references].empty?
|
74
|
+
scope.references!(values[:references])
|
75
|
+
else
|
76
|
+
scope.references!(source_reflection.table_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
if joins = values[:joins]
|
80
|
+
scope.joins!(source_reflection.name => joins)
|
81
|
+
end
|
82
|
+
|
83
|
+
if left_outer_joins = values[:left_outer_joins]
|
84
|
+
scope.left_outer_joins!(source_reflection.name => left_outer_joins)
|
85
|
+
end
|
86
|
+
|
87
|
+
if scope.eager_loading? && order_values = values[:order]
|
88
|
+
scope = scope.order(order_values)
|
89
|
+
end
|
56
90
|
end
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
91
|
|
62
|
-
|
63
|
-
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
|
-
(reflection.options[:source_type] && through_reflection.collection?)
|
65
|
-
|
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
|
-
}
|
92
|
+
scope unless scope.empty_scope?
|
71
93
|
end
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
def through_scope
|
76
|
-
scope = through_reflection.klass.unscoped
|
77
94
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
95
|
+
def target_reflection_scope
|
96
|
+
if preload_scope
|
97
|
+
reflection_scope.merge(preload_scope)
|
98
|
+
elsif reflection.scope
|
99
|
+
reflection_scope
|
100
|
+
else
|
101
|
+
nil
|
85
102
|
end
|
86
|
-
|
87
|
-
scope.references! reflection_scope.values[:references]
|
88
|
-
scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
|
89
103
|
end
|
90
|
-
|
91
|
-
scope
|
92
|
-
end
|
93
104
|
end
|
94
105
|
end
|
95
106
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Associations
|
3
5
|
# Implements the details of eager loading of Active Record associations.
|
@@ -10,13 +12,13 @@ module ActiveRecord
|
|
10
12
|
# end
|
11
13
|
#
|
12
14
|
# class Book < ActiveRecord::Base
|
13
|
-
# # columns: title, sales
|
15
|
+
# # columns: title, sales, author_id
|
14
16
|
# end
|
15
17
|
#
|
16
18
|
# When you load an author with all associated books Active Record will make
|
17
19
|
# multiple queries like this:
|
18
20
|
#
|
19
|
-
# Author.includes(:books).where(:
|
21
|
+
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
20
22
|
#
|
21
23
|
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
24
|
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
@@ -42,16 +44,8 @@ module ActiveRecord
|
|
42
44
|
extend ActiveSupport::Autoload
|
43
45
|
|
44
46
|
eager_autoload do
|
45
|
-
autoload :Association,
|
46
|
-
autoload :
|
47
|
-
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
48
|
-
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
49
|
-
|
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'
|
47
|
+
autoload :Association, "active_record/associations/preloader/association"
|
48
|
+
autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
|
55
49
|
end
|
56
50
|
|
57
51
|
# Eager loads the named associations for the given Active Record record(s).
|
@@ -88,18 +82,14 @@ module ActiveRecord
|
|
88
82
|
# [ :books, :author ]
|
89
83
|
# { author: :avatar }
|
90
84
|
# [ :books, { author: :avatar } ]
|
91
|
-
|
92
|
-
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
|
93
|
-
|
94
85
|
def preload(records, associations, preload_scope = nil)
|
95
|
-
records
|
96
|
-
associations = Array.wrap(associations)
|
97
|
-
preload_scope = preload_scope || NULL_RELATION
|
86
|
+
records = Array.wrap(records).compact
|
98
87
|
|
99
88
|
if records.empty?
|
100
89
|
[]
|
101
90
|
else
|
102
|
-
|
91
|
+
records.uniq!
|
92
|
+
Array.wrap(associations).flat_map { |association|
|
103
93
|
preloaders_on association, records, preload_scope
|
104
94
|
}
|
105
95
|
end
|
@@ -107,97 +97,97 @@ module ActiveRecord
|
|
107
97
|
|
108
98
|
private
|
109
99
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
100
|
+
# Loads all the given data into +records+ for the +association+.
|
101
|
+
def preloaders_on(association, records, scope)
|
102
|
+
case association
|
103
|
+
when Hash
|
104
|
+
preloaders_for_hash(association, records, scope)
|
105
|
+
when Symbol
|
106
|
+
preloaders_for_one(association, records, scope)
|
107
|
+
when String
|
108
|
+
preloaders_for_one(association.to_sym, records, scope)
|
109
|
+
else
|
110
|
+
raise ArgumentError, "#{association.inspect} was not recognized for preload"
|
111
|
+
end
|
120
112
|
end
|
121
|
-
end
|
122
113
|
|
123
|
-
|
124
|
-
|
125
|
-
|
114
|
+
def preloaders_for_hash(association, records, scope)
|
115
|
+
association.flat_map { |parent, child|
|
116
|
+
loaders = preloaders_for_one parent, records, scope
|
126
117
|
|
127
|
-
|
128
|
-
|
129
|
-
|
118
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
119
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
120
|
+
preloaders_on assoc, recs, scope
|
121
|
+
}
|
122
|
+
loaders
|
130
123
|
}
|
131
|
-
|
132
|
-
}
|
133
|
-
end
|
124
|
+
end
|
134
125
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
126
|
+
# Loads all the given data into +records+ for a singular +association+.
|
127
|
+
#
|
128
|
+
# Functions by instantiating a preloader class such as Preloader::HasManyThrough and
|
129
|
+
# call the +run+ method for each passed in class in the +records+ argument.
|
130
|
+
#
|
131
|
+
# Not all records have the same class, so group then preload group on the reflection
|
132
|
+
# itself so that if various subclass share the same association then we do not split
|
133
|
+
# them unnecessarily
|
134
|
+
#
|
135
|
+
# Additionally, polymorphic belongs_to associations can have multiple associated
|
136
|
+
# classes, depending on the polymorphic_type field. So we group by the classes as
|
137
|
+
# well.
|
138
|
+
def preloaders_for_one(association, records, scope)
|
139
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
140
|
+
klasses.map do |rhs_klass, rs|
|
141
|
+
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
|
142
|
+
loader.run self
|
143
|
+
loader
|
144
|
+
end
|
148
145
|
end
|
149
146
|
end
|
150
|
-
end
|
151
147
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
148
|
+
def grouped_records(association, records)
|
149
|
+
h = {}
|
150
|
+
records.each do |record|
|
151
|
+
next unless record
|
152
|
+
assoc = record.association(association)
|
153
|
+
next unless assoc.klass
|
154
|
+
klasses = h[assoc.reflection] ||= {}
|
155
|
+
(klasses[assoc.klass] ||= []) << record
|
156
|
+
end
|
157
|
+
h
|
159
158
|
end
|
160
|
-
h
|
161
|
-
end
|
162
159
|
|
163
|
-
|
164
|
-
|
160
|
+
class AlreadyLoaded # :nodoc:
|
161
|
+
def initialize(klass, owners, reflection, preload_scope)
|
162
|
+
@owners = owners
|
163
|
+
@reflection = reflection
|
164
|
+
end
|
165
165
|
|
166
|
-
|
167
|
-
@owners = owners
|
168
|
-
@reflection = reflection
|
169
|
-
end
|
166
|
+
def run(preloader); end
|
170
167
|
|
171
|
-
|
168
|
+
def preloaded_records
|
169
|
+
owners.flat_map { |owner| owner.association(reflection.name).target }
|
170
|
+
end
|
172
171
|
|
173
|
-
|
174
|
-
|
172
|
+
protected
|
173
|
+
attr_reader :owners, :reflection
|
175
174
|
end
|
176
|
-
end
|
177
175
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
def
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
176
|
+
# Returns a class containing the logic needed to load preload the data
|
177
|
+
# and attach it to a relation. The class returned implements a `run` method
|
178
|
+
# that accepts a preloader.
|
179
|
+
def preloader_for(reflection, owners)
|
180
|
+
if owners.all? { |o| o.association(reflection.name).loaded? }
|
181
|
+
return AlreadyLoaded
|
182
|
+
end
|
183
|
+
reflection.check_preloadable!
|
186
184
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
case reflection.macro
|
193
|
-
when :has_many
|
194
|
-
reflection.options[:through] ? HasManyThrough : HasMany
|
195
|
-
when :has_one
|
196
|
-
reflection.options[:through] ? HasOneThrough : HasOne
|
197
|
-
when :belongs_to
|
198
|
-
BelongsTo
|
185
|
+
if reflection.options[:through]
|
186
|
+
ThroughAssociation
|
187
|
+
else
|
188
|
+
Association
|
189
|
+
end
|
199
190
|
end
|
200
|
-
end
|
201
191
|
end
|
202
192
|
end
|
203
193
|
end
|