activerecord 6.1.3.2 → 7.0.0.alpha2

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -1058
  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 +35 -7
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +16 -6
  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 +1 -1
  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 +24 -25
  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/preloader/association.rb +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +11 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -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 +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  67. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  82. data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
  83. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  87. data/lib/active_record/connection_adapters.rb +8 -5
  88. data/lib/active_record/connection_handling.rb +20 -38
  89. data/lib/active_record/core.rb +129 -117
  90. data/lib/active_record/database_configurations/database_config.rb +12 -0
  91. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  92. data/lib/active_record/database_configurations/url_config.rb +2 -2
  93. data/lib/active_record/database_configurations.rb +18 -9
  94. data/lib/active_record/delegated_type.rb +33 -11
  95. data/lib/active_record/destroy_association_async_job.rb +1 -1
  96. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  97. data/lib/active_record/dynamic_matchers.rb +1 -1
  98. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  99. data/lib/active_record/encryption/cipher.rb +53 -0
  100. data/lib/active_record/encryption/config.rb +44 -0
  101. data/lib/active_record/encryption/configurable.rb +61 -0
  102. data/lib/active_record/encryption/context.rb +35 -0
  103. data/lib/active_record/encryption/contexts.rb +72 -0
  104. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  105. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  106. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  107. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  108. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  109. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  110. data/lib/active_record/encryption/encryptor.rb +155 -0
  111. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  112. data/lib/active_record/encryption/errors.rb +15 -0
  113. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  114. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  115. data/lib/active_record/encryption/key.rb +28 -0
  116. data/lib/active_record/encryption/key_generator.rb +42 -0
  117. data/lib/active_record/encryption/key_provider.rb +46 -0
  118. data/lib/active_record/encryption/message.rb +33 -0
  119. data/lib/active_record/encryption/message_serializer.rb +80 -0
  120. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  121. data/lib/active_record/encryption/properties.rb +76 -0
  122. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  123. data/lib/active_record/encryption/scheme.rb +99 -0
  124. data/lib/active_record/encryption.rb +55 -0
  125. data/lib/active_record/enum.rb +44 -46
  126. data/lib/active_record/errors.rb +66 -3
  127. data/lib/active_record/fixture_set/file.rb +15 -1
  128. data/lib/active_record/fixture_set/table_row.rb +40 -5
  129. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  130. data/lib/active_record/fixtures.rb +16 -11
  131. data/lib/active_record/future_result.rb +139 -0
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +55 -17
  134. data/lib/active_record/insert_all.rb +39 -6
  135. data/lib/active_record/integration.rb +1 -1
  136. data/lib/active_record/internal_metadata.rb +3 -5
  137. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  138. data/lib/active_record/locking/optimistic.rb +10 -9
  139. data/lib/active_record/log_subscriber.rb +6 -2
  140. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  141. data/lib/active_record/middleware/database_selector.rb +8 -3
  142. data/lib/active_record/migration/command_recorder.rb +4 -4
  143. data/lib/active_record/migration/compatibility.rb +83 -1
  144. data/lib/active_record/migration/join_table.rb +1 -1
  145. data/lib/active_record/migration.rb +109 -79
  146. data/lib/active_record/model_schema.rb +46 -32
  147. data/lib/active_record/nested_attributes.rb +3 -3
  148. data/lib/active_record/no_touching.rb +2 -2
  149. data/lib/active_record/null_relation.rb +2 -6
  150. data/lib/active_record/persistence.rb +134 -45
  151. data/lib/active_record/query_cache.rb +2 -2
  152. data/lib/active_record/query_logs.rb +203 -0
  153. data/lib/active_record/querying.rb +15 -5
  154. data/lib/active_record/railtie.rb +117 -17
  155. data/lib/active_record/railties/controller_runtime.rb +1 -1
  156. data/lib/active_record/railties/databases.rake +83 -58
  157. data/lib/active_record/readonly_attributes.rb +11 -0
  158. data/lib/active_record/reflection.rb +45 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  160. data/lib/active_record/relation/batches.rb +3 -3
  161. data/lib/active_record/relation/calculations.rb +42 -25
  162. data/lib/active_record/relation/delegation.rb +6 -6
  163. data/lib/active_record/relation/finder_methods.rb +32 -23
  164. data/lib/active_record/relation/merger.rb +20 -13
  165. data/lib/active_record/relation/predicate_builder.rb +1 -6
  166. data/lib/active_record/relation/query_attribute.rb +5 -11
  167. data/lib/active_record/relation/query_methods.rb +233 -50
  168. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  169. data/lib/active_record/relation/spawn_methods.rb +2 -2
  170. data/lib/active_record/relation/where_clause.rb +22 -15
  171. data/lib/active_record/relation.rb +170 -87
  172. data/lib/active_record/result.rb +17 -2
  173. data/lib/active_record/runtime_registry.rb +2 -4
  174. data/lib/active_record/sanitization.rb +11 -7
  175. data/lib/active_record/schema_dumper.rb +3 -3
  176. data/lib/active_record/schema_migration.rb +0 -4
  177. data/lib/active_record/scoping/default.rb +62 -15
  178. data/lib/active_record/scoping/named.rb +3 -11
  179. data/lib/active_record/scoping.rb +40 -22
  180. data/lib/active_record/serialization.rb +1 -1
  181. data/lib/active_record/signed_id.rb +1 -1
  182. data/lib/active_record/statement_cache.rb +2 -2
  183. data/lib/active_record/tasks/database_tasks.rb +107 -23
  184. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  185. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  186. data/lib/active_record/test_databases.rb +1 -1
  187. data/lib/active_record/test_fixtures.rb +45 -4
  188. data/lib/active_record/timestamp.rb +3 -4
  189. data/lib/active_record/transactions.rb +9 -14
  190. data/lib/active_record/translation.rb +2 -2
  191. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  192. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  193. data/lib/active_record/type/internal/timezone.rb +2 -2
  194. data/lib/active_record/type/serialized.rb +1 -1
  195. data/lib/active_record/type/type_map.rb +17 -20
  196. data/lib/active_record/type.rb +1 -2
  197. data/lib/active_record/validations/associated.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +1 -1
  199. data/lib/active_record.rb +170 -2
  200. data/lib/arel/attributes/attribute.rb +0 -8
  201. data/lib/arel/collectors/bind.rb +2 -2
  202. data/lib/arel/collectors/composite.rb +3 -3
  203. data/lib/arel/collectors/sql_string.rb +1 -1
  204. data/lib/arel/collectors/substitute_binds.rb +1 -1
  205. data/lib/arel/crud.rb +18 -22
  206. data/lib/arel/delete_manager.rb +2 -4
  207. data/lib/arel/insert_manager.rb +2 -3
  208. data/lib/arel/nodes/casted.rb +1 -1
  209. data/lib/arel/nodes/delete_statement.rb +8 -13
  210. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  211. data/lib/arel/nodes/insert_statement.rb +2 -2
  212. data/lib/arel/nodes/select_core.rb +2 -2
  213. data/lib/arel/nodes/select_statement.rb +2 -2
  214. data/lib/arel/nodes/update_statement.rb +3 -2
  215. data/lib/arel/predications.rb +3 -3
  216. data/lib/arel/select_manager.rb +10 -4
  217. data/lib/arel/table.rb +0 -1
  218. data/lib/arel/tree_manager.rb +0 -12
  219. data/lib/arel/update_manager.rb +2 -4
  220. data/lib/arel/visitors/dot.rb +80 -90
  221. data/lib/arel/visitors/mysql.rb +6 -1
  222. data/lib/arel/visitors/postgresql.rb +0 -10
  223. data/lib/arel/visitors/to_sql.rb +44 -3
  224. data/lib/arel.rb +1 -1
  225. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  227. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  228. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  229. metadata +55 -16
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2020 David Heinemeier Hansson
1
+ Copyright (c) 2004-2021 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
@@ -97,8 +98,12 @@ module ActiveRecord
97
98
  end
