activerecord 6.0.4 → 6.1.0.rc1

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +767 -846
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/association_relation.rb +22 -14
  8. data/lib/active_record/associations.rb +114 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +39 -27
  11. data/lib/active_record/associations/association_scope.rb +11 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -13
  22. data/lib/active_record/associations/collection_proxy.rb +12 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/join_dependency/join_association.rb +29 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/preloader/association.rb +13 -5
  32. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/attribute_assignment.rb +10 -8
  35. data/lib/active_record/attribute_methods.rb +52 -48
  36. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  37. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  38. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  39. data/lib/active_record/attribute_methods/query.rb +3 -6
  40. data/lib/active_record/attribute_methods/read.rb +8 -11
  41. data/lib/active_record/attribute_methods/serialization.rb +4 -4
  42. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  43. data/lib/active_record/attribute_methods/write.rb +12 -20
  44. data/lib/active_record/attributes.rb +27 -7
  45. data/lib/active_record/autosave_association.rb +47 -30
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +32 -22
  48. data/lib/active_record/coders/yaml_column.rb +1 -1
  49. data/lib/active_record/connection_adapters.rb +50 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +180 -134
  51. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +110 -30
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -70
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  63. data/lib/active_record/connection_adapters/column.rb +15 -1
  64. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  65. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -24
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -3
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  75. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +12 -53
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -10
  83. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  93. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  94. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  95. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  96. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  97. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +30 -5
  98. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  99. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  101. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  102. data/lib/active_record/connection_handling.rb +210 -71
  103. data/lib/active_record/core.rb +215 -49
  104. data/lib/active_record/database_configurations.rb +124 -85
  105. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  106. data/lib/active_record/database_configurations/database_config.rb +52 -9
  107. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  108. data/lib/active_record/database_configurations/url_config.rb +15 -40
  109. data/lib/active_record/delegated_type.rb +209 -0
  110. data/lib/active_record/destroy_association_async_job.rb +36 -0
  111. data/lib/active_record/enum.rb +33 -23
  112. data/lib/active_record/errors.rb +47 -12
  113. data/lib/active_record/explain.rb +9 -4
  114. data/lib/active_record/explain_subscriber.rb +1 -1
  115. data/lib/active_record/fixture_set/file.rb +10 -17
  116. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  117. data/lib/active_record/fixture_set/render_context.rb +1 -1
  118. data/lib/active_record/fixture_set/table_row.rb +2 -2
  119. data/lib/active_record/fixtures.rb +54 -8
  120. data/lib/active_record/gem_version.rb +3 -3
  121. data/lib/active_record/inheritance.rb +40 -18
  122. data/lib/active_record/insert_all.rb +32 -5
  123. data/lib/active_record/integration.rb +3 -5
  124. data/lib/active_record/internal_metadata.rb +15 -4
  125. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  126. data/lib/active_record/locking/optimistic.rb +13 -16
  127. data/lib/active_record/locking/pessimistic.rb +6 -2
  128. data/lib/active_record/log_subscriber.rb +26 -8
  129. data/lib/active_record/middleware/database_selector.rb +4 -1
  130. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  131. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  132. data/lib/active_record/migration.rb +113 -83
  133. data/lib/active_record/migration/command_recorder.rb +47 -27
  134. data/lib/active_record/migration/compatibility.rb +67 -17
  135. data/lib/active_record/model_schema.rb +88 -42
  136. data/lib/active_record/nested_attributes.rb +2 -3
  137. data/lib/active_record/no_touching.rb +1 -1
  138. data/lib/active_record/persistence.rb +50 -45
  139. data/lib/active_record/query_cache.rb +15 -5
  140. data/lib/active_record/querying.rb +11 -6
  141. data/lib/active_record/railtie.rb +64 -44
  142. data/lib/active_record/railties/databases.rake +253 -98
  143. data/lib/active_record/readonly_attributes.rb +4 -0
  144. data/lib/active_record/reflection.rb +59 -44
  145. data/lib/active_record/relation.rb +90 -64
  146. data/lib/active_record/relation/batches.rb +38 -31
  147. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  148. data/lib/active_record/relation/calculations.rb +100 -43
  149. data/lib/active_record/relation/finder_methods.rb +44 -14
  150. data/lib/active_record/relation/from_clause.rb +1 -1
  151. data/lib/active_record/relation/merger.rb +20 -23
  152. data/lib/active_record/relation/predicate_builder.rb +57 -33
  153. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  154. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
  155. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  156. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  157. data/lib/active_record/relation/query_methods.rb +319 -196
  158. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  159. data/lib/active_record/relation/spawn_methods.rb +6 -5
  160. data/lib/active_record/relation/where_clause.rb +104 -57
  161. data/lib/active_record/result.rb +41 -33
  162. data/lib/active_record/runtime_registry.rb +2 -2
  163. data/lib/active_record/sanitization.rb +6 -17
  164. data/lib/active_record/schema_dumper.rb +34 -4
  165. data/lib/active_record/schema_migration.rb +0 -4
  166. data/lib/active_record/scoping/named.rb +1 -17
  167. data/lib/active_record/secure_token.rb +16 -8
  168. data/lib/active_record/serialization.rb +5 -3
  169. data/lib/active_record/signed_id.rb +116 -0
  170. data/lib/active_record/statement_cache.rb +20 -4
  171. data/lib/active_record/store.rb +2 -2
  172. data/lib/active_record/suppressor.rb +2 -2
  173. data/lib/active_record/table_metadata.rb +36 -52
  174. data/lib/active_record/tasks/database_tasks.rb +139 -113
  175. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  176. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  177. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  178. data/lib/active_record/test_databases.rb +5 -4
  179. data/lib/active_record/test_fixtures.rb +36 -33
  180. data/lib/active_record/timestamp.rb +4 -6
  181. data/lib/active_record/touch_later.rb +21 -21
  182. data/lib/active_record/transactions.rb +15 -64
  183. data/lib/active_record/type.rb +8 -1
  184. data/lib/active_record/type/serialized.rb +6 -2
  185. data/lib/active_record/type_caster/connection.rb +0 -1
  186. data/lib/active_record/type_caster/map.rb +8 -5
  187. data/lib/active_record/validations.rb +1 -0
  188. data/lib/active_record/validations/associated.rb +1 -1
  189. data/lib/active_record/validations/numericality.rb +35 -0
  190. data/lib/active_record/validations/uniqueness.rb +24 -4
  191. data/lib/arel.rb +5 -13
  192. data/lib/arel/attributes/attribute.rb +4 -0
  193. data/lib/arel/collectors/bind.rb +5 -0
  194. data/lib/arel/collectors/composite.rb +8 -0
  195. data/lib/arel/collectors/sql_string.rb +7 -0
  196. data/lib/arel/collectors/substitute_binds.rb +7 -0
  197. data/lib/arel/nodes.rb +3 -1
  198. data/lib/arel/nodes/binary.rb +82 -8
  199. data/lib/arel/nodes/bind_param.rb +8 -0
  200. data/lib/arel/nodes/casted.rb +21 -9
  201. data/lib/arel/nodes/equality.rb +6 -9
  202. data/lib/arel/nodes/grouping.rb +3 -0
  203. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  204. data/lib/arel/nodes/in.rb +8 -1
  205. data/lib/arel/nodes/infix_operation.rb +13 -1
  206. data/lib/arel/nodes/join_source.rb +1 -1
  207. data/lib/arel/nodes/node.rb +7 -6
  208. data/lib/arel/nodes/ordering.rb +27 -0
  209. data/lib/arel/nodes/sql_literal.rb +3 -0
  210. data/lib/arel/nodes/table_alias.rb +7 -3
  211. data/lib/arel/nodes/unary.rb +0 -1
  212. data/lib/arel/predications.rb +12 -18
  213. data/lib/arel/select_manager.rb +1 -2
  214. data/lib/arel/table.rb +13 -5
  215. data/lib/arel/visitors.rb +0 -7
  216. data/lib/arel/visitors/dot.rb +14 -2
  217. data/lib/arel/visitors/mysql.rb +11 -1
  218. data/lib/arel/visitors/postgresql.rb +15 -4
  219. data/lib/arel/visitors/to_sql.rb +89 -78
  220. data/lib/rails/generators/active_record/migration.rb +6 -1
  221. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  222. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  223. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  224. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  225. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  226. metadata +27 -28
  227. data/lib/active_record/advisory_lock_base.rb +0 -18
  228. data/lib/active_record/attribute_decorators.rb +0 -88
  229. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  230. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  231. data/lib/active_record/define_callbacks.rb +0 -22
  232. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  233. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  234. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  235. data/lib/arel/attributes.rb +0 -22
  236. data/lib/arel/visitors/depth_first.rb +0 -203
  237. data/lib/arel/visitors/ibm_db.rb +0 -34
  238. data/lib/arel/visitors/informix.rb +0 -62
  239. data/lib/arel/visitors/mssql.rb +0 -156
  240. data/lib/arel/visitors/oracle.rb +0 -158
  241. data/lib/arel/visitors/oracle12.rb +0 -65
  242. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -101,7 +101,7 @@ module ActiveRecord
