activerecord 7.1.3.3 → 7.2.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +507 -2128
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +4 -2
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -7
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +34 -11
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +87 -58
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +14 -30
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +59 -38
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +17 -2
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/enum.rb +11 -2
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +34 -69
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +52 -64
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +98 -37
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -43
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +70 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +82 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- metadata +16 -11
data/README.rdoc
CHANGED
@@ -34,7 +34,7 @@ A short rundown of some of the major features:
|
|
34
34
|
This would also define the following accessors: <tt>Product#name</tt> and
|
35
35
|
<tt>Product#name=(new_name)</tt>.
|
36
36
|
|
37
|
-
{Learn more}[
|
37
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html]
|
38
38
|
|
39
39
|
* Associations between objects defined by simple class methods.
|
40
40
|
|
@@ -44,7 +44,7 @@ A short rundown of some of the major features:
|
|
44
44
|
belongs_to :conglomerate
|
45
45
|
end
|
46
46
|
|
47
|
-
{Learn more}[
|
47
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html]
|
48
48
|
|
49
49
|
|
50
50
|
* Aggregations of value objects.
|
@@ -56,7 +56,7 @@ A short rundown of some of the major features:
|
|
56
56
|
mapping: [%w(address_street street), %w(address_city city)]
|
57
57
|
end
|
58
58
|
|
59
|
-
{Learn more}[
|
59
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html]
|
60
60
|
|
61
61
|
|
62
62
|
* Validation rules that can differ for new or existing objects.
|
@@ -68,7 +68,7 @@ A short rundown of some of the major features:
|
|
68
68
|
validates :password, :email_address, confirmation: true, on: :create
|
69
69
|
end
|
70
70
|
|
71
|
-
{Learn more}[
|
71
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Validations.html]
|
72
72
|
|
73
73
|
|
74
74
|
* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.).
|
@@ -78,7 +78,7 @@ A short rundown of some of the major features:
|
|
78
78
|
# the `invalidate_payment_plan` method gets called just before Person#destroy
|
79
79
|
end
|
80
80
|
|
81
|
-
{Learn more}[
|
81
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html]
|
82
82
|
|
83
83
|
|
84
84
|
* Inheritance hierarchies.
|
@@ -88,7 +88,7 @@ A short rundown of some of the major features:
|
|
88
88
|
class Client < Company; end
|
89
89
|
class PriorityClient < Client; end
|
90
90
|
|
91
|
-
{Learn more}[
|
91
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html]
|
92
92
|
|
93
93
|
|
94
94
|
* Transactions.
|
@@ -99,7 +99,7 @@ A short rundown of some of the major features:
|
|
99
99
|
mary.deposit(100)
|
100
100
|
end
|
101
101
|
|
102
|
-
{Learn more}[
|
102
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html]
|
103
103
|
|
104
104
|
|
105
105
|
* Reflections on columns, associations, and aggregations.
|
@@ -108,7 +108,7 @@ A short rundown of some of the major features:
|
|
108
108
|
reflection.klass # => Client (class)
|
109
109
|
Firm.columns # Returns an array of column descriptors for the firms table
|
110
110
|
|
111
|
-
{Learn more}[
|
111
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html]
|
112
112
|
|
113
113
|
|
114
114
|
* Database abstraction through simple adapters.
|
@@ -125,13 +125,13 @@ A short rundown of some of the major features:
|
|
125
125
|
database: 'activerecord'
|
126
126
|
)
|
127
127
|
|
128
|
-
{Learn more}[
|
129
|
-
MySQL[
|
130
|
-
PostgreSQL[
|
131
|
-
SQLite3[
|
128
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html] and read about the built-in support for
|
129
|
+
MySQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html],
|
130
|
+
PostgreSQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
|
131
|
+
SQLite3[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
|
132
132
|
|
133
133
|
|
134
|
-
* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-
|
134
|
+
* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://docs.ruby-lang.org/en/master/Logger.html].
|
135
135
|
|
136
136
|
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
|
137
137
|
ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
|
@@ -139,7 +139,7 @@ A short rundown of some of the major features:
|
|
139
139
|
|
140
140
|
* Database agnostic schema management with Migrations.
|
141
141
|
|
142
|
-
class AddSystemSettings < ActiveRecord::Migration[7.
|
142
|
+
class AddSystemSettings < ActiveRecord::Migration[7.2]
|
143
143
|
def up
|
144
144
|
create_table :system_settings do |t|
|
145
145
|
t.string :name
|
@@ -157,7 +157,7 @@ A short rundown of some of the major features:
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
{Learn more}[
|
160
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Migration.html]
|
161
161
|
|
162
162
|
|
163
163
|
== Philosophy
|
data/examples/performance.rb
CHANGED
@@ -176,10 +176,10 @@ Benchmark.ips(TIME) do |x|
|
|
176
176
|
end
|
177
177
|
|
178
178
|
x.report "Model.log" do
|
179
|
-
Exhibit.
|
179
|
+
Exhibit.lease_connection.send(:log, "hello", "world") { }
|
180
180
|
end
|
181
181
|
|
182
182
|
x.report "AR.execute(query)" do
|
183
|
-
ActiveRecord::Base.
|
183
|
+
ActiveRecord::Base.lease_connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
|
184
184
|
end
|
185
185
|
end
|
@@ -6,21 +6,23 @@ module ActiveRecord
|
|
6
6
|
module Associations
|
7
7
|
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
|
8
8
|
class AliasTracker # :nodoc:
|
9
|
-
def self.create(
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
def self.create(pool, initial_table, joins, aliases = nil)
|
10
|
+
pool.with_connection do |connection|
|
11
|
+
if joins.empty?
|
12
|
+
aliases ||= Hash.new(0)
|
13
|
+
elsif aliases
|
14
|
+
default_proc = aliases.default_proc || proc { 0 }
|
15
|
+
aliases.default_proc = proc { |h, k|
|
16
|
+
h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
|
17
|
+
}
|
18
|
+
else
|
19
|
+
aliases = Hash.new { |h, k|
|
20
|
+
h[k] = initial_count_for(connection, k, joins)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
aliases[initial_table] = 1
|
24
|
+
new(connection.table_alias_length, aliases)
|
21
25
|
end
|
22
|
-
aliases[initial_table] = 1
|
23
|
-
new(connection, aliases)
|
24
26
|
end
|
25
27
|
|
26
28
|
def self.initial_count_for(connection, name, table_joins)
|
@@ -46,9 +48,9 @@ module ActiveRecord
|
|
46
48
|
end
|
47
49
|
|
48
50
|
# table_joins is an array of arel joins which might conflict with the aliases we assign here
|
49
|
-
def initialize(
|
50
|
-
@aliases
|
51
|
-
@
|
51
|
+
def initialize(table_alias_length, aliases)
|
52
|
+
@aliases = aliases
|
53
|
+
@table_alias_length = table_alias_length
|
52
54
|
end
|
53
55
|
|
54
56
|
def aliased_table_for(arel_table, table_name = nil)
|
@@ -60,7 +62,7 @@ module ActiveRecord
|
|
60
62
|
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
|
61
63
|
else
|
62
64
|
# Otherwise, we need to use an alias
|
63
|
-
aliased_name =
|
65
|
+
aliased_name = table_alias_for(yield)
|
64
66
|
|
65
67
|
# Update the count
|
66
68
|
count = aliases[aliased_name] += 1
|
@@ -76,8 +78,12 @@ module ActiveRecord
|
|
76
78
|
attr_reader :aliases
|
77
79
|
|
78
80
|
private
|
81
|
+
def table_alias_for(table_name)
|
82
|
+
table_name[0...@table_alias_length].tr(".", "_")
|
83
|
+
end
|
84
|
+
|
79
85
|
def truncate(name)
|
80
|
-
name.slice(0, @
|
86
|
+
name.slice(0, @table_alias_length - 2)
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
@@ -53,7 +53,6 @@ module ActiveRecord
|
|
53
53
|
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
54
54
|
def reset
|
55
55
|
@loaded = false
|
56
|
-
@target = nil
|
57
56
|
@stale_state = nil
|
58
57
|
end
|
59
58
|
|
@@ -64,7 +63,7 @@ module ActiveRecord
|
|
64
63
|
# Reloads the \target and returns +self+ on success.
|
65
64
|
# The QueryCache is cleared if +force+ is true.
|
66
65
|
def reload(force = false)
|
67
|
-
klass.
|
66
|
+
klass.connection_pool.clear_query_cache if force && klass
|
68
67
|
reset
|
69
68
|
reset_scope
|
70
69
|
load_target
|
@@ -232,12 +231,14 @@ module ActiveRecord
|
|
232
231
|
end
|
233
232
|
|
234
233
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
234
|
+
klass.with_connection do |c|
|
235
|
+
sc.execute(binds, c) do |record|
|
236
|
+
set_inverse_instance(record)
|
237
|
+
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
238
|
+
record.strict_loading!
|
239
|
+
else
|
240
|
+
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
241
|
+
end
|
241
242
|
end
|
242
243
|
end
|
243
244
|
end
|
@@ -12,11 +12,11 @@ module ActiveRecord
|
|
12
12
|
raise ActiveRecord::Rollback unless target.destroy
|
13
13
|
when :destroy_async
|
14
14
|
if reflection.foreign_key.is_a?(Array)
|
15
|
-
primary_key_column = reflection.active_record_primary_key
|
16
|
-
id = reflection.foreign_key.map { |col| owner.public_send(col
|
15
|
+
primary_key_column = reflection.active_record_primary_key
|
16
|
+
id = reflection.foreign_key.map { |col| owner.public_send(col) }
|
17
17
|
else
|
18
|
-
primary_key_column = reflection.active_record_primary_key
|
19
|
-
id = owner.public_send(reflection.foreign_key
|
18
|
+
primary_key_column = reflection.active_record_primary_key
|
19
|
+
id = owner.public_send(reflection.foreign_key)
|
20
20
|
end
|
21
21
|
|
22
22
|
enqueue_destroy_association(
|
@@ -124,12 +124,20 @@ module ActiveRecord
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def replace_keys(record, force: false)
|
127
|
-
|
128
|
-
reflection_fk
|
127
|
+
reflection_fk = reflection.foreign_key
|
128
|
+
if reflection_fk.is_a?(Array)
|
129
|
+
target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
|
130
|
+
|
131
|
+
if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
|
132
|
+
reflection_fk.each_with_index do |key, index|
|
133
|
+
owner[key] = target_key_values[index]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
else
|
137
|
+
target_key_value = record ? record._read_attribute(primary_key(record.class)) : nil
|
129
138
|
|
130
|
-
|
131
|
-
|
132
|
-
owner[key] = value
|
139
|
+
if force || owner._read_attribute(reflection_fk) != target_key_value
|
140
|
+
owner[reflection_fk] = target_key_value
|
133
141
|
end
|
134
142
|
end
|
135
143
|
end
|
@@ -148,8 +156,7 @@ module ActiveRecord
|
|
148
156
|
end
|
149
157
|
|
150
158
|
def stale_state
|
151
|
-
|
152
|
-
result && result.to_s
|
159
|
+
owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
|
153
160
|
end
|
154
161
|
end
|
155
162
|
end
|
@@ -38,6 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
38
38
|
|
39
39
|
klass = reflection.class_name.safe_constantize
|
40
40
|
klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
|
41
|
+
model.counter_cached_association_names |= [reflection.name]
|
41
42
|
end
|
42
43
|
|
43
44
|
def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
|
@@ -42,8 +42,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
42
42
|
self.right_reflection = _reflect_on_association(rhs_name)
|
43
43
|
end
|
44
44
|
|
45
|
-
def self.
|
46
|
-
left_model.
|
45
|
+
def self.connection_pool
|
46
|
+
left_model.connection_pool
|
47
47
|
end
|
48
48
|
}
|
49
49
|
|
@@ -7,11 +7,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
valid = super + [:counter_cache, :join_table, :index_errors]
|
11
|
-
valid += [:
|
12
|
-
valid += [:
|
10
|
+
valid = super + [:counter_cache, :join_table, :index_errors, :as, :through]
|
11
|
+
valid += [:foreign_type] if options[:as]
|
12
|
+
valid += [:source, :source_type, :disable_joins] if options[:through]
|
13
13
|
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
14
|
-
valid += [:disable_joins] if options[:disable_joins] && options[:through]
|
15
14
|
valid
|
16
15
|
end
|
17
16
|
|
@@ -7,11 +7,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
valid = super
|
11
|
-
valid += [:
|
10
|
+
valid = super + [:as, :through]
|
11
|
+
valid += [:foreign_type] if options[:as]
|
12
12
|
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
13
|
-
valid += [:
|
14
|
-
valid += [:disable_joins] if options[:disable_joins] && options[:through]
|
13
|
+
valid += [:source, :source_type, :disable_joins] if options[:through]
|
15
14
|
valid
|
16
15
|
end
|
17
16
|
|
@@ -28,6 +28,8 @@ module ActiveRecord
|
|
28
28
|
# If you need to work on all current children, new and existing records,
|
29
29
|
# +load_target+ and the +loaded+ flag are your friends.
|
30
30
|
class CollectionAssociation < Association # :nodoc:
|
31
|
+
attr_accessor :nested_attributes_target
|
32
|
+
|
31
33
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
32
34
|
def reader
|
33
35
|
ensure_klass_exists!
|
@@ -228,7 +230,7 @@ module ActiveRecord
|
|
228
230
|
# loaded and you are going to fetch the records anyway it is better to
|
229
231
|
# check <tt>collection.length.zero?</tt>.
|
230
232
|
def empty?
|
231
|
-
if loaded? || @association_ids || reflection.
|
233
|
+
if loaded? || @association_ids || reflection.has_active_cached_counter?
|
232
234
|
size.zero?
|
233
235
|
else
|
234
236
|
target.empty? && !scope.exists?
|
@@ -303,7 +305,7 @@ module ActiveRecord
|
|
303
305
|
|
304
306
|
def find_from_target?
|
305
307
|
loaded? ||
|
306
|
-
owner.strict_loading? ||
|
308
|
+
(owner.strict_loading? && owner.strict_loading_all?) ||
|
307
309
|
reflection.strict_loading? ||
|
308
310
|
owner.new_record? ||
|
309
311
|
target.any? { |record| record.new_record? || record.changed? }
|
@@ -928,7 +928,20 @@ module ActiveRecord
|
|
928
928
|
!!@association.include?(record)
|
929
929
|
end
|
930
930
|
|
931
|
-
|
931
|
+
# Returns the association object for the collection.
|
932
|
+
#
|
933
|
+
# class Person < ActiveRecord::Base
|
934
|
+
# has_many :pets
|
935
|
+
# end
|
936
|
+
#
|
937
|
+
# person.pets.proxy_association
|
938
|
+
# # => #<ActiveRecord::Associations::HasManyAssociation owner="#<Person:0x00>">
|
939
|
+
#
|
940
|
+
# Returns the same object as <tt>person.association(:pets)</tt>,
|
941
|
+
# allowing you to make calls like <tt>person.pets.proxy_association.owner</tt>.
|
942
|
+
#
|
943
|
+
# See Associations::ClassMethods@Association+extensions for more.
|
944
|
+
def proxy_association
|
932
945
|
@association
|
933
946
|
end
|
934
947
|
|
@@ -35,10 +35,10 @@ module ActiveRecord
|
|
35
35
|
unless target.empty?
|
36
36
|
association_class = target.first.class
|
37
37
|
if association_class.query_constraints_list
|
38
|
-
primary_key_column = association_class.query_constraints_list
|
38
|
+
primary_key_column = association_class.query_constraints_list
|
39
39
|
ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
|
40
40
|
else
|
41
|
-
primary_key_column = association_class.primary_key
|
41
|
+
primary_key_column = association_class.primary_key
|
42
42
|
ids = target.collect { |assoc| assoc.public_send(primary_key_column) }
|
43
43
|
end
|
44
44
|
|
@@ -78,7 +78,7 @@ module ActiveRecord
|
|
78
78
|
# If the collection is empty the target is set to an empty array and
|
79
79
|
# the loaded flag is set to true as well.
|
80
80
|
def count_records
|
81
|
-
count = if reflection.
|
81
|
+
count = if reflection.has_active_cached_counter?
|
82
82
|
owner.read_attribute(reflection.counter_cache_column).to_i
|
83
83
|
else
|
84
84
|
scope.count(:all)
|
@@ -34,10 +34,10 @@ module ActiveRecord
|
|
34
34
|
throw(:abort) unless target.destroyed?
|
35
35
|
when :destroy_async
|
36
36
|
if target.class.query_constraints_list
|
37
|
-
primary_key_column = target.class.query_constraints_list
|
37
|
+
primary_key_column = target.class.query_constraints_list
|
38
38
|
id = primary_key_column.map { |col| target.public_send(col) }
|
39
39
|
else
|
40
|
-
primary_key_column = target.class.primary_key
|
40
|
+
primary_key_column = target.class.primary_key
|
41
41
|
id = target.public_send(primary_key_column)
|
42
42
|
end
|
43
43
|
|
@@ -37,39 +37,41 @@ module ActiveRecord
|
|
37
37
|
chain << [reflection, table]
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
base_klass.with_connection do |connection|
|
41
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
42
|
+
# more sense in this context), so we reverse
|
43
|
+
chain.reverse_each do |reflection, table|
|
44
|
+
klass = reflection.klass
|
44
45
|
|
45
|
-
|
46
|
+
scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
unless scope.references_values.empty?
|
49
|
+
associations = scope.eager_load_values | scope.includes_values
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
unless associations.empty?
|
52
|
+
scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
53
|
+
end
|
52
54
|
end
|
53
|
-
end
|
54
55
|
|
55
|
-
|
56
|
-
|
56
|
+
arel = scope.arel(alias_tracker.aliases)
|
57
|
+
nodes = arel.constraints.first
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
if nodes.is_a?(Arel::Nodes::And)
|
60
|
+
others = nodes.children.extract! do |node|
|
61
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
62
|
+
end
|
61
63
|
end
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
if others && !others.empty?
|
68
|
+
joins.concat arel.join_sources
|
69
|
+
append_constraints(connection, joins.last, others)
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
72
|
+
# The current table in this iteration becomes the foreign table in the next
|
73
|
+
foreign_table, foreign_klass = table, klass
|
74
|
+
end
|
73
75
|
end
|
74
76
|
|
75
77
|
joins
|
@@ -88,10 +90,10 @@ module ActiveRecord
|
|
88
90
|
end
|
89
91
|
|
90
92
|
private
|
91
|
-
def append_constraints(join, constraints)
|
93
|
+
def append_constraints(connection, join, constraints)
|
92
94
|
if join.is_a?(Arel::Nodes::StringJoin)
|
93
95
|
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
94
|
-
join.left = Arel.sql(
|
96
|
+
join.left = Arel.sql(connection.visitor.compile(join_string))
|
95
97
|
else
|
96
98
|
right = join.right
|
97
99
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
@@ -254,10 +254,10 @@ module ActiveRecord
|
|
254
254
|
|
255
255
|
if node.primary_key
|
256
256
|
keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
|
257
|
-
|
257
|
+
id = keys.map { |key| row[key] }
|
258
258
|
else
|
259
259
|
keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
|
260
|
-
|
260
|
+
id = keys.map { nil } # Avoid id-based model caching.
|
261
261
|
end
|
262
262
|
|
263
263
|
if keys.any? { |key| row[key].nil? }
|
@@ -266,11 +266,9 @@ module ActiveRecord
|
|
266
266
|
next
|
267
267
|
end
|
268
268
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
seen[ar_parent][node][id] = model if id
|
273
|
-
end
|
269
|
+
unless model = seen[ar_parent][node][id]
|
270
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
271
|
+
seen[ar_parent][node][id] = model if id
|
274
272
|
end
|
275
273
|
|
276
274
|
construct(model, node, row, seen, model_cache, strict_loading_value)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Validation error class to wrap association records' errors,
|
4
|
+
# with index_errors support.
|
5
|
+
module ActiveRecord
|
6
|
+
module Associations
|
7
|
+
class NestedError < ::ActiveModel::NestedError
|
8
|
+
def initialize(association, inner_error)
|
9
|
+
@base = association.owner
|
10
|
+
@association = association
|
11
|
+
@inner_error = inner_error
|
12
|
+
super(@base, inner_error, { attribute: compute_attribute(inner_error) })
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :association
|
17
|
+
|
18
|
+
def compute_attribute(inner_error)
|
19
|
+
association_name = association.reflection.name
|
20
|
+
|
21
|
+
if index_errors_setting && index
|
22
|
+
"#{association_name}[#{index}].#{inner_error.attribute}".to_sym
|
23
|
+
else
|
24
|
+
"#{association_name}.#{inner_error.attribute}".to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def index_errors_setting
|
29
|
+
@index_errors_setting ||=
|
30
|
+
association.options.fetch(:index_errors, ActiveRecord.index_nested_attribute_errors)
|
31
|
+
end
|
32
|
+
|
33
|
+
def index
|
34
|
+
@index ||= ordered_records&.find_index(inner_error.base)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ordered_records
|
38
|
+
case index_errors_setting
|
39
|
+
when true # default is association order
|
40
|
+
association.target
|
41
|
+
when :nested_attributes_order
|
42
|
+
association.nested_attributes_target
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -248,7 +248,8 @@ module ActiveRecord
|
|
248
248
|
association = owner.association(reflection.name)
|
249
249
|
|
250
250
|
if reflection.collection?
|
251
|
-
association.target
|
251
|
+
not_persisted_records = association.target.reject(&:persisted?)
|
252
|
+
association.target = records + not_persisted_records
|
252
253
|
else
|
253
254
|
association.target = records.first
|
254
255
|
end
|
@@ -9,7 +9,13 @@ module ActiveRecord
|
|
9
9
|
attr_writer :preloaded_records
|
10
10
|
|
11
11
|
def initialize(association:, children:, parent:, associate_by_default:, scope:)
|
12
|
-
@association = association
|
12
|
+
@association = if association
|
13
|
+
begin
|
14
|
+
@association = association.to_sym
|
15
|
+
rescue NoMethodError
|
16
|
+
raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}"
|
17
|
+
end
|
18
|
+
end
|
13
19
|
@parent = parent
|
14
20
|
@scope = scope
|
15
21
|
@associate_by_default = associate_by_default
|
@@ -9,9 +9,7 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def records_by_owner
|
12
|
-
|
13
|
-
|
14
|
-
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
12
|
+
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
|
15
13
|
if loaded?(owner)
|
16
14
|
result[owner] = target_for(owner)
|
17
15
|
next
|
@@ -14,6 +14,12 @@ module ActiveRecord
|
|
14
14
|
target
|
15
15
|
end
|
16
16
|
|
17
|
+
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
18
|
+
def reset
|
19
|
+
super
|
20
|
+
@target = nil
|
21
|
+
end
|
22
|
+
|
17
23
|
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
18
24
|
def writer(record)
|
19
25
|
replace(record)
|