activerecord 6.1.4.6 → 7.0.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (240) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1188 -932
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/join_dependency.rb +6 -2
  25. data/lib/active_record/associations/preloader/association.rb +187 -55
  26. data/lib/active_record/associations/preloader/batch.rb +48 -0
  27. data/lib/active_record/associations/preloader/branch.rb +147 -0
  28. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  29. data/lib/active_record/associations/preloader.rb +39 -113
  30. data/lib/active_record/associations/singular_association.rb +8 -2
  31. data/lib/active_record/associations/through_association.rb +3 -3
  32. data/lib/active_record/associations.rb +119 -90
  33. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  34. data/lib/active_record/attribute_assignment.rb +1 -1
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  37. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  38. data/lib/active_record/attribute_methods/query.rb +2 -2
  39. data/lib/active_record/attribute_methods/read.rb +7 -5
  40. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  42. data/lib/active_record/attribute_methods/write.rb +7 -10
  43. data/lib/active_record/attribute_methods.rb +13 -14
  44. data/lib/active_record/attributes.rb +24 -35
  45. data/lib/active_record/autosave_association.rb +8 -23
  46. data/lib/active_record/base.rb +19 -1
  47. data/lib/active_record/callbacks.rb +2 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +38 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +35 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +47 -53
  94. data/lib/active_record/core.rb +122 -132
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +16 -32
  100. data/lib/active_record/delegated_type.rb +52 -11
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +61 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +49 -42
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +17 -20
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +3 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +9 -3
  147. data/lib/active_record/log_subscriber.rb +14 -3
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +8 -3
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +4 -4
  152. data/lib/active_record/migration/compatibility.rb +107 -3
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +109 -79
  155. data/lib/active_record/model_schema.rb +45 -58
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +219 -52
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +15 -5
  163. data/lib/active_record/railtie.rb +127 -17
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +66 -129
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +67 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +3 -3
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +249 -61
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +184 -84
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +11 -7
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +61 -12
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +1 -1
  191. data/lib/active_record/signed_id.rb +1 -1
  192. data/lib/active_record/suppressor.rb +11 -15
  193. data/lib/active_record/tasks/database_tasks.rb +120 -58
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  196. data/lib/active_record/test_databases.rb +1 -1
  197. data/lib/active_record/test_fixtures.rb +4 -4
  198. data/lib/active_record/timestamp.rb +3 -4
  199. data/lib/active_record/transactions.rb +9 -14
  200. data/lib/active_record/translation.rb +2 -2
  201. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  202. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  203. data/lib/active_record/type/internal/timezone.rb +2 -2
  204. data/lib/active_record/type/serialized.rb +1 -1
  205. data/lib/active_record/type/type_map.rb +17 -20
  206. data/lib/active_record/type.rb +1 -2
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/uniqueness.rb +1 -1
  209. data/lib/active_record.rb +204 -28
  210. data/lib/arel/attributes/attribute.rb +0 -8
  211. data/lib/arel/crud.rb +28 -22
  212. data/lib/arel/delete_manager.rb +18 -4
  213. data/lib/arel/filter_predications.rb +9 -0
  214. data/lib/arel/insert_manager.rb +2 -3
  215. data/lib/arel/nodes/casted.rb +1 -1
  216. data/lib/arel/nodes/delete_statement.rb +12 -13
  217. data/lib/arel/nodes/filter.rb +10 -0
  218. data/lib/arel/nodes/function.rb +1 -0
  219. data/lib/arel/nodes/insert_statement.rb +2 -2
  220. data/lib/arel/nodes/select_core.rb +2 -2
  221. data/lib/arel/nodes/select_statement.rb +2 -2
  222. data/lib/arel/nodes/update_statement.rb +8 -3
  223. data/lib/arel/nodes.rb +1 -0
  224. data/lib/arel/predications.rb +11 -3
  225. data/lib/arel/select_manager.rb +10 -4
  226. data/lib/arel/table.rb +0 -1
  227. data/lib/arel/tree_manager.rb +0 -12
  228. data/lib/arel/update_manager.rb +18 -4
  229. data/lib/arel/visitors/dot.rb +80 -90
  230. data/lib/arel/visitors/mysql.rb +8 -2
  231. data/lib/arel/visitors/postgresql.rb +0 -10
  232. data/lib/arel/visitors/to_sql.rb +58 -2
  233. data/lib/arel.rb +2 -1
  234. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  235. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  236. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  237. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  238. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  239. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  240. metadata +56 -11
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2020 David Heinemeier Hansson
1
+ Copyright (c) 2004-2022 David Heinemeier Hansson
2
2
 
