activerecord 7.1.3.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2133
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. 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}[link:classes/ActiveRecord/Base.html]
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}[link:classes/ActiveRecord/Associations/ClassMethods.html]
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}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
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}[link:classes/ActiveRecord/Validations.html]
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}[link:classes/ActiveRecord/Callbacks.html]
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}[link:classes/ActiveRecord/Base.html]
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}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
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}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
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}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
129
- MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html],
130
- PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
131
- SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
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-doc.org/stdlib/libdoc/logger/rdoc/].
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.1]
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}[link:classes/ActiveRecord/Migration.html]
160
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Migration.html]
161
161
 
162
162
 
163
163
  == Philosophy
@@ -176,10 +176,10 @@ Benchmark.ips(TIME) do |x|
176
176
  end
177
177
 
178
178
  x.report "Model.log" do
179
- Exhibit.connection.send(:log, "hello", "world") { }
179
+ Exhibit.lease_connection.send(:log, "hello", "world") { }
180
180
  end
181
181
 
182
182
  x.report "AR.execute(query)" do
183
- ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
183
+ ActiveRecord::Base.lease_connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
184
184
  end
185
185
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
23
23
  end
24
24
 
25
- scoping { klass.#{method}(attributes, **kwargs) }
25
+ super
26
26
  end
27
27
  RUBY
28
28
  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(connection, initial_table, joins, aliases = nil)
10
- if joins.empty?
11
- aliases ||= Hash.new(0)
12
- elsif aliases
13
- default_proc = aliases.default_proc || proc { 0 }
14
- aliases.default_proc = proc { |h, k|
15
- h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
16
- }
17
- else
18
- aliases = Hash.new { |h, k|
19
- h[k] = initial_count_for(connection, k, joins)
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(connection, aliases)
50
- @aliases = aliases
51
- @connection = connection
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 = @connection.table_alias_for(yield)
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, @connection.table_alias_length - 2)
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.connection.clear_query_cache if force && 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
- sc.execute(binds, klass.connection) 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)
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.map(&:to_sym)
16
- id = reflection.foreign_key.map { |col| owner.public_send(col.to_sym) }
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.to_sym
19
- id = owner.public_send(reflection.foreign_key.to_sym)
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
- target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
128
- reflection_fk = Array(reflection.foreign_key)
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
- if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
131
- reflection_fk.zip(target_key_values).each do |key, value|
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
- result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
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
@@ -41,8 +41,9 @@ module ActiveRecord
41
41
  end
42
42
 
43
43
  def stale_state
44
- foreign_key = super
45
- foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
44
+ if foreign_key = super
45
+ [foreign_key, owner[reflection.foreign_type]]
46
+ end
46
47
  end
47
48
  end
48
49
  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.retrieve_connection
46
- left_model.retrieve_connection
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 += [:as, :foreign_type] if options[:as]
12
- valid += [:through, :source, :source_type] if options[:through]
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 += [:as, :foreign_type] if options[:as]
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 += [:through, :source, :source_type] if options[:through]
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.has_cached_counter?
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
- def proxy_association # :nodoc:
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.map(&:to_sym)
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.to_sym
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.has_cached_counter?
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.map(&:to_sym)
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.to_sym
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
- # The chain starts with the target table, but we want to end with it here (makes
41
- # more sense in this context), so we reverse
42
- chain.reverse_each do |reflection, table|
43
- klass = reflection.klass
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
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
47
 
47
- unless scope.references_values.empty?
48
- associations = scope.eager_load_values | scope.includes_values
48
+ unless scope.references_values.empty?
49
+ associations = scope.eager_load_values | scope.includes_values
49
50
 
50
- unless associations.empty?
51
- scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
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
- arel = scope.arel(alias_tracker.aliases)
56
- nodes = arel.constraints.first
56
+ arel = scope.arel(alias_tracker.aliases)
57
+ nodes = arel.constraints.first
57
58
 
58
- if nodes.is_a?(Arel::Nodes::And)
59
- others = nodes.children.extract! do |node|
60
- !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
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
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
66
 
66
- if others && !others.empty?
67
- joins.concat arel.join_sources
68
- append_constraints(joins.last, others)
69
- end
67
+ if others && !others.empty?
68
+ joins.concat arel.join_sources
69
+ append_constraints(connection, joins.last, others)
70
+ end
70
71
 
71
- # The current table in this iteration becomes the foreign table in the next
72
- foreign_table, foreign_klass = table, klass
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(base_klass.connection.visitor.compile(join_string))
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
- ids = keys.map { |key| row[key] }
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
- ids = keys.map { nil } # Avoid id-based model caching.
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
- ids.each do |id|
270
- unless model = seen[ar_parent][node][id]
271
- model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
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 = records
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
- return @records_by_owner if defined?(@records_by_owner)
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)