101
101
  # converting them into an array and iterating through them using
102
102
  # Array#select.
103
103
  #
104
- # person.pets.select { |pet| pet.name =~ /oo/ }
104
+ # person.pets.select { |pet| /oo/.match?(pet.name) }
105
105
  # # => [
106
106
  # # #<Pet id: 2, name: "Spook", person_id: 1>,
107
107
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
@@ -374,7 +374,7 @@ module ActiveRecord
374
374
  # person.pets
375
375
  # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
376
376
  #
377
- # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
377
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
378
378
  #
379
379
  # person.pets.replace(other_pets)
380
380
  #
@@ -921,7 +921,7 @@ module ActiveRecord
921
921
  !!@association.include?(record)
922
922
  end
923
923
 
924
- def proxy_association
924
+ def proxy_association # :nodoc:
925
925
  @association
926
926
  end
927
927
 
@@ -1087,17 +1087,24 @@ module ActiveRecord
1087
1087
  end
1088
1088
 
1089
1089
  def reset_scope # :nodoc:
1090
- @offsets = {}
1090
+ @offsets = @take = nil
1091
1091
  @scope = nil
1092
1092
  self
1093
1093
  end
1094
1094
 
1095
+ def inspect # :nodoc:
1096
+ load_target if find_from_target?
1097
+ super
1098
+ end
1099
+
1095
1100
  delegate_methods = [
1096
1101
  QueryMethods,
1097
1102
  SpawnMethods,
1098
1103
  ].flat_map { |klass|
1099
1104
  klass.public_instance_methods(false)
1100
- } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1105
+ } - self.public_instance_methods(false) - [:select] + [
1106
+ :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
1107
+ ]
1101
1108
 