3
3
  Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson
4
4
 
data/README.rdoc CHANGED
@@ -140,7 +140,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
140
140
 
141
141
  * Database agnostic schema management with Migrations.
142
142
 
143
- class AddSystemSettings < ActiveRecord::Migration[6.0]
143
+ class AddSystemSettings < ActiveRecord::Migration[7.0]
144
144
  def up
145
145
  create_table :system_settings do |t|
146
146
  t.string :name
@@ -264,7 +264,7 @@ module ActiveRecord
264
264
  end
265
265
 
266
266
  hash_from_multiparameter_assignment = part.is_a?(Hash) &&
267
- part.each_key.all? { |k| k.is_a?(Integer) }
267
+ part.keys.all?(Integer)
268
268
  if hash_from_multiparameter_assignment
269
269
  raise ArgumentError unless part.size == part.each_key.max
270
270
  part = klass.new(*part.sort.map(&:last))
@@ -27,16 +27,6 @@ module ActiveRecord
27
27
  RUBY
28
28
  end
29
29
 
30
- def build(attributes = nil, &block)
31
- if attributes.is_a?(Array)
32
- attributes.collect { |attr| build(attr, &block) }
33
- else
34
- block = current_scope_restoring_block(&block)
35
- scoping { _new(attributes, &block) }
36
- end
37
- end
38
- alias new build
39
-
40
30
  private
41
31
  def _new(attributes, &block)
42
32
  @association.build(attributes, &block)
@@ -32,8 +32,8 @@ module ActiveRecord
32
32
  # The association of <tt>blog.posts</tt> has the object +blog+ as its
33
33
  # <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
34
34
  # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
35
- class Association #:nodoc:
36
- attr_reader :owner, :target, :reflection
35
+ class Association # :nodoc:
36
+ attr_reader :owner, :target, :reflection, :disable_joins
37
37
 
38
38
  delegate :options, to: :reflection
39
39
 
@@ -41,6 +41,7 @@ module ActiveRecord
41
41
  reflection.check_validity!
42
42
 
43
43
  @owner, @reflection = owner, reflection
44
+ @disable_joins = @reflection.options[:disable_joins] || false
44
45
 
45
46
  reset
46
47
  reset_scope
@@ -51,7 +52,6 @@ module ActiveRecord
51
52
  @loaded = false
52
53
  @target = nil
53
54
  @stale_state = nil
54
- @inversed = false
55
55
  end
56
56
 
57
57
  def reset_negative_cache # :nodoc:
@@ -77,7 +77,6 @@ module ActiveRecord
77
77
  def loaded!
78
78
  @loaded = true
79
79
  @stale_state = stale_state
80
- @inversed = false
81
80
  end
82
81
 
83
82
  # The target is stale if the target no longer points to the record(s) that the
@@ -87,7 +86,7 @@ module ActiveRecord
87
86
  #
88
87
  # Note that if the target has not been loaded, it is not considered stale.
89
88
  def stale_target?
90
- !@inversed && loaded? && @stale_state != stale_state
89
+ loaded? && @stale_state != stale_state
91
90
  end
92
91
 
93
92
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -97,8 +96,12 @@ module ActiveRecord
97
96
  end