98
99
 
99
100
  def scope
100
- if (scope = klass.current_scope) && scope.try(:proxy_association) == self
101
+ if disable_joins
102
+ DisableJoinsAssociationScope.create.scope(self)
103
+ elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self
101
104
  scope.spawn
105
+ elsif scope = klass.global_current_scope
106
+ target_scope.merge!(association_scope).merge!(scope)
102
107
  else
103
108
  target_scope.merge!(association_scope)
104
109
  end
@@ -191,7 +196,7 @@ module ActiveRecord
191
196
  @reflection = @owner.class._reflect_on_association(reflection_name)
192
197
  end
193
198
 
194
- def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
199
+ def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
195
200
  except_from_scope_attributes ||= {}
196
201
  skip_assign = [reflection.foreign_key, reflection.type].compact
197
202
  assigned_keys = record.changed_attribute_names_to_save
@@ -210,8 +215,14 @@ module ActiveRecord
210
215
  end
211
216
 
212
217
  private
218
+ # Reader and writer methods call this so that consistent errors are presented
219
+ # when the association target class does not exist.
220
+ def ensure_klass_exists!
221
+ klass
222
+ end
223
+
213
224
  def find_target
214
- if (owner.strict_loading? || reflection.strict_loading?) && owner.validation_context.nil?
225
+ if violates_strict_loading? && owner.validation_context.nil?
215
226
  Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