1102
1109
  delegate(*delegate_methods, to: :scope)
1103
1110
 
@@ -16,5 +16,18 @@ module ActiveRecord::Associations
16
16
  attrs[reflection.type] = nil if reflection.type.present?
17
17
  end
18
18
  end
19
+
20
+ private
21
+ # Sets the owner attributes on the given record
22
+ def set_owner_attributes(record)
23
+ return if options[:through]
24
+
25
+ key = owner._read_attribute(reflection.join_foreign_key)
26
+ record._write_attribute(reflection.join_primary_key, key)
27
+
28
+ if reflection.type
29
+ record._write_attribute(reflection.type, owner.class.polymorphic_name)
30
+ end
31
+ end
19
32
  end
20
33
  end
@@ -26,6 +26,28 @@ module ActiveRecord
26
26
  # No point in executing the counter update since we're going to destroy the parent anyway
27
27
  load_target.each { |t| t.destroyed_by_association = reflection }
28
28
  destroy_all
29
+ when :destroy_async
30
+ load_target.each do |t|
31
+ t.destroyed_by_association = reflection
32
+ end
33
+
34
+ unless target.empty?
35
+ association_class = target.first.class
36
+ primary_key_column = association_class.primary_key.to_sym
37
+
38
+ ids = target.collect do |assoc|
39
+ assoc.public_send(primary_key_column)
40
+ end
41
+
42
+ enqueue_destroy_association(
43
+ owner_model_name: owner.class.to_s,
44
+ owner_id: owner.id,
45
+ association_class: association_class.to_s,
46
+ association_ids: ids,
47
+ association_primary_key_column: primary_key_column,
48
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
49
+ )
50
+ end
29
51
  else