98
97
 
99
98
  def scope
100
- if (scope = klass.current_scope) && scope.try(:proxy_association) == self
99
+ if disable_joins
100
+ DisableJoinsAssociationScope.create.scope(self)
101
+ elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self
101
102
  scope.spawn
103
+ elsif scope = klass.global_current_scope
104
+ target_scope.merge!(association_scope).merge!(scope)
102
105
  else
103
106
  target_scope.merge!(association_scope)
104
107
  end
@@ -132,15 +135,11 @@ module ActiveRecord
132
135
 
133
136
  def inversed_from(record)
134
137
  self.target = record
135
- @inversed = !!record
136
138
  end
137
139
 
138
140
  def inversed_from_queries(record)
139
141
  if inversable?(record)
140
142
  self.target = record
141
- @inversed = true
142
- else
143
- @inversed = false
144
143
  end
145
144
  end
146
145
 
@@ -191,7 +190,7 @@ module ActiveRecord
191
190
  @reflection = @owner.class._reflect_on_association(reflection_name)
192
191
  end
193
192
 
194
- def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
193
+ def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
195
194
  except_from_scope_attributes ||= {}
196
195
  skip_assign = [reflection.foreign_key, reflection.type].compact
197
196
  assigned_keys = record.changed_attribute_names_to_save
@@ -210,8 +209,14 @@ module ActiveRecord
210
209
  end
211
210
 
212
211
  private
212
+ # Reader and writer methods call this so that consistent errors are presented
213
+ # when the association target class does not exist.
214
+ def ensure_klass_exists!
215
+ klass
216
+ end
217
+
213
218
  def find_target
214
- if strict_loading? && owner.validation_context.nil?
219
+ if violates_strict_loading? && owner.validation_context.nil?
215
220
  Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
216
221
  end
217
222
 
@@ -224,13 +229,20 @@ module ActiveRecord
224
229
  end
225
230
 
226
231
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
227
- sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
232
+ sc.execute(binds, klass.connection) do |record|
233
+ set_inverse_instance(record)
234
+ if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
235
+ record.strict_loading!
236
+ else
237
+ record.strict_loading!(false, mode: owner.strict_loading_mode)
238
+ end
239
+ end
228
240
  end
229
241
 
230
- def strict_loading?
242
+ def violates_strict_loading?
231
243
  return reflection.strict_loading? if reflection.options.key?(:strict_loading)
232
244
 
233
- owner.strict_loading?
245
+ owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
234
246
  end
235
247
 
236
248
  # The scope for this association.
@@ -241,7 +253,11 @@ module ActiveRecord
241
253
  # actually gets built.
242
254
  def association_scope
243
255
  if klass
244
- @association_scope ||= AssociationScope.scope(self)
256
+ @association_scope ||= if disable_joins
257
+ DisableJoinsAssociationScope.scope(self)
258
+ else
259
+ AssociationScope.scope(self)
260
+ end
245
261
  end
246
262
  end
247
263
 
@@ -273,7 +289,7 @@ module ActiveRecord
273
289
 
274
290
  # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
275
291
  # the kind of the class of the associated objects. Meant to be used as
276
- # a sanity check when you are about to assign an associated record.
292
+ # a safety check when you are about to assign an associated record.
277
293
  def raise_on_type_mismatch!(record)
278
294
  unless record.is_a?(reflection.klass)
279
295
  fresh_class = reflection.class_name.safe_constantize
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- class AssociationScope #:nodoc:
5
+ class AssociationScope # :nodoc:
6
6
  def self.scope(association)
7
7
  INSTANCE.scope(association)
8
8
  end
@@ -123,8 +123,6 @@ module ActiveRecord
123
123
 
124
124
  chain_head = chain.first
125
125
  chain.reverse_each do |reflection|
126
- # Exclude the scope of the association itself, because that
127
- # was already merged in the #scope method.
128
126
  reflection.constraints.each do |scope_chain_item|
