activerecord 3.2.19 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- 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 +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -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 +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -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/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -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 +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- 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/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- 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 -12
@@ -2,22 +2,16 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class CollectionAssociation < Association #:nodoc:
|
5
|
-
|
6
5
|
private
|
7
6
|
|
8
|
-
def
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
def preload
|
13
|
-
associated_records_by_owner.each do |owner, records|
|
7
|
+
def preload(preloader)
|
8
|
+
associated_records_by_owner(preloader).each do |owner, records|
|
14
9
|
association = owner.association(reflection.name)
|
15
10
|
association.loaded!
|
16
11
|
association.target.concat(records)
|
17
12
|
records.each { |record| association.set_inverse_instance(record) }
|
18
13
|
end
|
19
14
|
end
|
20
|
-
|
21
15
|
end
|
22
16
|
end
|
23
17
|
end
|
@@ -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(&:uniq!)
|
10
12
|
end
|
13
|
+
|
14
|
+
records_by_owner
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class HasOne < SingularAssociation #:nodoc:
|
5
|
-
|
6
5
|
def association_key_name
|
7
6
|
reflection.foreign_key
|
8
7
|
end
|
@@ -10,13 +9,6 @@ module ActiveRecord
|
|
10
9
|
def owner_key_name
|
11
10
|
reflection.active_record_primary_key
|
12
11
|
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def build_scope
|
17
|
-
super.order(preload_options[:order] || options[:order])
|
18
|
-
end
|
19
|
-
|
20
12
|
end
|
21
13
|
end
|
22
14
|
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,98 @@ 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
|
-
|
24
|
-
records.compact!
|
21
|
+
center = target_records_from_association(association)
|
22
|
+
[owner, Array(center)]
|
25
23
|
end
|
26
|
-
end
|
27
24
|
|
28
|
-
|
25
|
+
reset_association owners, through_reflection.name
|
29
26
|
|
30
|
-
|
31
|
-
ActiveRecord::Associations::Preloader.new(
|
32
|
-
owners, through_reflection.name,
|
33
|
-
through_options
|
34
|
-
).run
|
27
|
+
middle_records = through_records.flat_map { |(_,rec)| rec }
|
35
28
|
|
36
|
-
|
37
|
-
|
29
|
+
preloaders = preloader.preload(middle_records,
|
30
|
+
source_reflection.name,
|
31
|
+
reflection_scope)
|
32
|
+
|
33
|
+
@preloaded_records = preloaders.flat_map(&:preloaded_records)
|
34
|
+
|
35
|
+
middle_to_pl = preloaders.each_with_object({}) do |pl,h|
|
36
|
+
pl.owners.each { |middle|
|
37
|
+
h[middle] = pl
|
38
|
+
}
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
through_records.each_with_object({}) do |(lhs,center), records_by_owner|
|
42
|
+
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
|
43
|
+
|
44
|
+
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
|
45
|
+
rhs_records = middles.flat_map { |r|
|
46
|
+
association = r.association source_reflection.name
|
47
|
+
|
48
|
+
target_records_from_association(association)
|
49
|
+
}.compact
|
50
|
+
|
51
|
+
# Respect the order on `reflection_scope` if it exists, else use the natural order.
|
52
|
+
if reflection_scope.values[:order].present?
|
53
|
+
@id_map ||= id_to_index_map @preloaded_records
|
54
|
+
rhs_records.sort_by { |rhs| @id_map[rhs] }
|
55
|
+
else
|
56
|
+
rhs_records
|
57
|
+
end
|
43
58
|
end
|
59
|
+
end
|
60
|
+
end
|
44
61
|
|
45
|
-
|
46
|
-
|
62
|
+
private
|
63
|
+
|
64
|
+
def id_to_index_map(ids)
|
65
|
+
id_map = {}
|
66
|
+
ids.each_with_index { |id, index| id_map[id] = index }
|
67
|
+
id_map
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset_association(owners, association_name)
|
71
|
+
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
72
|
+
(reflection.options[:source_type] && through_reflection.collection?)
|
73
|
+
|
74
|
+
# Don't cache the association - we would only be caching a subset
|
75
|
+
if should_reset
|
76
|
+
owners.each { |owner|
|
77
|
+
owner.association(association_name).reset
|
78
|
+
}
|
79
|
+
end
|
47
80
|
end
|
48
81
|
|
49
|
-
|
50
|
-
|
82
|
+
|
83
|
+
def through_scope
|
84
|
+
scope = through_reflection.klass.unscoped
|
51
85
|
|
52
86
|
if options[:source_type]
|
53
|
-
|
87
|
+
scope.where! reflection.foreign_type => options[:source_type]
|
54
88
|
else
|
55
|
-
|
56
|
-
|
57
|
-
|
89
|
+
unless reflection_scope.where_clause.empty?
|
90
|
+
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
91
|
+
scope.where_clause = reflection_scope.where_clause
|
92
|
+
end
|
93
|
+
|
94
|
+
scope.references! reflection_scope.values[:references]
|
95
|
+
if scope.eager_loading? && order_values = reflection_scope.values[:order]
|
96
|
+
scope = scope.order(order_values)
|
58
97
|
end
|
59
|
-
through_options[:order] = options[:order] if options.has_key?(:order)
|
60
98
|
end
|
61
99
|
|
62
|
-
|
100
|
+
scope
|
101
|
+
end
|
102
|
+
|
103
|
+
def target_records_from_association(association)
|
104
|
+
association.loaded? ? association.target : association.reader
|
63
105
|
end
|
64
106
|
end
|
65
107
|
end
|
@@ -2,33 +2,42 @@ 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
42
|
extend ActiveSupport::Autoload
|
34
43
|
|
@@ -42,11 +51,10 @@ module ActiveRecord
|
|
42
51
|
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
43
52
|
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
44
53
|
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
45
|
-
autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
|
46
54
|
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
47
55
|
end
|
48
56
|
|
49
|
-
|
57
|
+
NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
|
50
58
|
|
51
59
|
# Eager loads the named associations for the given Active Record record(s).
|
52
60
|
#
|
@@ -72,7 +80,7 @@ module ActiveRecord
|
|
72
80
|
# books.
|
73
81
|
# - a Hash which specifies multiple association names, as well as
|
74
82
|
# association names for the to-be-preloaded association objects. For
|
75
|
-
# example, specifying <tt>{ :
|
83
|
+
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
76
84
|
# book's author, as well as that author's avatar.
|
77
85
|
#
|
78
86
|
# +:associations+ has the same format as the +:include+ option for
|
@@ -80,45 +88,55 @@ module ActiveRecord
|
|
80
88
|
#
|
81
89
|
# :books
|
82
90
|
# [ :books, :author ]
|
83
|
-
# { :
|
84
|
-
# [ :books, { :
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# to the child associations when +associations+ is a Hash.
|
90
|
-
def initialize(records, associations, options = {})
|
91
|
-
@records = Array.wrap(records).compact.uniq
|
92
|
-
@associations = Array.wrap(associations)
|
93
|
-
@options = options
|
94
|
-
end
|
91
|
+
# { author: :avatar }
|
92
|
+
# [ :books, { author: :avatar } ]
|
93
|
+
def preload(records, associations, preload_scope = nil)
|
94
|
+
records = Array.wrap(records).compact.uniq
|
95
|
+
associations = Array.wrap(associations)
|
96
|
+
preload_scope = preload_scope || NULL_RELATION
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
-
|
98
|
+
if records.empty?
|
99
|
+
[]
|
100
|
+
else
|
101
|
+
associations.flat_map { |association|
|
102
|
+
preloaders_on association, records, preload_scope
|
103
|
+
}
|
99
104
|
end
|
100
105
|
end
|
101
106
|
|
102
107
|
private
|
103
108
|
|
104
|
-
|
109
|
+
# Loads all the given data into +records+ for the +association+.
|
110
|
+
def preloaders_on(association, records, scope)
|
105
111
|
case association
|
106
112
|
when Hash
|
107
|
-
|
108
|
-
when
|
109
|
-
|
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)
|
110
118
|
else
|
111
|
-
raise ArgumentError, "#{association.inspect} was not
|
119
|
+
raise ArgumentError, "#{association.inspect} was not recognized for preload"
|
112
120
|
end
|
113
121
|
end
|
114
122
|
|
115
|
-
def
|
116
|
-
association.
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
+
}
|
120
133
|
end
|
121
134
|
|
135
|
+
# Loads all the given data into +records+ for a singular +association+.
|
136
|
+
#
|
137
|
+
# Functions by instantiating a preloader class such as Preloader::HasManyThrough and
|
138
|
+
# call the +run+ method for each passed in class in the +records+ argument.
|
139
|
+
#
|
122
140
|
# Not all records have the same class, so group then preload group on the reflection
|
123
141
|
# itself so that if various subclass share the same association then we do not split
|
124
142
|
# them unnecessarily
|
@@ -126,52 +144,66 @@ module ActiveRecord
|
|
126
144
|
# Additionally, polymorphic belongs_to associations can have multiple associated
|
127
145
|
# classes, depending on the polymorphic_type field. So we group by the classes as
|
128
146
|
# well.
|
129
|
-
def
|
130
|
-
grouped_records(association).
|
131
|
-
klasses.
|
132
|
-
preloader_for(reflection).new(
|
147
|
+
def preloaders_for_one(association, records, scope)
|
148
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
149
|
+
klasses.map do |rhs_klass, rs|
|
150
|
+
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
|
151
|
+
loader.run self
|
152
|
+
loader
|
133
153
|
end
|
134
154
|
end
|
135
155
|
end
|
136
156
|
|
137
|
-
def grouped_records(association)
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
157
|
+
def grouped_records(association, records)
|
158
|
+
h = {}
|
159
|
+
records.each do |record|
|
160
|
+
next unless record
|
161
|
+
assoc = record.association(association)
|
162
|
+
klasses = h[assoc.reflection] ||= {}
|
163
|
+
(klasses[assoc.klass] ||= []) << record
|
164
|
+
end
|
165
|
+
h
|
143
166
|
end
|
144
167
|
|
145
|
-
|
146
|
-
|
147
|
-
reflection = record.class.reflections[association]
|
168
|
+
class AlreadyLoaded # :nodoc:
|
169
|
+
attr_reader :owners, :reflection
|
148
170
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
171
|
+
def initialize(klass, owners, reflection, preload_scope)
|
172
|
+
@owners = owners
|
173
|
+
@reflection = reflection
|
174
|
+
end
|
175
|
+
|
176
|
+
def run(preloader); end
|
153
177
|
|
154
|
-
|
178
|
+
def preloaded_records
|
179
|
+
owners.flat_map { |owner| owner.association(reflection.name).target }
|
155
180
|
end
|
156
181
|
end
|
157
182
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
reflection.klass
|
164
|
-
end
|
183
|
+
class NullPreloader # :nodoc:
|
184
|
+
def self.new(klass, owners, reflection, preload_scope); self; end
|
185
|
+
def self.run(preloader); end
|
186
|
+
def self.preloaded_records; []; end
|
187
|
+
def self.owners; []; end
|
165
188
|
end
|
166
189
|
|
167
|
-
|
190
|
+
# Returns a class containing the logic needed to load preload the data
|
191
|
+
# and attach it to a relation. For example +Preloader::Association+ or
|
192
|
+
# +Preloader::HasManyThrough+. The class returned implements a `run` method
|
193
|
+
# that accepts a preloader.
|
194
|
+
def preloader_for(reflection, owners, rhs_klass)
|
195
|
+
return NullPreloader unless rhs_klass
|
196
|
+
|
197
|
+
if owners.first.association(reflection.name).loaded?
|
198
|
+
return AlreadyLoaded
|
199
|
+
end
|
200
|
+
reflection.check_preloadable!
|
201
|
+
|
168
202
|
case reflection.macro
|
169
203
|
when :has_many
|
170
204
|
reflection.options[:through] ? HasManyThrough : HasMany
|
171
205
|
when :has_one
|
172
206
|
reflection.options[:through] ? HasOneThrough : HasOne
|
173
|
-
when :has_and_belongs_to_many
|
174
|
-
HasAndBelongsToMany
|
175
207
|
when :belongs_to
|
176
208
|
BelongsTo
|
177
209
|
end
|
@@ -3,7 +3,13 @@ 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
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
8
|
+
Passing an argument to force an association to reload is now
|
9
|
+
deprecated and will be removed in Rails 5.1. Please call `reload`
|
10
|
+
on the parent object instead.
|
11
|
+
MSG
|
12
|
+
|
7
13
|
klass.uncached { reload }
|
8
14
|
elsif !loaded? || stale_target?
|
9
15
|
reload
|
@@ -12,21 +18,21 @@ module ActiveRecord
|
|
12
18
|
target
|
13
19
|
end
|
14
20
|
|
15
|
-
# Implements the writer method, e.g. foo.
|
21
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
16
22
|
def writer(record)
|
17
23
|
replace(record)
|
18
24
|
end
|
19
25
|
|
20
|
-
def create(attributes = {},
|
21
|
-
|
26
|
+
def create(attributes = {}, &block)
|
27
|
+
_create_record(attributes, &block)
|
22
28
|
end
|
23
29
|
|
24
|
-
def create!(attributes = {},
|
25
|
-
|
30
|
+
def create!(attributes = {}, &block)
|
31
|
+
_create_record(attributes, true, &block)
|
26
32
|
end
|
27
33
|
|
28
|
-
def build(attributes = {}
|
29
|
-
record = build_record(attributes
|
34
|
+
def build(attributes = {})
|
35
|
+
record = build_record(attributes)
|
30
36
|
yield(record) if block_given?
|
31
37
|
set_new_record(record)
|
32
38
|
record
|
@@ -35,14 +41,30 @@ module ActiveRecord
|
|
35
41
|
private
|
36
42
|
|
37
43
|
def create_scope
|
38
|
-
|
44
|
+
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_records
|
48
|
+
return scope.limit(1).records if skip_statement_cache?
|
49
|
+
|
50
|
+
conn = klass.connection
|
51
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
52
|
+
StatementCache.create(conn) { |params|
|
53
|
+
as = AssociationScope.create { params.bind }
|
54
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
59
|
+
sc.execute binds, klass, klass.connection
|
39
60
|
end
|
40
61
|
|
41
62
|
def find_target
|
42
|
-
|
63
|
+
if record = get_records.first
|
64
|
+
set_inverse_instance record
|
65
|
+
end
|
43
66
|
end
|
44
67
|
|
45
|
-
# Implemented by subclasses
|
46
68
|
def replace(record)
|
47
69
|
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
48
70
|
end
|
@@ -51,8 +73,8 @@ module ActiveRecord
|
|
51
73
|
replace(record)
|
52
74
|
end
|
53
75
|
|
54
|
-
def
|
55
|
-
record = build_record(attributes
|
76
|
+
def _create_record(attributes, raise_error = false)
|
77
|
+
record = build_record(attributes)
|
56
78
|
yield(record) if block_given?
|
57
79
|
saved = record.save
|
58
80
|
set_new_record(record)
|