activerecord 5.2.8.1 → 6.0.6.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 +4 -4
- data/CHANGELOG.md +938 -573
- data/MIT-LICENSE +3 -1
- data/README.rdoc +5 -3
- data/examples/performance.rb +1 -1
- data/lib/active_record/advisory_lock_base.rb +18 -0
- data/lib/active_record/aggregations.rb +4 -3
- data/lib/active_record/association_relation.rb +10 -8
- data/lib/active_record/associations/alias_tracker.rb +0 -1
- data/lib/active_record/associations/association.rb +55 -19
- data/lib/active_record/associations/association_scope.rb +11 -7
- data/lib/active_record/associations/belongs_to_association.rb +36 -42
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +19 -52
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +19 -23
- data/lib/active_record/associations/collection_proxy.rb +14 -17
- data/lib/active_record/associations/foreign_association.rb +7 -0
- data/lib/active_record/associations/has_many_association.rb +2 -11
- data/lib/active_record/associations/has_many_through_association.rb +14 -14
- data/lib/active_record/associations/has_one_association.rb +28 -30
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
- data/lib/active_record/associations/join_dependency/join_part.rb +4 -4
- data/lib/active_record/associations/join_dependency.rb +47 -30
- data/lib/active_record/associations/preloader/association.rb +61 -41
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +44 -33
- data/lib/active_record/associations/singular_association.rb +2 -16
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +21 -16
- data/lib/active_record/attribute_assignment.rb +7 -11
- data/lib/active_record/attribute_decorators.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
- data/lib/active_record/attribute_methods/dirty.rb +111 -40
- data/lib/active_record/attribute_methods/primary_key.rb +15 -24
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +15 -54
- data/lib/active_record/attribute_methods/serialization.rb +1 -2
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
- data/lib/active_record/attribute_methods/write.rb +17 -25
- data/lib/active_record/attribute_methods.rb +28 -100
- data/lib/active_record/attributes.rb +13 -1
- data/lib/active_record/autosave_association.rb +12 -14
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +6 -21
- data/lib/active_record/coders/yaml_column.rb +15 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -18
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +77 -17
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +105 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -217
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +137 -147
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_handling.rb +139 -26
- data/lib/active_record/core.rb +108 -67
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +44 -7
- data/lib/active_record/errors.rb +15 -7
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +144 -474
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +13 -6
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +68 -16
- data/lib/active_record/internal_metadata.rb +11 -3
- data/lib/active_record/locking/optimistic.rb +14 -7
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +8 -27
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
- data/lib/active_record/middleware/database_selector.rb +74 -0
- data/lib/active_record/migration/command_recorder.rb +54 -22
- data/lib/active_record/migration/compatibility.rb +79 -52
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +104 -85
- data/lib/active_record/model_schema.rb +62 -11
- data/lib/active_record/nested_attributes.rb +2 -4
- data/lib/active_record/no_touching.rb +9 -2
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +232 -29
- data/lib/active_record/query_cache.rb +11 -4
- data/lib/active_record/querying.rb +33 -21
- data/lib/active_record/railtie.rb +80 -61
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +199 -46
- data/lib/active_record/reflection.rb +51 -51
- data/lib/active_record/relation/batches.rb +13 -11
- data/lib/active_record/relation/calculations.rb +55 -49
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +23 -28
- data/lib/active_record/relation/from_clause.rb +4 -0
- data/lib/active_record/relation/merger.rb +12 -17
- data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder.rb +5 -11
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +234 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/relation/where_clause.rb +14 -11
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/relation.rb +326 -81
- data/lib/active_record/result.rb +30 -12
- data/lib/active_record/sanitization.rb +32 -40
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +22 -7
- data/lib/active_record/schema_migration.rb +6 -2
- data/lib/active_record/scoping/default.rb +4 -6
- data/lib/active_record/scoping/named.rb +25 -16
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/statement_cache.rb +30 -3
- data/lib/active_record/store.rb +87 -8
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +23 -15
- data/lib/active_record/tasks/database_tasks.rb +194 -25
- data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +243 -0
- data/lib/active_record/timestamp.rb +39 -26
- data/lib/active_record/touch_later.rb +5 -4
- data/lib/active_record/transactions.rb +64 -73
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +0 -1
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +3 -5
- data/lib/active_record/type_caster/connection.rb +15 -14
- data/lib/active_record/type_caster/map.rb +1 -4
- data/lib/active_record/validations/associated.rb +0 -1
- data/lib/active_record/validations/uniqueness.rb +15 -27
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record.rb +10 -2
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +256 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/depth_first.rb +203 -0
- data/lib/arel/visitors/dot.rb +296 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +156 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +158 -0
- data/lib/arel/visitors/oracle12.rb +65 -0
- data/lib/arel/visitors/postgresql.rb +109 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +888 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors/where_sql.rb +22 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +62 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/migration.rb +14 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +113 -26
- data/lib/active_record/collection_cache_key.rb +0 -53
@@ -14,10 +14,8 @@ module ActiveRecord
|
|
14
14
|
i[column.name] = column.alias
|
15
15
|
}
|
16
16
|
}
|
17
|
-
@
|
18
|
-
h[table.node] = table.columns
|
19
|
-
[column.name, column.alias]
|
20
|
-
}
|
17
|
+
@columns_cache = tables.each_with_object({}) { |table, h|
|
18
|
+
h[table.node] = table.columns
|
21
19
|
}
|
22
20
|
end
|
23
21
|
|
@@ -25,9 +23,8 @@ module ActiveRecord
|
|
25
23
|
@tables.flat_map(&:column_aliases)
|
26
24
|
end
|
27
25
|
|
28
|
-
# An array of [column_name, alias] pairs for the table
|
29
26
|
def column_aliases(node)
|
30
|
-
@
|
27
|
+
@columns_cache[node]
|
31
28
|
end
|
32
29
|
|
33
30
|
def column_alias(node, column)
|
@@ -67,16 +64,21 @@ module ActiveRecord
|
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
|
-
def initialize(base, table, associations)
|
67
|
+
def initialize(base, table, associations, join_type)
|
71
68
|
tree = self.class.make_tree associations
|
72
69
|
@join_root = JoinBase.new(base, table, build(tree, base))
|
70
|
+
@join_type = join_type
|
71
|
+
end
|
72
|
+
|
73
|
+
def base_klass
|
74
|
+
join_root.base_klass
|
73
75
|
end
|
74
76
|
|
75
77
|
def reflections
|
76
78
|
join_root.drop(1).map!(&:reflection)
|
77
79
|
end
|
78
80
|
|
79
|
-
def join_constraints(joins_to_add,
|
81
|
+
def join_constraints(joins_to_add, alias_tracker)
|
80
82
|
@alias_tracker = alias_tracker
|
81
83
|
|
82
84
|
construct_tables!(join_root)
|
@@ -85,9 +87,9 @@ module ActiveRecord
|
|
85
87
|
joins.concat joins_to_add.flat_map { |oj|
|
86
88
|
construct_tables!(oj.join_root)
|
87
89
|
if join_root.match? oj.join_root
|
88
|
-
walk join_root, oj.
|
90
|
+
walk(join_root, oj.join_root, oj.join_type)
|
89
91
|
else
|
90
|
-
make_join_constraints(oj.join_root, join_type)
|
92
|
+
make_join_constraints(oj.join_root, oj.join_type)
|
91
93
|
end
|
92
94
|
}
|
93
95
|
end
|
@@ -103,7 +105,17 @@ module ActiveRecord
|
|
103
105
|
|
104
106
|
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
105
107
|
parents = model_cache[join_root]
|
106
|
-
|
108
|
+
|
109
|
+
column_aliases = aliases.column_aliases(join_root)
|
110
|
+
column_names = explicit_selections(column_aliases, result_set)
|
111
|
+
|
112
|
+
if column_names.empty?
|
113
|
+
column_types = {}
|
114
|
+
else
|
115
|
+
column_types = result_set.column_types
|
116
|
+
column_types = column_types.slice(*column_names) unless column_types.empty?
|
117
|
+
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
118
|
+
end
|
107
119
|
|
108
120
|
message_bus = ActiveSupport::Notifications.instrumenter
|
109
121
|
|
@@ -115,8 +127,8 @@ module ActiveRecord
|
|
115
127
|
message_bus.instrument("instantiation.active_record", payload) do
|
116
128
|
result_set.each { |row_hash|
|
117
129
|
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
118
|
-
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
|
119
|
-
construct(parent, join_root, row_hash,
|
130
|
+
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
131
|
+
construct(parent, join_root, row_hash, seen, model_cache)
|
120
132
|
}
|
121
133
|
end
|
122
134
|
|
@@ -128,9 +140,18 @@ module ActiveRecord
|
|
128
140
|
end
|
129
141
|
|
130
142
|
protected
|
131
|
-
attr_reader :
|
143
|
+
attr_reader :join_root, :join_type
|
132
144
|
|
133
145
|
private
|
146
|
+
attr_reader :alias_tracker
|
147
|
+
|
148
|
+
def explicit_selections(root_column_aliases, result_set)
|
149
|
+
root_names = root_column_aliases.map(&:name).to_set
|
150
|
+
result_set.columns.each_with_object([]) do |name, result|
|
151
|
+
result << name unless /\At\d+_r\d+\z/.match?(name) || root_names.include?(name)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
134
155
|
def aliases
|
135
156
|
@aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
|
136
157
|
columns = join_part.column_names.each_with_index.map { |column_name, j|
|
@@ -152,7 +173,7 @@ module ActiveRecord
|
|
152
173
|
end
|
153
174
|
end
|
154
175
|
|
155
|
-
def make_constraints(parent, child, join_type
|
176
|
+
def make_constraints(parent, child, join_type)
|
156
177
|
foreign_table = parent.table
|
157
178
|
foreign_klass = parent.base_klass
|
158
179
|
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
|
@@ -170,17 +191,17 @@ module ActiveRecord
|
|
170
191
|
end
|
171
192
|
|
172
193
|
def table_alias_for(reflection, parent, join)
|
173
|
-
name =
|
194
|
+
name = reflection.alias_candidate(parent.table_name)
|
174
195
|
join ? "#{name}_join" : name
|
175
196
|
end
|
176
197
|
|
177
|
-
def walk(left, right)
|
198
|
+
def walk(left, right, join_type)
|
178
199
|
intersection, missing = right.children.map { |node1|
|
179
200
|
[left.children.find { |node2| node1.match? node2 }, node1]
|
180
201
|
}.partition(&:first)
|
181
202
|
|
182
|
-
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
|
183
|
-
joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
|
203
|
+
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
|
204
|
+
joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
|
184
205
|
end
|
185
206
|
|
186
207
|
def find_reflection(klass, name)
|
@@ -202,7 +223,7 @@ module ActiveRecord
|
|
202
223
|
end
|
203
224
|
end
|
204
225
|
|
205
|
-
def construct(ar_parent, parent, row,
|
226
|
+
def construct(ar_parent, parent, row, seen, model_cache)
|
206
227
|
return if ar_parent.nil?
|
207
228
|
|
208
229
|
parent.children.each do |node|
|
@@ -211,7 +232,7 @@ module ActiveRecord
|
|
211
232
|
other.loaded!
|
212
233
|
elsif ar_parent.association_cached?(node.reflection.name)
|
213
234
|
model = ar_parent.association(node.reflection.name).target
|
214
|
-
construct(model, node, row,
|
235
|
+
construct(model, node, row, seen, model_cache)
|
215
236
|
next
|
216
237
|
end
|
217
238
|
|
@@ -226,22 +247,17 @@ module ActiveRecord
|
|
226
247
|
model = seen[ar_parent.object_id][node][id]
|
227
248
|
|
228
249
|
if model
|
229
|
-
construct(model, node, row,
|
250
|
+
construct(model, node, row, seen, model_cache)
|
230
251
|
else
|
231
|
-
model = construct_model(ar_parent, node, row, model_cache, id
|
232
|
-
|
233
|
-
if node.reflection.scope &&
|
234
|
-
node.reflection.scope_for(node.base_klass.unscoped).readonly_value
|
235
|
-
model.readonly!
|
236
|
-
end
|
252
|
+
model = construct_model(ar_parent, node, row, model_cache, id)
|
237
253
|
|
238
254
|
seen[ar_parent.object_id][node][id] = model
|
239
|
-
construct(model, node, row,
|
255
|
+
construct(model, node, row, seen, model_cache)
|
240
256
|
end
|
241
257
|
end
|
242
258
|
end
|
243
259
|
|
244
|
-
def construct_model(record, node, row, model_cache, id
|
260
|
+
def construct_model(record, node, row, model_cache, id)
|
245
261
|
other = record.association(node.reflection.name)
|
246
262
|
|
247
263
|
model = model_cache[node][id] ||=
|
@@ -255,6 +271,7 @@ module ActiveRecord
|
|
255
271
|
other.target = model
|
256
272
|
end
|
257
273
|
|
274
|
+
model.readonly! if node.readonly?
|
258
275
|
model
|
259
276
|
end
|
260
277
|
end
|
@@ -4,33 +4,62 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class Association #:nodoc:
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(klass, owners, reflection, preload_scope)
|
7
|
+
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
|
10
8
|
@klass = klass
|
11
|
-
@owners = owners
|
9
|
+
@owners = owners.uniq(&:__id__)
|
12
10
|
@reflection = reflection
|
13
11
|
@preload_scope = preload_scope
|
12
|
+
@associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
|
14
13
|
@model = owners.first && owners.first.class
|
15
|
-
@preloaded_records = []
|
16
14
|
end
|
17
15
|
|
18
|
-
def run
|
19
|
-
records =
|
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
|
16
|
+
def run
|
17
|
+
records = records_by_owner
|
24
18
|
|
25
19
|
owners.each do |owner|
|
26
|
-
associate_records_to_owner(owner, records[
|
27
|
-
end
|
20
|
+
associate_records_to_owner(owner, records[owner] || [])
|
21
|
+
end if @associate
|
22
|
+
|
23
|
+
self
|
28
24
|
end
|
29
25
|
|
30
|
-
|
31
|
-
|
26
|
+
def records_by_owner
|
27
|
+
load_records unless defined?(@records_by_owner)
|
28
|
+
|
29
|
+
@records_by_owner
|
30
|
+
end
|
31
|
+
|
32
|
+
def preloaded_records
|
33
|
+
load_records unless defined?(@preloaded_records)
|
34
|
+
|
35
|
+
@preloaded_records
|
36
|
+
end
|
32
37
|
|
33
38
|
private
|
39
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
40
|
+
|
41
|
+
def load_records
|
42
|
+
# owners can be duplicated when a relation has a collection association join
|
43
|
+
# #compare_by_identity makes such owners different hash keys
|
44
|
+
@records_by_owner = {}.compare_by_identity
|
45
|
+
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
46
|
+
|
47
|
+
@preloaded_records = raw_records.select do |record|
|
48
|
+
assignments = []
|
49
|
+
|
50
|
+
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
51
|
+
entries = (@records_by_owner[owner] ||= [])
|
52
|
+
|
53
|
+
if reflection.collection? || entries.empty?
|
54
|
+
entries << record
|
55
|
+
assignments << record
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
!assignments.empty?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
34
63
|
# The name of the key on the associated records
|
35
64
|
def association_key_name
|
36
65
|
reflection.join_primary_key(klass)
|
@@ -43,11 +72,10 @@ module ActiveRecord
|
|
43
72
|
|
44
73
|
def associate_records_to_owner(owner, records)
|
45
74
|
association = owner.association(reflection.name)
|
46
|
-
association.loaded!
|
47
75
|
if reflection.collection?
|
48
|
-
association.target
|
76
|
+
association.target = records
|
49
77
|
else
|
50
|
-
association.target = records.first
|
78
|
+
association.target = records.first
|
51
79
|
end
|
52
80
|
end
|
53
81
|
|
@@ -56,13 +84,10 @@ module ActiveRecord
|
|
56
84
|
end
|
57
85
|
|
58
86
|
def owners_by_key
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
h[key] = owner if key
|
63
|
-
end
|
87
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
88
|
+
key = convert_key(owner[owner_key_name])
|
89
|
+
(result[key] ||= []) << owner if key
|
64
90
|
end
|
65
|
-
@owners_by_key
|
66
91
|
end
|
67
92
|
|
68
93
|
def key_conversion_required?
|
@@ -89,39 +114,34 @@ module ActiveRecord
|
|
89
114
|
@model.type_for_attribute(owner_key_name).type
|
90
115
|
end
|
91
116
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
@preloaded_records.group_by do |record|
|
101
|
-
convert_key(record[association_key_name])
|
117
|
+
def records_for(ids)
|
118
|
+
scope.where(association_key_name => ids).load do |record|
|
119
|
+
# Processing only the first owner
|
120
|
+
# because the record is modified but not an owner
|
121
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
122
|
+
association = owner.association(reflection.name)
|
123
|
+
association.set_inverse_instance(record)
|
102
124
|
end
|
103
125
|
end
|
104
126
|
|
105
|
-
def records_for(ids, &block)
|
106
|
-
scope.where(association_key_name => ids).load(&block)
|
107
|
-
end
|
108
|
-
|
109
127
|
def scope
|
110
128
|
@scope ||= build_scope
|
111
129
|
end
|
112
130
|
|
113
131
|
def reflection_scope
|
114
|
-
@reflection_scope ||=
|
132
|
+
@reflection_scope ||= begin
|
133
|
+
reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
|
134
|
+
end
|
115
135
|
end
|
116
136
|
|
117
137
|
def build_scope
|
118
138
|
scope = klass.scope_for_association
|
119
139
|
|
120
|
-
if reflection.type
|
140
|
+
if reflection.type && !reflection.through_reflection?
|
121
141
|
scope.where!(reflection.type => model.polymorphic_name)
|
122
142
|
end
|
123
143
|
|
124
|
-
scope.merge!(reflection_scope)
|
144
|
+
scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
|
125
145
|
scope.merge!(preload_scope) if preload_scope
|
126
146
|
scope
|
127
147
|
end
|
@@ -4,42 +4,57 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class ThroughAssociation < Association # :nodoc:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
super
|
11
|
+
@already_loaded = owners.first.association(through_reflection.name).loaded?
|
12
|
+
end
|
13
|
+
|
14
|
+
def preloaded_records
|
15
|
+
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def records_by_owner
|
19
|
+
return @records_by_owner if defined?(@records_by_owner)
|
20
|
+
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
|
21
|
+
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
|
22
|
+
|
23
|
+
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
24
|
+
through_records = through_records_by_owner[owner] || []
|
25
|
+
|
26
|
+
if @already_loaded
|
19
27
|
if source_type = reflection.options[:source_type]
|
20
28
|
through_records = through_records.select do |record|
|
21
29
|
record[reflection.foreign_type] == source_type
|
22
30
|
end
|
23
31
|
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
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
result.uniq! if reflection_scope.distinct_value
|
33
|
+
|
34
|
+
records = through_records.flat_map do |record|
|
35
|
+
source_records_by_owner[record]
|
37
36
|
end
|
38
|
-
|
37
|
+
|
38
|
+
records.compact!
|
39
|
+
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
|
40
|
+
records.uniq! if scope.distinct_value
|
41
|
+
result[owner] = records
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
46
|
+
def source_preloaders
|
47
|
+
@source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
|
48
|
+
end
|
49
|
+
|
50
|
+
def middle_records
|
51
|
+
through_preloaders.flat_map(&:preloaded_records)
|
52
|
+
end
|
53
|
+
|
54
|
+
def through_preloaders
|
55
|
+
@through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
|
56
|
+
end
|
57
|
+
|
43
58
|
def through_reflection
|
44
59
|
reflection.through_reflection
|
45
60
|
end
|
@@ -49,8 +64,8 @@ module ActiveRecord
|
|
49
64
|
end
|
50
65
|
|
51
66
|
def preload_index
|
52
|
-
@preload_index ||=
|
53
|
-
result[
|
67
|
+
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
|
68
|
+
result[record] = index
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
@@ -58,11 +73,15 @@ module ActiveRecord
|
|
58
73
|
scope = through_reflection.klass.unscoped
|
59
74
|
options = reflection.options
|
60
75
|
|
76
|
+
values = reflection_scope.values
|
77
|
+
if annotations = values[:annotate]
|
78
|
+
scope.annotate!(*annotations)
|
79
|
+
end
|
80
|
+
|
61
81
|
if options[:source_type]
|
62
82
|
scope.where! reflection.foreign_type => options[:source_type]
|
63
83
|
elsif !reflection_scope.where_clause.empty?
|
64
84
|
scope.where_clause = reflection_scope.where_clause
|
65
|
-
values = reflection_scope.values
|
66
85
|
|
67
86
|
if includes = values[:includes]
|
68
87
|
scope.includes!(source_reflection.name => includes)
|
@@ -89,17 +108,7 @@ module ActiveRecord
|
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
|
-
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
|
111
|
+
scope
|
103
112
|
end
|
104
113
|
end
|
105
114
|
end
|
@@ -88,44 +88,46 @@ module ActiveRecord
|
|
88
88
|
if records.empty?
|
89
89
|
[]
|
90
90
|
else
|
91
|
-
records.uniq!
|
92
91
|
Array.wrap(associations).flat_map { |association|
|
93
92
|
preloaders_on association, records, preload_scope
|
94
93
|
}
|
95
94
|
end
|
96
95
|
end
|
97
96
|
|
98
|
-
|
97
|
+
def initialize(associate_by_default: true)
|
98
|
+
@associate_by_default = associate_by_default
|
99
|
+
end
|
99
100
|
|
101
|
+
private
|
100
102
|
# Loads all the given data into +records+ for the +association+.
|
101
|
-
def preloaders_on(association, records, scope)
|
103
|
+
def preloaders_on(association, records, scope, polymorphic_parent = false)
|
102
104
|
case association
|
103
105
|
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)
|
106
|
+
preloaders_for_hash(association, records, scope, polymorphic_parent)
|
107
|
+
when Symbol, String
|
108
|
+
preloaders_for_one(association, records, scope, polymorphic_parent)
|
109
109
|
else
|
110
110
|
raise ArgumentError, "#{association.inspect} was not recognized for preload"
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
-
def preloaders_for_hash(association, records, scope)
|
114
|
+
def preloaders_for_hash(association, records, scope, polymorphic_parent)
|
115
115
|
association.flat_map { |parent, child|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
116
|
+
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
|
117
|
+
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
|
118
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
119
|
+
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
|
120
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
121
|
+
preloaders_on assoc, recs, scope, child_polymorphic_parent
|
122
|
+
}
|
123
|
+
loaders
|
124
|
+
end
|
123
125
|
}
|
124
126
|
end
|
125
127
|
|
126
128
|
# Loads all the given data into +records+ for a singular +association+.
|
127
129
|
#
|
128
|
-
# Functions by instantiating a preloader class such as Preloader::
|
130
|
+
# Functions by instantiating a preloader class such as Preloader::Association and
|
129
131
|
# call the +run+ method for each passed in class in the +records+ argument.
|
130
132
|
#
|
131
133
|
# Not all records have the same class, so group then preload group on the reflection
|
@@ -135,41 +137,50 @@ module ActiveRecord
|
|
135
137
|
# Additionally, polymorphic belongs_to associations can have multiple associated
|
136
138
|
# classes, depending on the polymorphic_type field. So we group by the classes as
|
137
139
|
# well.
|
138
|
-
def preloaders_for_one(association, records, scope)
|
139
|
-
grouped_records(association, records
|
140
|
-
|
141
|
-
|
142
|
-
loader.run self
|
143
|
-
loader
|
140
|
+
def preloaders_for_one(association, records, scope, polymorphic_parent)
|
141
|
+
grouped_records(association, records, polymorphic_parent)
|
142
|
+
.flat_map do |reflection, reflection_records|
|
143
|
+
preloaders_for_reflection reflection, reflection_records, scope
|
144
144
|
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def preloaders_for_reflection(reflection, records, scope)
|
148
|
+
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
|
149
|
+
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
|
145
150
|
end
|
146
151
|
end
|
147
152
|
|
148
|
-
def grouped_records(association, records)
|
153
|
+
def grouped_records(association, records, polymorphic_parent)
|
149
154
|
h = {}
|
150
155
|
records.each do |record|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
klasses = h[assoc.reflection] ||= {}
|
155
|
-
(klasses[assoc.klass] ||= []) << record
|
156
|
+
reflection = record.class._reflect_on_association(association)
|
157
|
+
next if polymorphic_parent && !reflection || !record.association(association).klass
|
158
|
+
(h[reflection] ||= []) << record
|
156
159
|
end
|
157
160
|
h
|
158
161
|
end
|
159
162
|
|
160
163
|
class AlreadyLoaded # :nodoc:
|
161
|
-
def initialize(klass, owners, reflection, preload_scope)
|
164
|
+
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
|
162
165
|
@owners = owners
|
163
166
|
@reflection = reflection
|
164
167
|
end
|
165
168
|
|
166
|
-
def run
|
169
|
+
def run
|
170
|
+
self
|
171
|
+
end
|
167
172
|
|
168
173
|
def preloaded_records
|
169
|
-
|
174
|
+
@preloaded_records ||= records_by_owner.flat_map(&:last)
|
175
|
+
end
|
176
|
+
|
177
|
+
def records_by_owner
|
178
|
+
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
|
179
|
+
result[owner] = Array(owner.association(reflection.name).target)
|
180
|
+
end
|
170
181
|
end
|
171
182
|
|
172
|
-
|
183
|
+
private
|
173
184
|
attr_reader :owners, :reflection
|
174
185
|
end
|
175
186
|
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
# Implements the reload reader method, e.g. foo.reload_bar for
|
27
27
|
# Foo.has_one :bar
|
28
28
|
def force_reload_reader
|
29
|
-
|
29
|
+
reload(true)
|
30
30
|
target
|
31
31
|
end
|
32
32
|
|
@@ -36,21 +36,7 @@ module ActiveRecord
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def find_target
|
39
|
-
|
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
|
39
|
+
super.first
|
54
40
|
end
|
55
41
|
|
56
42
|
def replace(record)
|
@@ -32,7 +32,7 @@ module ActiveRecord
|
|
32
32
|
reflection.chain.drop(1).each do |reflection|
|
33
33
|
relation = reflection.klass.scope_for_association
|
34
34
|
scope.merge!(
|
35
|
-
relation.except(:select, :create_with, :includes, :preload, :joins, :
|
35
|
+
relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
|
36
36
|
)
|
37
37
|
end
|
38
38
|
scope
|