129
127
  item = eval_scope(reflection, scope_chain_item, owner)
130
128
 
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Association
6
- class BelongsToAssociation < SingularAssociation #:nodoc:
6
+ class BelongsToAssociation < SingularAssociation # :nodoc:
7
7
  def handle_dependency
8
8
  return unless load_target
9
9
 
@@ -55,7 +55,8 @@ module ActiveRecord
55
55
 
56
56
  def decrement_counters_before_last_save
57
57
  if reflection.polymorphic?
58
- model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
58
+ model_type_was = owner.attribute_before_last_save(reflection.foreign_type)
59
+ model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was
59
60
  else
60
61
  model_was = klass
61
62
  end
@@ -68,6 +69,14 @@ module ActiveRecord
68
69
  end
69
70
 
70
71
  def target_changed?
72
+ owner.attribute_changed?(reflection.foreign_key) || (!foreign_key_present? && target&.new_record?)
73
+ end
74
+
75
+ def target_previously_changed?
76
+ owner.attribute_previously_changed?(reflection.foreign_key)
77
+ end
78
+
79
+ def saved_change_to_target?
71
80
  owner.saved_change_to_attribute?(reflection.foreign_key)
72
81
  end
73
82
 
@@ -77,6 +86,8 @@ module ActiveRecord
77
86
  raise_on_type_mismatch!(record)
78
87
  set_inverse_instance(record)
79
88
  @updated = true
89
+ elsif target
90
+ remove_inverse_instance(target)
80
91
  end
81
92
 
82
93
  replace_keys(record, force: true)
@@ -110,7 +121,7 @@ module ActiveRecord
110
121
  def replace_keys(record, force: false)
111
122
  target_key = record ? record._read_attribute(primary_key(record.class)) : nil
112
123
 
113
- if force || owner[reflection.foreign_key] != target_key
124
+ if force || owner._read_attribute(reflection.foreign_key) != target_key
114
125
  owner[reflection.foreign_key] = target_key
115
126
  end
116
127
  end
@@ -125,7 +136,7 @@ module ActiveRecord
125
136
 
126
137
  def invertible_for?(record)
127
138
  inverse = inverse_reflection_for(record)
128
- inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
139
+ inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
129
140
  end
130
141
 
131
142
  def stale_state
@@ -3,13 +3,21 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Polymorphic Association
6
- class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
6
+ class BelongsToPolymorphicAssociation < BelongsToAssociation # :nodoc:
7
7
  def klass
8
8
  type = owner[reflection.foreign_type]
9
9
  type.presence && owner.class.polymorphic_class_for(type)
10
10
  end
11
11
 
12
12
  def target_changed?
13
+ super || owner.attribute_changed?(reflection.foreign_type)
14
+ end
15
+
16
+ def target_previously_changed?
17
+ super || owner.attribute_previously_changed?(reflection.foreign_type)
18
+ end
19
+
20
+ def saved_change_to_target?
13
21
  super || owner.saved_change_to_attribute?(reflection.foreign_type)
14
22
  end
15
23
 
@@ -19,7 +27,7 @@ module ActiveRecord
19
27
 
20
28
  target_type = record ? record.class.polymorphic_name : nil
21
29
 
22
- if force || owner[reflection.foreign_type] != target_type
30
+ if force || owner._read_attribute(reflection.foreign_type) != target_type
23
31
  owner[reflection.foreign_type] = target_type
24
32
  end
25
33
  end
@@ -12,7 +12,7 @@
12
12
  # - HasManyAssociation
13
13
 
14
14
  module ActiveRecord::Associations::Builder # :nodoc:
15
- class Association #:nodoc:
15
+ class Association # :nodoc:
16
16
  class << self
17
17
  attr_accessor :extensions
18
18
  end
@@ -33,6 +33,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
33
33
  define_accessors model, reflection
34
34
  define_callbacks model, reflection