30
52
  delete_all
31
53
  end
@@ -52,7 +74,7 @@ module ActiveRecord
52
74
  # the loaded flag is set to true as well.
53
75
  def count_records
54
76
  count = if reflection.has_cached_counter?
55
- owner._read_attribute(reflection.counter_cache_column).to_i
77
+ owner.read_attribute(reflection.counter_cache_column).to_i
56
78
  else
57
79
  scope.count(:all)
58
80
  end
@@ -75,7 +97,7 @@ module ActiveRecord
75
97
  if reflection.counter_must_be_updated_by_has_many?
76
98
  counter = reflection.counter_cache_column
77
99
  owner.increment(counter, difference)
78
- owner.send(:clear_attribute_change, counter) # eww
100
+ owner.send(:"clear_#{counter}_change")
79
101
  end
80
102
  end
81
103
 
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
 
9
9
  def initialize(owner, reflection)
10
10
  super
11
- @through_records = {}
11
+ @through_records = {}.compare_by_identity
12
12
  end
13
13
 
14
14
  def concat(*records)
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # However, after insert_record has been called, the cache is cleared in
55
55
  # order to allow multiple instances of the same record in an association.
56
56
  def build_through_record(record)
57
- @through_records[record.object_id] ||= begin
57
+ @through_records[record] ||= begin
58
58
  ensure_mutable
59
59
 
60
60
  attributes = through_scope_attributes
@@ -65,7 +65,10 @@ module ActiveRecord
65
65
  end
66
66
  end
67
67
 
68
+ attr_reader :through_scope
69
+
68
70
  def through_scope_attributes
71
+ scope = through_scope || self.scope
69
72
  scope.where_values_hash(through_association.reflection.name.to_s).
70
73
  except!(through_association.reflection.foreign_key,
71
74
  through_association.reflection.klass.inheritance_column)
@@ -77,12 +80,13 @@ module ActiveRecord
77
80
  association.save!
78
81
  end
79
82
  ensure
80
- @through_records.delete(record.object_id)
83
+ @through_records.delete(record)
81
84
  end
82
85
 
83
86
  def build_record(attributes)
84
87
  ensure_not_nested
85
88
 
89
+ @through_scope = scope
86
90
  record = super
87
91
 
88
92
  inverse = source_reflection.inverse_of
@@ -95,6 +99,8 @@ module ActiveRecord
95
99
  end
96
100
 
97
101
  record
102
+ ensure
103
+ @through_scope = nil
98
104
  end
99
105
 
100
106
  def remove_records(existing_records, records, method)
@@ -202,7 +208,7 @@ module ActiveRecord
202
208
  end
203
209
  end
204
210
 
205
- @through_records.delete(record.object_id)
211
+ @through_records.delete(record)
206
212
  end
207
213
  end
208
214
 
@@ -32,6 +32,18 @@ module ActiveRecord
32
32
  target.destroyed_by_association = reflection
33
33
  target.destroy
34
34
  throw(:abort) unless target.destroyed?
