activerecord 5.2.3
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 +937 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +217 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record.rb +188 -0
- data/lib/active_record/aggregations.rb +283 -0
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations.rb +1860 -0
- data/lib/active_record/associations/alias_tracker.rb +81 -0
- data/lib/active_record/associations/association.rb +299 -0
- data/lib/active_record/associations/association_scope.rb +168 -0
- data/lib/active_record/associations/belongs_to_association.rb +130 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +140 -0
- data/lib/active_record/associations/builder/belongs_to.rb +163 -0
- data/lib/active_record/associations/builder/collection_association.rb +82 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
- data/lib/active_record/associations/builder/has_many.rb +17 -0
- data/lib/active_record/associations/builder/has_one.rb +30 -0
- data/lib/active_record/associations/builder/singular_association.rb +42 -0
- data/lib/active_record/associations/collection_association.rb +513 -0
- data/lib/active_record/associations/collection_proxy.rb +1131 -0
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +144 -0
- data/lib/active_record/associations/has_many_through_association.rb +227 -0
- data/lib/active_record/associations/has_one_association.rb +120 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency.rb +262 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +193 -0
- data/lib/active_record/associations/preloader/association.rb +131 -0
- data/lib/active_record/associations/preloader/through_association.rb +107 -0
- data/lib/active_record/associations/singular_association.rb +73 -0
- data/lib/active_record/associations/through_association.rb +121 -0
- data/lib/active_record/attribute_assignment.rb +88 -0
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods.rb +492 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_record/attribute_methods/dirty.rb +150 -0
- data/lib/active_record/attribute_methods/primary_key.rb +143 -0
- data/lib/active_record/attribute_methods/query.rb +42 -0
- data/lib/active_record/attribute_methods/read.rb +85 -0
- data/lib/active_record/attribute_methods/serialization.rb +90 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_record/attribute_methods/write.rb +68 -0
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +498 -0
- data/lib/active_record/base.rb +329 -0
- data/lib/active_record/callbacks.rb +353 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +50 -0
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
- data/lib/active_record/connection_adapters/column.rb +91 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- 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 +129 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
- 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 +573 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +218 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +122 -0
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +380 -0
- data/lib/active_record/explain.rb +50 -0
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +1065 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +283 -0
- data/lib/active_record/integration.rb +155 -0
- 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 +48 -0
- data/lib/active_record/locking/optimistic.rb +198 -0
- data/lib/active_record/locking/pessimistic.rb +89 -0
- data/lib/active_record/log_subscriber.rb +137 -0
- data/lib/active_record/migration.rb +1378 -0
- data/lib/active_record/migration/command_recorder.rb +240 -0
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/model_schema.rb +521 -0
- data/lib/active_record/nested_attributes.rb +600 -0
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +763 -0
- data/lib/active_record/query_cache.rb +45 -0
- data/lib/active_record/querying.rb +70 -0
- data/lib/active_record/railtie.rb +226 -0
- data/lib/active_record/railties/console_sandbox.rb +7 -0
- data/lib/active_record/railties/controller_runtime.rb +56 -0
- data/lib/active_record/railties/databases.rake +377 -0
- data/lib/active_record/readonly_attributes.rb +24 -0
- data/lib/active_record/reflection.rb +1044 -0
- data/lib/active_record/relation.rb +629 -0
- data/lib/active_record/relation/batches.rb +287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +417 -0
- data/lib/active_record/relation/delegation.rb +147 -0
- data/lib/active_record/relation/finder_methods.rb +565 -0
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder.rb +152 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- 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 +19 -0
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1231 -0
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +77 -0
- 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/result.rb +149 -0
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +222 -0
- data/lib/active_record/schema.rb +70 -0
- data/lib/active_record/schema_dumper.rb +255 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +106 -0
- data/lib/active_record/scoping/default.rb +152 -0
- data/lib/active_record/scoping/named.rb +213 -0
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +211 -0
- 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 +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +153 -0
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +502 -0
- data/lib/active_record/translation.rb +24 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- 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 +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/validations.rb +93 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +60 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +238 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- 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.rb +35 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- metadata +333 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
# Implements the details of eager loading of Active Record associations.
|
6
|
+
#
|
7
|
+
# Suppose that you have the following two Active Record models:
|
8
|
+
#
|
9
|
+
# class Author < ActiveRecord::Base
|
10
|
+
# # columns: name, age
|
11
|
+
# has_many :books
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Book < ActiveRecord::Base
|
15
|
+
# # columns: title, sales, author_id
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# When you load an author with all associated books Active Record will make
|
19
|
+
# multiple queries like this:
|
20
|
+
#
|
21
|
+
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
22
|
+
#
|
23
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
24
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
25
|
+
#
|
26
|
+
# Active Record saves the ids of the records from the first query to use in
|
27
|
+
# the second. Depending on the number of associations involved there can be
|
28
|
+
# arbitrarily many SQL queries made.
|
29
|
+
#
|
30
|
+
# However, if there is a WHERE clause that spans across tables Active
|
31
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
32
|
+
#
|
33
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
34
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
35
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
36
|
+
# FROM `authors`
|
37
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
38
|
+
# WHERE `books`.`title` = 'Illiad'
|
39
|
+
#
|
40
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
41
|
+
# and is therefore only used when necessary.
|
42
|
+
#
|
43
|
+
class Preloader #:nodoc:
|
44
|
+
extend ActiveSupport::Autoload
|
45
|
+
|
46
|
+
eager_autoload do
|
47
|
+
autoload :Association, "active_record/associations/preloader/association"
|
48
|
+
autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Eager loads the named associations for the given Active Record record(s).
|
52
|
+
#
|
53
|
+
# In this description, 'association name' shall refer to the name passed
|
54
|
+
# to an association creation method. For example, a model that specifies
|
55
|
+
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
|
56
|
+
# names +:author+ and +:buyers+.
|
57
|
+
#
|
58
|
+
# == Parameters
|
59
|
+
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
|
60
|
+
# i.e. +records+ itself may also contain arrays of records. In any case,
|
61
|
+
# +preload_associations+ will preload the all associations records by
|
62
|
+
# flattening +records+.
|
63
|
+
#
|
64
|
+
# +associations+ specifies one or more associations that you want to
|
65
|
+
# preload. It may be:
|
66
|
+
# - a Symbol or a String which specifies a single association name. For
|
67
|
+
# example, specifying +:books+ allows this method to preload all books
|
68
|
+
# for an Author.
|
69
|
+
# - an Array which specifies multiple association names. This array
|
70
|
+
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
|
71
|
+
# allows this method to preload an author's avatar as well as all of his
|
72
|
+
# books.
|
73
|
+
# - a Hash which specifies multiple association names, as well as
|
74
|
+
# association names for the to-be-preloaded association objects. For
|
75
|
+
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
76
|
+
# book's author, as well as that author's avatar.
|
77
|
+
#
|
78
|
+
# +:associations+ has the same format as the +:include+ option for
|
79
|
+
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
|
80
|
+
#
|
81
|
+
# :books
|
82
|
+
# [ :books, :author ]
|
83
|
+
# { author: :avatar }
|
84
|
+
# [ :books, { author: :avatar } ]
|
85
|
+
def preload(records, associations, preload_scope = nil)
|
86
|
+
records = Array.wrap(records).compact
|
87
|
+
|
88
|
+
if records.empty?
|
89
|
+
[]
|
90
|
+
else
|
91
|
+
records.uniq!
|
92
|
+
Array.wrap(associations).flat_map { |association|
|
93
|
+
preloaders_on association, records, preload_scope
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
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
|
112
|
+
end
|
113
|
+
|
114
|
+
def preloaders_for_hash(association, records, scope)
|
115
|
+
association.flat_map { |parent, child|
|
116
|
+
loaders = preloaders_for_one parent, records, scope
|
117
|
+
|
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
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
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
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
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
|
158
|
+
end
|
159
|
+
|
160
|
+
class AlreadyLoaded # :nodoc:
|
161
|
+
def initialize(klass, owners, reflection, preload_scope)
|
162
|
+
@owners = owners
|
163
|
+
@reflection = reflection
|
164
|
+
end
|
165
|
+
|
166
|
+
def run(preloader); end
|
167
|
+
|
168
|
+
def preloaded_records
|
169
|
+
owners.flat_map { |owner| owner.association(reflection.name).target }
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
attr_reader :owners, :reflection
|
174
|
+
end
|
175
|
+
|
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.first.association(reflection.name).loaded?
|
181
|
+
return AlreadyLoaded
|
182
|
+
end
|
183
|
+
reflection.check_preloadable!
|
184
|
+
|
185
|
+
if reflection.options[:through]
|
186
|
+
ThroughAssociation
|
187
|
+
else
|
188
|
+
Association
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class Preloader
|
6
|
+
class Association #:nodoc:
|
7
|
+
attr_reader :preloaded_records
|
8
|
+
|
9
|
+
def initialize(klass, owners, reflection, preload_scope)
|
10
|
+
@klass = klass
|
11
|
+
@owners = owners
|
12
|
+
@reflection = reflection
|
13
|
+
@preload_scope = preload_scope
|
14
|
+
@model = owners.first && owners.first.class
|
15
|
+
@preloaded_records = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(preloader)
|
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
|
24
|
+
|
25
|
+
owners.each do |owner|
|
26
|
+
associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
32
|
+
|
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
|
38
|
+
|
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
|
43
|
+
|
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
|
52
|
+
end
|
53
|
+
|
54
|
+
def owner_keys
|
55
|
+
@owner_keys ||= owners_by_key.keys
|
56
|
+
end
|
57
|
+
|
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
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@owners_by_key
|
66
|
+
end
|
67
|
+
|
68
|
+
def key_conversion_required?
|
69
|
+
unless defined?(@key_conversion_required)
|
70
|
+
@key_conversion_required = (association_key_type != owner_key_type)
|
71
|
+
end
|
72
|
+
|
73
|
+
@key_conversion_required
|
74
|
+
end
|
75
|
+
|
76
|
+
def convert_key(key)
|
77
|
+
if key_conversion_required?
|
78
|
+
key.to_s
|
79
|
+
else
|
80
|
+
key
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def association_key_type
|
85
|
+
@klass.type_for_attribute(association_key_name).type
|
86
|
+
end
|
87
|
+
|
88
|
+
def owner_key_type
|
89
|
+
@model.type_for_attribute(owner_key_name).type
|
90
|
+
end
|
91
|
+
|
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
|
104
|
+
|
105
|
+
def records_for(ids, &block)
|
106
|
+
scope.where(association_key_name => ids).load(&block)
|
107
|
+
end
|
108
|
+
|
109
|
+
def scope
|
110
|
+
@scope ||= build_scope
|
111
|
+
end
|
112
|
+
|
113
|
+
def reflection_scope
|
114
|
+
@reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_scope
|
118
|
+
scope = klass.scope_for_association
|
119
|
+
|
120
|
+
if reflection.type
|
121
|
+
scope.where!(reflection.type => model.polymorphic_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
scope.merge!(reflection_scope) if reflection.scope
|
125
|
+
scope.merge!(preload_scope) if preload_scope
|
126
|
+
scope
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class Preloader
|
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)
|
15
|
+
|
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
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def through_reflection
|
44
|
+
reflection.through_reflection
|
45
|
+
end
|
46
|
+
|
47
|
+
def source_reflection
|
48
|
+
reflection.source_reflection
|
49
|
+
end
|
50
|
+
|
51
|
+
def preload_index
|
52
|
+
@preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
|
53
|
+
result[id] = index
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
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
|
90
|
+
end
|
91
|
+
|
92
|
+
scope unless scope.empty_scope?
|
93
|
+
end
|
94
|
+
|
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
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class SingularAssociation < Association #:nodoc:
|
6
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
7
|
+
def reader
|
8
|
+
if !loaded? || stale_target?
|
9
|
+
reload
|
10
|
+
end
|
11
|
+
|
12
|
+
target
|
13
|
+
end
|
14
|
+
|
15
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
16
|
+
def writer(record)
|
17
|
+
replace(record)
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(attributes = {}, &block)
|
21
|
+
record = build_record(attributes, &block)
|
22
|
+
set_new_record(record)
|
23
|
+
record
|
24
|
+
end
|
25
|
+
|
26
|
+
# Implements the reload reader method, e.g. foo.reload_bar for
|
27
|
+
# Foo.has_one :bar
|
28
|
+
def force_reload_reader
|
29
|
+
klass.uncached { reload }
|
30
|
+
target
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def scope_for_create
|
35
|
+
super.except!(klass.primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_target
|
39
|
+
scope = self.scope
|
40
|
+
return scope.take if skip_statement_cache?(scope)
|
41
|
+
|
42
|
+
conn = klass.connection
|
43
|
+
sc = reflection.association_scope_cache(conn, owner) do |params|
|
44
|
+
as = AssociationScope.create { params.bind }
|
45
|
+
target_scope.merge!(as.scope(self)).limit(1)
|
46
|
+
end
|
47
|
+
|
48
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
49
|
+
sc.execute(binds, conn) do |record|
|
50
|
+
set_inverse_instance record
|
51
|
+
end.first
|
52
|
+
rescue ::RangeError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def replace(record)
|
57
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_new_record(record)
|
61
|
+
replace(record)
|
62
|
+
end
|
63
|
+
|
64
|
+
def _create_record(attributes, raise_error = false, &block)
|
65
|
+
record = build_record(attributes, &block)
|
66
|
+
saved = record.save
|
67
|
+
set_new_record(record)
|
68
|
+
raise RecordInvalid.new(record) if !saved && raise_error
|
69
|
+
record
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|