35
35
  define_validations model, reflection
36
+ define_change_tracking_methods model, reflection
36
37
  reflection
37
38
  end
38
39
 
@@ -117,6 +118,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
117
118
  # noop
118
119
  end
119
120
 
121
+ def self.define_change_tracking_methods(model, reflection)
122
+ # noop
123
+ end
124
+
120
125
  def self.valid_dependent_options
121
126
  raise NotImplementedError
122
127
  end
@@ -158,6 +163,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
158
163
 
159
164
  private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
160
165
  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
161
- :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback
166
+ :define_change_tracking_methods, :valid_dependent_options, :check_dependent_options,
167
+ :add_destroy_callbacks, :add_after_commit_jobs_callback
162
168
  end
163
169
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class BelongsTo < SingularAssociation #:nodoc:
4
+ class BelongsTo < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :belongs_to
7
7
  end
@@ -30,7 +30,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  model.after_update lambda { |record|
31
31
  association = association(reflection.name)
32
32
 
33
- if association.target_changed?
33
+ if association.saved_change_to_target?
34
34
  association.increment_counters
35
35
  association.decrement_counters_before_last_save
36
36
  end
@@ -49,7 +49,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
49
49
  if reflection.polymorphic?
50
50
  foreign_type = reflection.foreign_type
51
51
  klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
52
- klass = klass.constantize
52
+ klass = o.class.polymorphic_class_for(klass)
53
53
  else
54
54
  klass = association.klass
55
55
  end
@@ -87,7 +87,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
87
87
  if reflection.counter_cache_column
88
88
  touch_callback = callback.(:saved_changes)
89
89
  update_callback = lambda { |record|
90
- instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
90
+ instance_exec(record, &touch_callback) unless association(reflection.name).saved_change_to_target?
91
91
  }
92
92
  model.after_update update_callback, if: :saved_changes?
93
93
  else
@@ -127,7 +127,20 @@ module ActiveRecord::Associations::Builder # :nodoc:
127
127
  end
128
128
  end
129
129
 
130
- private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
131
- :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
130
+ def self.define_change_tracking_methods(model, reflection)
131
+ model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
132
+ def #{reflection.name}_changed?
133
+ association(:#{reflection.name}).target_changed?
134
+ end
135
+
136
+ def #{reflection.name}_previously_changed?
137
+ association(:#{reflection.name}).target_previously_changed?
138
+ end
139
+ CODE
140
+ end
141
+
142
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks,
143
+ :define_validations, :define_change_tracking_methods, :add_counter_cache_callbacks,
144
+ :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
132
145
  end
133
146
  end
@@ -3,7 +3,7 @@
3
3
  require "active_record/associations"
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class CollectionAssociation < Association #:nodoc:
6
+ class CollectionAssociation < Association # :nodoc:
7
7
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
8
8
 
9
9
  def self.valid_options(options)
@@ -30,11 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  def self.define_callback(model, callback_name, name, options)
31
31
  full_callback_name = "#{callback_name}_for_#{name}"
32
32
 
33
- unless model.method_defined?(full_callback_name)
33
+ callback_values = Array(options[callback_name.to_sym])
34
+ method_defined = model.respond_to?(full_callback_name)
35
+
36
+ # If there are no callbacks, we must also check if a superclass had
37
+ # previously defined this association
38
+ return if callback_values.empty? && !method_defined
39
+
40
+ unless method_defined
34
41
  model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
35
42
  end
36
43
 
37
- callbacks = Array(options[callback_name.to_sym]).map do |callback|
44
+ callbacks = callback_values.map do |callback|
38
45
  case callback
39
46
  when Symbol
40
47
  ->(method, owner, record) { owner.send(callback, record) }
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasMany < CollectionAssociation #:nodoc:
4
+ class HasMany < CollectionAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_many
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
10
+ valid = super + [:counter_cache, :join_table, :index_errors]
11
11
  valid += [:as, :foreign_type] if options[:as]
12
12
  valid += [:through, :source, :source_type] if options[:through]
13
13
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
14
15
  valid
15
16
  end
16
17
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasOne < SingularAssociation #:nodoc:
4
+ class HasOne < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_one
7
7
  end
@@ -11,6 +11,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
11
11
  valid += [:as, :foreign_type] if options[:as]
12
12
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
13
  valid += [:through, :source, :source_type] if options[:through]
14
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
14
15
  valid
15
16
  end
16
17
 
@@ -3,7 +3,7 @@
3
3
  # This class is inherited by the has_one and belongs_to association classes
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class SingularAssociation < Association #:nodoc:
6
+ class SingularAssociation < Association # :nodoc:
7
7
  def self.valid_options(options)
8
8
  super + [:required, :touch]
9
9
  end
@@ -13,7 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
13
13
  mixin = model.generated_association_methods
14
14
  name = reflection.name
15
15
 
16
- define_constructors(mixin, name) if reflection.constructable?
16
+ define_constructors(mixin, name) unless reflection.polymorphic?
17
17
 
18
18
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
19
19
  def reload_#{name}
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  # = Active Record Association Collection
@@ -25,9 +27,11 @@ module ActiveRecord
25
27
  #
26
28
  # If you need to work on all current children, new and existing records,
27
29
  # +load_target+ and the +loaded+ flag are your friends.
28
- class CollectionAssociation < Association #:nodoc:
30
+ class CollectionAssociation < Association # :nodoc:
29
31
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
30
32
  def reader
33
+ ensure_klass_exists!
34
+
31
35
  if stale_target?
32
36
  reload
33
37
  end
@@ -75,6 +79,7 @@ module ActiveRecord
75
79
  def reset
76
80
  super
77
81
  @target = []
82
+ @replaced_or_added_targets = Set.new
78
83
  @association_ids = nil
79
84
  end
80
85
 
@@ -121,21 +126,6 @@ module ActiveRecord
121
126
  end
122
127
  end
123
128
 
124
- # Starts a transaction in the association class's database connection.
125
- #
126
- # class Author < ActiveRecord::Base
127
- # has_many :books
128
- # end
129
- #
130
- # Author.first.books.transaction do
131
- # # same effect as calling Book.transaction
132
- # end
133
- def transaction(*args)
134
- reflection.klass.transaction(*args) do
135
- yield
136
- end
137
- end
138
-
139
129
  # Removes all records from the association without calling callbacks
140
130
  # on the associated records. It honors the +:dependent+ option. However
141
131
  # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
@@ -279,20 +269,19 @@ module ActiveRecord
279
269
  end
280
270
 
281
271
  def add_to_target(record, skip_callbacks: false, replace: false, &block)
282
- if replace || association_scope.distinct_value
283
- index = @target.index(record)
284
- end
285
- replace_on_target(record, index, skip_callbacks, &block)
272
+ replace_on_target(record, skip_callbacks, replace: replace || association_scope.distinct_value, &block)
286
273
  end
287
274
 
288
275
  def target=(record)
289
- return super unless ActiveRecord::Base.has_many_inversing
276
+ return super unless reflection.klass.has_many_inversing
290
277
 
291
278
  case record
279
+ when nil
280
+ # It's not possible to remove the record from the inverse association.
292
281
  when Array
293
282
  super
294
283
  else
295
- add_to_target(record, skip_callbacks: true, replace: true)
284
+ replace_on_target(record, true, replace: true, inversing: true)
296
285
  end
297
286
  end
298
287
 
@@ -315,6 +304,10 @@ module ActiveRecord
315
304
  end
316
305
 
317
306
  private
307
+ def transaction(&block)
308
+ reflection.klass.transaction(&block)
309
+ end
310
+
318
311
  # We have some records loaded from the database (persisted) and some that are
319
312
  # in-memory (memory). The same record may be represented in the persisted array
320
313
  # and in the memory array.
@@ -342,12 +335,12 @@ module ActiveRecord
342
335
  end
343
336
  end
344
337
 
345
- persisted + memory
338
+ persisted + memory.reject(&:persisted?)
346
339
  end
347
340
 
348
341
  def _create_record(attributes, raise = false, &block)
349
342
  unless owner.persisted?
350
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
343
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
351
344
  end
352
345
 
353
346
  if attributes.is_a?(Array)
@@ -425,7 +418,7 @@ module ActiveRecord
425
418
  common_records = intersection(new_target, original_target)
426
419
  common_records.each do |record|
427
420
  skip_callbacks = true
428
- replace_on_target(record, @target.index(record), skip_callbacks)
421
+ replace_on_target(record, skip_callbacks, replace: true)
429
422
  end
430
423
  end
431
424
 
@@ -448,7 +441,11 @@ module ActiveRecord
448
441
  records
449
442
  end
450
443
 
451
- def replace_on_target(record, index, skip_callbacks)
444
+ def replace_on_target(record, skip_callbacks, replace:, inversing: false)
445
+ if replace && (!record.new_record? || @replaced_or_added_targets.include?(record))
446
+ index = @target.index(record)
447
+ end
448
+
452
449
  catch(:abort) do
453
450
  callback(:before_add, record)
454
451
  end || return unless skip_callbacks
@@ -459,6 +456,12 @@ module ActiveRecord
459
456
 
460
457
  yield(record) if block_given?
461
458
 
459
+ if !index && @replaced_or_added_targets.include?(record)
460
+ index = @target.index(record)
461
+ end
462
+
463
+ @replaced_or_added_targets << record if inversing || index || record.new_record?
464
+
462
465
  if index
463
466
  target[index] = record
464
467
  elsif @_was_loaded || !loaded?
@@ -481,7 +484,11 @@ module ActiveRecord
481
484
 
482
485
  def callbacks_for(callback_name)
483
486
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
484
- owner.class.send(full_callback_name)
487
+ if owner.class.respond_to?(full_callback_name)
488
+ owner.class.send(full_callback_name)
489
+ else
490
+ []
491
+ end
485
492
  end
486
493
 
487
494
  def include_in_memory?(record)
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  # is computed directly through SQL and does not trigger by itself the
28
28
  # instantiation of the actual post records.
29
29
  class CollectionProxy < Relation
30
- def initialize(klass, association, **) #:nodoc:
30
+ def initialize(klass, association, **) # :nodoc:
31
31
  @association = association
32
32
  super klass
33
33
 
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  # Returns +true+ if the association has been loaded, otherwise +false+.
47
47
  #
48
48
  # person.pets.loaded? # => false
49
- # person.pets
49
+ # person.pets.records
50
50
  # person.pets.loaded? # => true
51
51
  def loaded?
52
52
  @association.loaded?
@@ -813,7 +813,7 @@ module ActiveRecord
813
813
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
814
814
  # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
815
815
  # not already been loaded and you are going to fetch the records anyway it
816
- # is better to check <tt>collection.length.zero?</tt>.
816
+ # is better to check <tt>collection.load.empty?</tt>.
817
817
  #
818
818
  # class Person < ActiveRecord::Base
819
819
  # has_many :pets
@@ -849,6 +849,11 @@ module ActiveRecord
849
849
  # person.pets.count # => 1
850
850
  # person.pets.any? # => true
851
851
  #
852
+ # Calling it without a block when the collection is not yet
853
+ # loaded is equivalent to <tt>collection.exists?</tt>.
854
+ # If you're going to load the collection anyway, it is better
855
+ # to call <tt>collection.load.any?</tt> to avoid an extra query.
856
+ #
852
857
  # You can also pass a +block+ to define criteria. The behavior
853
858
  # is the same, it returns true if the collection based on the
854
859
  # criteria is not empty.