35
+ when :destroy_async
36
+ primary_key_column = target.class.primary_key.to_sym
37
+ id = target.public_send(primary_key_column)
38
+
39
+ enqueue_destroy_association(
40
+ owner_model_name: owner.class.to_s,
41
+ owner_id: owner.id,
42
+ association_class: reflection.klass.to_s,
43
+ association_ids: [id],
44
+ association_primary_key_column: primary_key_column,
45
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
46
+ )
35
47
  when :nullify
36
48
  target.update_columns(nullified_owner_attributes) if target.persisted?
37
49
  end
@@ -81,7 +93,9 @@ module ActiveRecord
81
93
  target.delete
82
94
  when :destroy
83
95
  target.destroyed_by_association = reflection
84
- target.destroy
96
+ if target.persisted?
97
+ target.destroy
98
+ end
85
99
  else
86
100
  nullify_owner_attributes(target)
87
101
  remove_inverse_instance(target)
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  Table = Struct.new(:node, :columns) do # :nodoc:
35
35
  def column_aliases
36
36
  t = node.table
37
- columns.map { |column| t[column.name].as Arel.sql column.alias }
37
+ columns.map { |column| t[column.name].as(column.alias) }
38
38
  end
39
39
  end
40
40
  Column = Struct.new(:name, :alias)
@@ -78,14 +78,18 @@ module ActiveRecord
78
78
  join_root.drop(1).map!(&:reflection)
79
79
  end
80
80
 
81
- def join_constraints(joins_to_add, alias_tracker)
81
+ def join_constraints(joins_to_add, alias_tracker, references)
82
82
  @alias_tracker = alias_tracker
83
+ @joined_tables = {}
84
+ @references = {}
85
+
86
+ references.each do |table_name|
87
+ @references[table_name.to_sym] = table_name if table_name.is_a?(String)
88
+ end unless references.empty?
83
89
 
84
- construct_tables!(join_root)
85
90
  joins = make_join_constraints(join_root, join_type)
86
91
 
87
92
  joins.concat joins_to_add.flat_map { |oj|
88
- construct_tables!(oj.join_root)
89
93
  if join_root.match? oj.join_root
90
94
  walk(join_root, oj.join_root, oj.join_type)
91
95
  else
@@ -94,26 +98,33 @@ module ActiveRecord
94
98
  }
95
99
  end
96
100
 
97
- def instantiate(result_set, &block)
101
+ def instantiate(result_set, strict_loading_value, &block)
98
102
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
99
103
 