216
227
  end
217
228
 
@@ -224,7 +235,20 @@ module ActiveRecord
224
235
  end
225
236
 
226
237
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
227
- sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
238
+ sc.execute(binds, klass.connection) do |record|
239
+ set_inverse_instance(record)
240
+ if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
241
+ record.strict_loading!
242
+ else
243
+ record.strict_loading!(false, mode: owner.strict_loading_mode)
244
+ end
245
+ end
246
+ end
247
+
248
+ def violates_strict_loading?
249
+ return reflection.strict_loading? if reflection.options.key?(:strict_loading)
250
+
251
+ owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
228
252
  end
229
253
 
230
254
  # The scope for this association.
@@ -235,7 +259,11 @@ module ActiveRecord
235
259
  # actually gets built.
236
260
  def association_scope
237
261
  if klass
238
- @association_scope ||= AssociationScope.scope(self)
262
+ @association_scope ||= if disable_joins
263
+ DisableJoinsAssociationScope.scope(self)
264
+ else
265
+ AssociationScope.scope(self)
266
+ end
239
267
  end
240
268
  end
241
269
 
@@ -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,14 +3,13 @@
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
 
10
10
  case options[:dependent]
11
11
  when :destroy
12
- target.destroy
13
- raise ActiveRecord::Rollback unless target.destroyed?
12
+ raise ActiveRecord::Rollback unless target.destroy
14
13
  when :destroy_async
15
14
  id = owner.public_send(reflection.foreign_key.to_sym)
16
15
  primary_key_column = reflection.active_record_primary_key.to_sym
@@ -56,7 +55,8 @@ module ActiveRecord
56
55
 
57
56
  def decrement_counters_before_last_save
58
57
  if reflection.polymorphic?
59
- 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
60
60
  else
61
61
  model_was = klass
62
62
  end
@@ -69,6 +69,14 @@ module ActiveRecord
69
69
  end
70
70
 
71
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?
72
80
  owner.saved_change_to_attribute?(reflection.foreign_key)
73
81
  end
74
82
 
@@ -78,6 +86,8 @@ module ActiveRecord
78
86
  raise_on_type_mismatch!(record)
79
87
  set_inverse_instance(record)
80
88
  @updated = true
89
+ elsif target
90
+ remove_inverse_instance(target)
81
91
  end
82
92
 
83
93
  replace_keys(record, force: true)
@@ -111,7 +121,7 @@ module ActiveRecord
111
121
  def replace_keys(record, force: false)
112
122
  target_key = record ? record._read_attribute(primary_key(record.class)) : nil
113
123
 
114
- if force || owner[reflection.foreign_key] != target_key
124
+ if force || owner._read_attribute(reflection.foreign_key) != target_key
115
125
  owner[reflection.foreign_key] = target_key
116
126
  end
117
127
  end
@@ -126,7 +136,7 @@ module ActiveRecord
126
136
 
127
137
  def invertible_for?(record)
128
138
  inverse = inverse_reflection_for(record)
129
- inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
139
+ inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
130
140
  end
131
141
 
132
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)
@@ -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.
@@ -347,7 +340,7 @@ module ActiveRecord
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,8 @@ module ActiveRecord
459
456
 
460
457
  yield(record) if block_given?
461
458
 
459
+ @replaced_or_added_targets << record if inversing || index || record.new_record?
460
+
462
461
  if index
463
462
  target[index] = record
464
463
  elsif @_was_loaded || !loaded?