activerecord 7.1.4 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -2387
- 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 +14 -7
- 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 +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +1 -1
- 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 +33 -16
- 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 +4 -16
- 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/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- 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 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- 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 +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- 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 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- 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/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 +15 -11
- 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 +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +52 -36
- data/lib/active_record/counter_cache.rb +18 -9
- 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 +38 -34
- 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 +24 -0
- 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 +22 -2
- 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/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 +8 -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 +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -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 +28 -67
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +48 -57
- 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 +90 -35
- 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.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- 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 +496 -72
- data/lib/active_record/result.rb +31 -44
- 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 +76 -59
- 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 +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- 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 +3 -2
- 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 +29 -16
- data/lib/arel.rb +7 -3
- metadata +17 -12
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
|
@@ -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!
|
@@ -48,11 +50,11 @@ module ActiveRecord
|
|
48
50
|
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
49
51
|
def ids_reader
|
50
52
|
if loaded?
|
51
|
-
target.pluck(
|
53
|
+
target.pluck(reflection.association_primary_key)
|
52
54
|
elsif !target.empty?
|
53
|
-
load_target.pluck(
|
55
|
+
load_target.pluck(reflection.association_primary_key)
|
54
56
|
else
|
55
|
-
@association_ids ||= scope.pluck(
|
57
|
+
@association_ids ||= scope.pluck(reflection.association_primary_key)
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
@@ -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?
|
@@ -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
|
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -82,7 +82,7 @@ module ActiveRecord
|
|
82
82
|
def stale_state
|
83
83
|
if through_reflection.belongs_to?
|
84
84
|
Array(through_reflection.foreign_key).filter_map do |foreign_key_column|
|
85
|
-
owner[foreign_key_column]
|
85
|
+
owner[foreign_key_column]
|
86
86
|
end.presence
|
87
87
|
end
|
88
88
|
end
|