100
- seen = Hash.new { |i, object_id|
101
- i[object_id] = Hash.new { |j, child_class|
104
+ seen = Hash.new { |i, parent|
105
+ i[parent] = Hash.new { |j, child_class|
102
106
  j[child_class] = {}
103
107
  }
104
- }
108
+ }.compare_by_identity
105
109
 
106
110
  model_cache = Hash.new { |h, klass| h[klass] = {} }
107
111
  parents = model_cache[join_root]
108
112
 
109
113
  column_aliases = aliases.column_aliases(join_root)
110
- column_names = explicit_selections(column_aliases, result_set)
114
+ column_names = []
115
+
116
+ result_set.columns.each do |name|
117
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
118
+ end
111
119
 
112
120
  if column_names.empty?
113
121
  column_types = {}
114
122
  else
115
123
  column_types = result_set.column_types
116
- column_types = column_types.slice(*column_names) unless column_types.empty?
124
+ unless column_types.empty?
125
+ attribute_types = join_root.attribute_types
126
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
127
+ end
117
128
  column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
118
129
  end
119
130
 
@@ -128,7 +139,7 @@ module ActiveRecord
128
139
  result_set.each { |row_hash|
129
140
  parent_key = primary_key ? row_hash[primary_key] : row_hash
130
141
  parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
131
- construct(parent, join_root, row_hash, seen, model_cache)
142
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
132
143
  }
133
144
  end
134
145
 
@@ -136,37 +147,36 @@ module ActiveRecord
136
147
  end
137
148
 
138
149
  def apply_column_aliases(relation)
150
+ @join_root_alias = relation.select_values.empty?
139
151
  relation._select!(-> { aliases.columns })
140
152
  end
141
153
 
154
+ def each(&block)
155
+ join_root.each(&block)
156
+ end
157
+
142
158
  protected
143
159
  attr_reader :join_root, :join_type
144
160
 
145
161
  private
146
- attr_reader :alias_tracker
147
-
148
- def explicit_selections(root_column_aliases, result_set)
149
- root_names = root_column_aliases.map(&:name).to_set
150
- result_set.columns.each_with_object([]) do |name, result|
151
- result << name unless /\At\d+_r\d+\z/.match?(name) || root_names.include?(name)
152
- end
153
- end
162
+ attr_reader :alias_tracker, :join_root_alias
154
163
 
155
164
  def aliases
156
165
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
157
- columns = join_part.column_names.each_with_index.map { |column_name, j|
166
+ column_names = if join_part == join_root && !join_root_alias
167
+ primary_key = join_root.primary_key
168
+ primary_key ? [primary_key] : []
169
+ else
170
+ join_part.column_names
171
+ end
172
+
173
+ columns = column_names.each_with_index.map { |column_name, j|
158
174
  Aliases::Column.new column_name, "t#{i}_r#{j}"
159
175
  }
160
176
  Aliases::Table.new(join_part, columns)
161
177
  }
162
178
  end
163
179
 
164
- def construct_tables!(join_root)
165
- join_root.each_children do |parent, child|
166
- child.tables = table_aliases_for(parent, child)
167
- end
168
- end
169
-
170
180
  def make_join_constraints(join_root, join_type)
171
181
  join_root.children.flat_map do |child|
172
182
  make_constraints(join_root, child, join_type)
@@ -176,23 +186,25 @@ module ActiveRecord
176
186
  def make_constraints(parent, child, join_type)
177
187
  foreign_table = parent.table
178
188
  foreign_klass = parent.base_klass
179
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
180
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
181
- end
189
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
190
+ table, terminated = @joined_tables[reflection]
191
+ root = reflection == child.reflection
182
192
 
183
- def table_aliases_for(parent, node)
184
- node.reflection.chain.map { |reflection|
185
- alias_tracker.aliased_table_for(
186
- reflection.table_name,
187
- table_alias_for(reflection, parent, reflection != node.reflection),
188
- reflection.klass.type_caster
189
- )
190
- }
191
- end
193
+ if table && (!root || !terminated)
194
+ @joined_tables[reflection] = [table, root] if root
195
+ next table, true
196
+ end
197
+
198
+ table_name = @references[reflection.name.to_sym]
199
+
200
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
201
+ name = reflection.alias_candidate(parent.table_name)
202
+ root ? name : "#{name}_join"
203
+ end
192
204
 
193
- def table_alias_for(reflection, parent, join)
194
- name = reflection.alias_candidate(parent.table_name)
195
- join ? "#{name}_join" : name
205
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
206
+ table
207
+ end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
196
208
  end
197
209
 
198
210
  def walk(left, right, join_type)
@@ -223,7 +235,7 @@ module ActiveRecord
223
235
  end
224
236
  end
225
237
 
226
- def construct(ar_parent, parent, row, seen, model_cache)
238
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
227
239
  return if ar_parent.nil?
228
240
 
229
241
  parent.children.each do |node|
@@ -232,7 +244,7 @@ module ActiveRecord
232
244
  other.loaded!
233
245
  elsif ar_parent.association_cached?(node.reflection.name)
234
246
  model = ar_parent.association(node.reflection.name).target
235
- construct(model, node, row, seen, model_cache)
247
+ construct(model, node, row, seen, model_cache, strict_loading_value)
236
248
  next
237
249
  end
238
250
 
@@ -244,24 +256,25 @@ module ActiveRecord
244
256
  next
245
257
  end
246
258
 
247
- model = seen[ar_parent.object_id][node][id]
259
+ model = seen[ar_parent][node][id]
248
260
 
249
261
  if model
250
- construct(model, node, row, seen, model_cache)
262
+ construct(model, node, row, seen, model_cache, strict_loading_value)
251
263
  else
252
- model = construct_model(ar_parent, node, row, model_cache, id)
264
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
253
265
 
254
- seen[ar_parent.object_id][node][id] = model
255
- construct(model, node, row, seen, model_cache)
266
+ seen[ar_parent][node][id] = model
267
+ construct(model, node, row, seen, model_cache, strict_loading_value)
256
268
  end
257
269
  end
258
270
  end
259
271
 
260
- def construct_model(record, node, row, model_cache, id)
272
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
261
273
  other = record.association(node.reflection.name)
262
274
 
263
275
  model = model_cache[node][id] ||=
264
276
  node.instantiate(row, aliases.column_aliases(node)) do |m|
277
+ m.strict_loading! if strict_loading_value
265
278
  other.set_inverse_instance(m)
266
279
  end
267
280
 
@@ -272,6 +285,7 @@ module ActiveRecord
272
285
  end
273
286
 
274
287
  model.readonly! if node.readonly?
288
+ model.strict_loading! if node.strict_loading?
275
289
  model
276
290
  end
277
291
  end
@@ -14,7 +14,6 @@ module ActiveRecord
14
14
  super(reflection.klass, children)
15
15
 
16
16
  @reflection = reflection
17
- @tables = nil
18
17
  end
19
18
 
20
19
  def match?(other)
@@ -24,11 +23,23 @@ module ActiveRecord
24
23
 
25
24
  def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
26
25
  joins = []
26
+ chain = []
27
+
28
+ reflection.chain.each do |reflection|
29
+ table, terminated = yield reflection
30
+ @table ||= table
31
+
32
+ if terminated
33
+ foreign_table, foreign_klass = table, reflection.klass
34
+ break
35
+ end
36
+
37
+ chain << [reflection, table]
38
+ end
27
39
 
28
40
  # The chain starts with the target table, but we want to end with it here (makes
29
41
  # more sense in this context), so we reverse
30
- reflection.chain.reverse_each.with_index(1) do |reflection, i|
31
- table = tables[-i]
42
+ chain.reverse_each do |reflection, table|
32
43
  klass = reflection.klass
33
44
 
34
45
  join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
@@ -43,13 +54,15 @@ module ActiveRecord
43
54
  arel = join_scope.arel(alias_tracker.aliases)
44
55
  nodes = arel.constraints.first
45
56
 
46
- others = nodes.children.extract! do |node|
47
- !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
57
+ if nodes.is_a?(Arel::Nodes::And)
58
+ others = nodes.children.extract! do |node|
59
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
60
+ end
48
61
  end
49
62
 
50
- joins << table.create_join(table, table.create_on(nodes), join_type)
63
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
51
64
 
52
- unless others.empty?
65
+ if others && !others.empty?
53
66
  joins.concat arel.join_sources
54
67
  append_constraints(joins.last, others)
55
68
  end
@@ -61,24 +74,26 @@ module ActiveRecord
61
74
  joins
62
75
  end
63
76
 
64
- def tables=(tables)
65
- @tables = tables
66
- @table = tables.first
67
- end
68
-
69
77
  def readonly?
70
78
  return @readonly if defined?(@readonly)
71
79
 
72
80
  @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
73
81
  end
74
82
 
83
+ def strict_loading?
84
+ return @strict_loading if defined?(@strict_loading)
85
+
86
+ @strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
87
+ end
88
+
75
89
  private
76
90
  def append_constraints(join, constraints)
77
91
  if join.is_a?(Arel::Nodes::StringJoin)
78
- join_string = table.create_and(constraints.unshift(join.left))
92
+ join_string = Arel::Nodes::And.new(constraints.unshift join.left)
79
93
  join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
80
94
  else
81
- join.right.expr.children.concat(constraints)
95
+ right = join.right
96
+ right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
82
97
  end
83
98
  end
84
99
  end