activerecord 6.0.6.1 → 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 +764 -942
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +22 -14
  7. data/lib/active_record/associations/alias_tracker.rb +19 -15
  8. data/lib/active_record/associations/association.rb +39 -27
  9. data/lib/active_record/associations/association_scope.rb +11 -15
  10. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  12. data/lib/active_record/associations/builder/association.rb +9 -3
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +19 -13
  20. data/lib/active_record/associations/collection_proxy.rb +12 -5
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -2
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +29 -14
  26. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/preloader/association.rb +13 -5
  29. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations.rb +114 -11
  33. data/lib/active_record/attribute_assignment.rb +10 -8
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  35. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  36. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  37. data/lib/active_record/attribute_methods/query.rb +3 -6
  38. data/lib/active_record/attribute_methods/read.rb +8 -11
  39. data/lib/active_record/attribute_methods/serialization.rb +4 -4
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  41. data/lib/active_record/attribute_methods/write.rb +12 -20
  42. data/lib/active_record/attribute_methods.rb +52 -48
  43. data/lib/active_record/attributes.rb +27 -7
  44. data/lib/active_record/autosave_association.rb +47 -30
  45. data/lib/active_record/base.rb +2 -14
  46. data/lib/active_record/callbacks.rb +32 -22
  47. data/lib/active_record/coders/yaml_column.rb +2 -24
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +180 -134
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +35 -44
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +110 -30
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -70
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  61. data/lib/active_record/connection_adapters/column.rb +15 -1
  62. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  63. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -24
  65. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  69. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -3
  71. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  73. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  74. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  75. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +12 -53
  77. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  78. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -10
  80. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  91. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  93. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  94. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  95. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +30 -5
  96. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  97. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  98. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  99. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  100. data/lib/active_record/connection_adapters.rb +50 -0
  101. data/lib/active_record/connection_handling.rb +210 -71
  102. data/lib/active_record/core.rb +214 -58
  103. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  104. data/lib/active_record/database_configurations/database_config.rb +52 -9
  105. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  106. data/lib/active_record/database_configurations/url_config.rb +15 -40
  107. data/lib/active_record/database_configurations.rb +124 -85
  108. data/lib/active_record/delegated_type.rb +209 -0
  109. data/lib/active_record/destroy_association_async_job.rb +36 -0
  110. data/lib/active_record/enum.rb +33 -23
  111. data/lib/active_record/errors.rb +47 -12
  112. data/lib/active_record/explain.rb +9 -4
  113. data/lib/active_record/explain_subscriber.rb +1 -1
  114. data/lib/active_record/fixture_set/file.rb +10 -17
  115. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  116. data/lib/active_record/fixture_set/render_context.rb +1 -1
  117. data/lib/active_record/fixture_set/table_row.rb +2 -2
  118. data/lib/active_record/fixtures.rb +54 -8
  119. data/lib/active_record/gem_version.rb +3 -3
  120. data/lib/active_record/inheritance.rb +40 -18
  121. data/lib/active_record/insert_all.rb +32 -5
  122. data/lib/active_record/integration.rb +3 -5
  123. data/lib/active_record/internal_metadata.rb +15 -4
  124. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  125. data/lib/active_record/locking/optimistic.rb +13 -16
  126. data/lib/active_record/locking/pessimistic.rb +6 -2
  127. data/lib/active_record/log_subscriber.rb +26 -8
  128. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  129. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/migration/command_recorder.rb +47 -27
  132. data/lib/active_record/migration/compatibility.rb +67 -17
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/model_schema.rb +88 -42
  135. data/lib/active_record/nested_attributes.rb +2 -3
  136. data/lib/active_record/no_touching.rb +1 -1
  137. data/lib/active_record/persistence.rb +50 -45
  138. data/lib/active_record/query_cache.rb +15 -5
  139. data/lib/active_record/querying.rb +11 -6
  140. data/lib/active_record/railtie.rb +64 -44
  141. data/lib/active_record/railties/databases.rake +253 -98
  142. data/lib/active_record/readonly_attributes.rb +4 -0
  143. data/lib/active_record/reflection.rb +59 -44
  144. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  145. data/lib/active_record/relation/batches.rb +38 -31
  146. data/lib/active_record/relation/calculations.rb +100 -43
  147. data/lib/active_record/relation/finder_methods.rb +44 -14
  148. data/lib/active_record/relation/from_clause.rb +1 -1
  149. data/lib/active_record/relation/merger.rb +20 -23
  150. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  151. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
  152. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  153. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  154. data/lib/active_record/relation/predicate_builder.rb +57 -33
  155. data/lib/active_record/relation/query_methods.rb +319 -198
  156. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  157. data/lib/active_record/relation/spawn_methods.rb +6 -5
  158. data/lib/active_record/relation/where_clause.rb +104 -57
  159. data/lib/active_record/relation.rb +90 -64
  160. data/lib/active_record/result.rb +41 -33
  161. data/lib/active_record/runtime_registry.rb +2 -2
  162. data/lib/active_record/sanitization.rb +6 -17
  163. data/lib/active_record/schema_dumper.rb +34 -4
  164. data/lib/active_record/schema_migration.rb +0 -4
  165. data/lib/active_record/scoping/named.rb +1 -17
  166. data/lib/active_record/secure_token.rb +16 -8
  167. data/lib/active_record/serialization.rb +5 -3
  168. data/lib/active_record/signed_id.rb +116 -0
  169. data/lib/active_record/statement_cache.rb +20 -4
  170. data/lib/active_record/store.rb +2 -2
  171. data/lib/active_record/suppressor.rb +2 -2
  172. data/lib/active_record/table_metadata.rb +36 -52
  173. data/lib/active_record/tasks/database_tasks.rb +139 -113
  174. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  175. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  176. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  177. data/lib/active_record/test_databases.rb +5 -4
  178. data/lib/active_record/test_fixtures.rb +36 -33
  179. data/lib/active_record/timestamp.rb +4 -6
  180. data/lib/active_record/touch_later.rb +21 -21
  181. data/lib/active_record/transactions.rb +15 -64
  182. data/lib/active_record/type/serialized.rb +6 -2
  183. data/lib/active_record/type.rb +8 -1
  184. data/lib/active_record/type_caster/connection.rb +0 -1
  185. data/lib/active_record/type_caster/map.rb +8 -5
  186. data/lib/active_record/validations/associated.rb +1 -1
  187. data/lib/active_record/validations/numericality.rb +35 -0
  188. data/lib/active_record/validations/uniqueness.rb +24 -4
  189. data/lib/active_record/validations.rb +1 -0
  190. data/lib/active_record.rb +7 -14
  191. data/lib/arel/attributes/attribute.rb +4 -0
  192. data/lib/arel/collectors/bind.rb +5 -0
  193. data/lib/arel/collectors/composite.rb +8 -0
  194. data/lib/arel/collectors/sql_string.rb +7 -0
  195. data/lib/arel/collectors/substitute_binds.rb +7 -0
  196. data/lib/arel/nodes/binary.rb +82 -8
  197. data/lib/arel/nodes/bind_param.rb +8 -0
  198. data/lib/arel/nodes/casted.rb +21 -9
  199. data/lib/arel/nodes/equality.rb +6 -9
  200. data/lib/arel/nodes/grouping.rb +3 -0
  201. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  202. data/lib/arel/nodes/in.rb +8 -1
  203. data/lib/arel/nodes/infix_operation.rb +13 -1
  204. data/lib/arel/nodes/join_source.rb +1 -1
  205. data/lib/arel/nodes/node.rb +7 -6
  206. data/lib/arel/nodes/ordering.rb +27 -0
  207. data/lib/arel/nodes/sql_literal.rb +3 -0
  208. data/lib/arel/nodes/table_alias.rb +7 -3
  209. data/lib/arel/nodes/unary.rb +0 -1
  210. data/lib/arel/nodes.rb +3 -1
  211. data/lib/arel/predications.rb +12 -18
  212. data/lib/arel/select_manager.rb +1 -2
  213. data/lib/arel/table.rb +13 -5
  214. data/lib/arel/visitors/dot.rb +14 -2
  215. data/lib/arel/visitors/mysql.rb +11 -1
  216. data/lib/arel/visitors/postgresql.rb +15 -4
  217. data/lib/arel/visitors/to_sql.rb +89 -78
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel.rb +5 -13
  220. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  221. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  222. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  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 +30 -32
  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
@@ -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
  # Implements the details of eager loading of Active Record associations.
@@ -58,7 +60,7 @@ module ActiveRecord
58
60
  # == Parameters
59
61
  # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
60
62
  # i.e. +records+ itself may also contain arrays of records. In any case,
61
- # +preload_associations+ will preload the all associations records by
63
+ # +preload_associations+ will preload all associations records by
62
64
  # flattening +records+.
63
65
  #
64
66
  # +associations+ specifies one or more associations that you want to
@@ -175,8 +177,8 @@ module ActiveRecord
175
177
  end
176
178
 
177
179
  def records_by_owner
178
- @records_by_owner ||= owners.each_with_object({}) do |owner, result|
179
- result[owner] = Array(owner.association(reflection.name).target)
180
+ @records_by_owner ||= owners.index_with do |owner|
181
+ Array(owner.association(reflection.name).target)
180
182
  end
181
183
  end
182
184
 
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  replace(record)
18
18
  end
19
19
 
20
- def build(attributes = {}, &block)
20
+ def build(attributes = nil, &block)
21
21
  record = build_record(attributes, &block)
22
22
  set_new_record(record)
23
23
  record
@@ -2,38 +2,116 @@
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
4
  require "active_support/core_ext/string/conversions"
5
- require "active_support/core_ext/module/remove_method"
6
- require "active_record/errors"
7
5
 
8
6
  module ActiveRecord
9
7
  class AssociationNotFoundError < ConfigurationError #:nodoc:
8
+ attr_reader :record, :association_name
10
9
  def initialize(record = nil, association_name = nil)
10
+ @record = record
11
+ @association_name = association_name
11
12
  if record && association_name
12
13
  super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
13
14
  else
14
15
  super("Association was not found.")
15
16
  end
16
17
  end
18
+
19
+ class Correction
20
+ def initialize(error)
21
+ @error = error
22
+ end
23
+
24
+ def corrections
25
+ if @error.association_name
26
+ maybe_these = @error.record.class.reflections.keys
27
+
28
+ maybe_these.sort_by { |n|
29
+ DidYouMean::Jaro.distance(@error.association_name.to_s, n)
30
+ }.reverse.first(4)
31
+ else
32
+ []
33
+ end
34
+ end
35
+ end
36
+
37
+ # We may not have DYM, and DYM might not let us register error handlers
38
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
39
+ DidYouMean.correct_error(self, Correction)
40
+ end
17
41
  end
18
42
 
19
43
  class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
44
+ attr_reader :reflection, :associated_class
20
45
  def initialize(reflection = nil, associated_class = nil)
21
46
  if reflection
47
+ @reflection = reflection
48
+ @associated_class = associated_class.nil? ? reflection.klass : associated_class
22
49
  super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
23
50
  else
24
51
  super("Could not find the inverse association.")
25
52
  end
26
53
  end
54
+
55
+ class Correction
56
+ def initialize(error)
57
+ @error = error
58
+ end
59
+
60
+ def corrections
61
+ if @error.reflection && @error.associated_class
62
+ maybe_these = @error.associated_class.reflections.keys
63
+
64
+ maybe_these.sort_by { |n|
65
+ DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
66
+ }.reverse.first(4)
67
+ else
68
+ []
69
+ end
70
+ end
71
+ end
72
+
73
+ # We may not have DYM, and DYM might not let us register error handlers
74
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
75
+ DidYouMean.correct_error(self, Correction)
76
+ end
27
77
  end
28
78
 
29
79
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
30
- def initialize(owner_class_name = nil, reflection = nil)
31
- if owner_class_name && reflection
32
- super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
80
+ attr_reader :owner_class, :reflection
81
+
82
+ def initialize(owner_class = nil, reflection = nil)
83
+ if owner_class && reflection
84
+ @owner_class = owner_class
85
+ @reflection = reflection
86
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
33
87
  else
34
88
  super("Could not find the association.")
35
89
  end
36
90
  end
91
+
92
+ class Correction
93
+ def initialize(error)
94
+ @error = error
95
+ end
96
+
97
+ def corrections
98
+ if @error.reflection && @error.owner_class
99
+ maybe_these = @error.owner_class.reflections.keys
100
+ maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
101
+
102
+ maybe_these.sort_by { |n|
103
+ DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
104
+ }.reverse.first(4)
105
+ else
106
+ []
107
+ end
108
+ end
109
+ end
110
+
111
+ # We may not have DYM, and DYM might not let us register error handlers
112
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
113
+ DidYouMean.correct_error(self, Correction)
114
+ end
37
115
  end
38
116
 
39
117
  class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
@@ -702,9 +780,8 @@ module ActiveRecord
702
780
  # inverse detection only works on #has_many, #has_one, and
703
781
  # #belongs_to associations.
704
782
  #
705
- # Extra options on the associations, as defined in the
706
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt>
707
- # constant, or a custom scope, will also prevent the association's inverse
783
+ # <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
784
+ # or a custom scope, will also prevent the association's inverse
708
785
  # from being found automatically.
709
786
  #
710
787
  # The automatic guessing of the inverse association uses a heuristic based
@@ -1292,7 +1369,9 @@ module ActiveRecord
1292
1369
  # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
1293
1370
  # <tt>:dependent</tt> behavior may affect other callbacks.
1294
1371
  #
1372
+ # * <tt>nil</tt> do nothing (default).
1295
1373
  # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1374
+ # * <tt>:destroy_async</tt> destroys all the associated objects in a background job.
1296
1375
  # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
1297
1376
  # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
1298
1377
  # on polymorphic associations. Callbacks are not executed.
@@ -1357,6 +1436,11 @@ module ActiveRecord
1357
1436
  # Specifies a module or array of modules that will be extended into the association object returned.
1358
1437
  # Useful for defining methods on associations, especially when they should be shared between multiple
1359
1438
  # association objects.
1439
+ # [:strict_loading]
1440
+ # Enforces strict loading every time the associated record is loaded through this association.
1441
+ # [:ensuring_owner_was]
1442
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1443
+ # associated records to be deleted in a background job.
1360
1444
  #
1361
1445
  # Option examples:
1362
1446
  # has_many :comments, -> { order("posted_on") }
@@ -1367,6 +1451,7 @@ module ActiveRecord
1367
1451
  # has_many :tags, as: :taggable
1368
1452
  # has_many :reports, -> { readonly }
1369
1453
  # has_many :subscribers, through: :subscriptions, source: :user
1454
+ # has_many :comments, strict_loading: true
1370
1455
  def has_many(name, scope = nil, **options, &extension)
1371
1456
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
1372
1457
  Reflection.add_reflection self, name, reflection
@@ -1436,7 +1521,9 @@ module ActiveRecord
1436
1521
  # Controls what happens to the associated object when
1437
1522
  # its owner is destroyed:
1438
1523
  #
1524
+ # * <tt>nil</tt> do nothing (default).
1439
1525
  # * <tt>:destroy</tt> causes the associated object to also be destroyed
1526
+ # * <tt>:destroy_async</tt> causes all the associated object to be destroyed in a background job.
1440
1527
  # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
1441
1528
  # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
1442
1529
  # on polymorphic associations. Callbacks are not executed.
@@ -1495,6 +1582,11 @@ module ActiveRecord
1495
1582
  # When set to +true+, the association will also have its presence validated.
1496
1583
  # This will validate the association itself, not the id. You can use
1497
1584
  # +:inverse_of+ to avoid an extra query during validation.
1585
+ # [:strict_loading]
1586
+ # Enforces strict loading every time the associated record is loaded through this association.
1587
+ # [:ensuring_owner_was]
1588
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1589
+ # associated records to be deleted in a background job.
1498
1590
  #
1499
1591
  # Option examples:
1500
1592
  # has_one :credit_card, dependent: :destroy # destroys the associated credit card
@@ -1507,6 +1599,7 @@ module ActiveRecord
1507
1599
  # has_one :club, through: :membership
1508
1600
  # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
1509
1601
  # has_one :credit_card, required: true
1602
+ # has_one :credit_card, strict_loading: true
1510
1603
  def has_one(name, scope = nil, **options)
1511
1604
  reflection = Builder::HasOne.build(self, name, scope, options)
1512
1605
  Reflection.add_reflection self, name, reflection
@@ -1588,7 +1681,8 @@ module ActiveRecord
1588
1681
  # By default this is +id+.
1589
1682
  # [:dependent]
1590
1683
  # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
1591
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
1684
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to
1685
+ # <tt>:destroy_async</tt>, the associated object is scheduled to be destroyed in a background job.
1592
1686
  # This option should not be specified when #belongs_to is used in conjunction with
1593
1687
  # a #has_many relationship on another class because of the potential to leave
1594
1688
  # orphaned records behind.
@@ -1640,6 +1734,11 @@ module ActiveRecord
1640
1734
  # [:default]
1641
1735
  # Provide a callable (i.e. proc or lambda) to specify that the association should
1642
1736
  # be initialized with a particular record before validation.
1737
+ # [:strict_loading]
1738
+ # Enforces strict loading every time the associated record is loaded through this association.
1739
+ # [:ensuring_owner_was]
1740
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1741
+ # associated records to be deleted in a background job.
1643
1742
  #
1644
1743
  # Option examples:
1645
1744
  # belongs_to :firm, foreign_key: "client_of"
@@ -1654,6 +1753,7 @@ module ActiveRecord
1654
1753
  # belongs_to :company, touch: :employees_last_updated_at
1655
1754
  # belongs_to :user, optional: true
1656
1755
  # belongs_to :account, default: -> { company.account }
1756
+ # belongs_to :account, strict_loading: true
1657
1757
  def belongs_to(name, scope = nil, **options)
1658
1758
  reflection = Builder::BelongsTo.build(self, name, scope, options)
1659
1759
  Reflection.add_reflection self, name, reflection
@@ -1676,7 +1776,7 @@ module ActiveRecord
1676
1776
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1677
1777
  # join table with a migration such as this:
1678
1778
  #
1679
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0]
1779
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
1680
1780
  # def change
1681
1781
  # create_join_table :developers, :projects
1682
1782
  # end
@@ -1816,6 +1916,8 @@ module ActiveRecord
1816
1916
  #
1817
1917
  # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
1818
1918
  # <tt>:autosave</tt> to <tt>true</tt>.
1919
+ # [:strict_loading]
1920
+ # Enforces strict loading every time an associated record is loaded through this association.
1819
1921
  #
1820
1922
  # Option examples:
1821
1923
  # has_and_belongs_to_many :projects
@@ -1823,6 +1925,7 @@ module ActiveRecord
1823
1925
  # has_and_belongs_to_many :nations, class_name: "Country"
1824
1926
  # has_and_belongs_to_many :categories, join_table: "prods_cats"
1825
1927
  # has_and_belongs_to_many :categories, -> { readonly }
1928
+ # has_and_belongs_to_many :categories, strict_loading: true
1826
1929
  def has_and_belongs_to_many(name, scope = nil, **options, &extension)
1827
1930
  habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
1828
1931
 
@@ -1853,7 +1956,7 @@ module ActiveRecord
1853
1956
  hm_options[:through] = middle_reflection.name
1854
1957
  hm_options[:source] = join_model.right_reflection.name
1855
1958
 
1856
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
1959
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
1857
1960
  hm_options[k] = options[k] if options.key? k
1858
1961
  end
1859
1962
 
@@ -8,20 +8,22 @@ module ActiveRecord
8
8
 
9
9
  private
10
10
  def _assign_attributes(attributes)
11
- multi_parameter_attributes = {}
12
- nested_parameter_attributes = {}
11
+ multi_parameter_attributes = nested_parameter_attributes = nil
13
12
 
14
13
  attributes.each do |k, v|
15
- if k.include?("(")
16
- multi_parameter_attributes[k] = attributes.delete(k)
14
+ key = k.to_s
15
+
16
+ if key.include?("(")
17
+ (multi_parameter_attributes ||= {})[key] = v
17
18
  elsif v.is_a?(Hash)
18
- nested_parameter_attributes[k] = attributes.delete(k)
19
+ (nested_parameter_attributes ||= {})[key] = v
20
+ else
21
+ _assign_attribute(key, v)
19
22
  end
20
23
  end
21
- super(attributes)
22
24
 
23
- assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
24
- assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
25
+ assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
26
+ assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
25
27
  end
26
28
 
27
29
  # Assign any deferred nested attributes after the base attributes have been set.
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
- attribute_method_suffix "_before_type_cast"
32
+ attribute_method_suffix "_before_type_cast", "_for_database"
33
33
  attribute_method_suffix "_came_from_user?"
34
34
  end
35
35
 
@@ -46,8 +46,10 @@ module ActiveRecord
46
46
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
47
47
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
48
48
  def read_attribute_before_type_cast(attr_name)
49
- sync_with_transaction_state if @transaction_state&.finalized?
50
- @attributes[attr_name.to_s].value_before_type_cast
49
+ name = attr_name.to_s
50
+ name = self.class.attribute_aliases[name] || name
51
+
52
+ attribute_before_type_cast(name)
51
53
  end
52
54
 
53
55
  # Returns a hash of attributes before typecasting and deserialization.
@@ -61,19 +63,21 @@ module ActiveRecord
61
63
  # task.attributes_before_type_cast
62
64
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
63
65
  def attributes_before_type_cast
64
- sync_with_transaction_state if @transaction_state&.finalized?
65
66
  @attributes.values_before_type_cast
66
67
  end
67
68
 
68
69
  private
69
70
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
70
- def attribute_before_type_cast(attribute_name)
71
- read_attribute_before_type_cast(attribute_name)
71
+ def attribute_before_type_cast(attr_name)
72
+ @attributes[attr_name].value_before_type_cast
73
+ end
74
+
75
+ def attribute_for_database(attr_name)
76
+ @attributes[attr_name].value_for_database
72
77
  end
73
78
 
74
- def attribute_came_from_user?(attribute_name)
75
- sync_with_transaction_state if @transaction_state&.finalized?
76
- @attributes[attribute_name].came_from_user?
79
+ def attribute_came_from_user?(attr_name)
80
+ @attributes[attr_name].came_from_user?
77
81
  end
78
82
  end
79
83
  end
@@ -89,7 +89,7 @@ module ActiveRecord
89
89
  # This method is useful in validations and before callbacks to determine
90
90
  # if the next call to +save+ will change a particular attribute. It can be
91
91
  # invoked as +will_save_change_to_name?+ instead of
92
- # <tt>will_save_change_to_attribute("name")</tt>.
92
+ # <tt>will_save_change_to_attribute?("name")</tt>.
93
93
  #
94
94
  # ==== Options
95
95
  #
@@ -156,16 +156,6 @@ module ActiveRecord
156
156
  end
157
157
 
158
158
  private
159
- def mutations_from_database
160
- sync_with_transaction_state if @transaction_state&.finalized?
161
- super
162
- end
163
-
164
- def mutations_before_last_save
165
- sync_with_transaction_state if @transaction_state&.finalized?
166
- super
167
- end
168
-
169
159
  def write_attribute_without_type_cast(attr_name, value)
170
160
  result = super
171
161
  clear_attribute_change(attr_name)
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
 
32
32
  # Returns the primary key column's value before type cast.
33
33
  def id_before_type_cast
34
- read_attribute_before_type_cast(@primary_key)
34
+ attribute_before_type_cast(@primary_key)
35
35
  end
36
36
 
37
37
  # Returns the primary key column's previous value.
@@ -44,13 +44,17 @@ module ActiveRecord
44
44
  attribute_in_database(@primary_key)
45
45
  end
46
46
 
47
+ def id_for_database # :nodoc:
48
+ @attributes[@primary_key].value_for_database
49
+ end
50
+
47
51
  private
48
52
  def attribute_method?(attr_name)
49
53
  attr_name == "id" || super
50
54
  end
51
55
 
52
56
  module ClassMethods
53
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
57
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
54
58
 
55
59
  def instance_method_already_implemented?(method_name)
56
60
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  when false, nil then false
18
18
  else
19
19
  if !type_for_attribute(attr_name) { false }
20
- if Numeric === value || value !~ /[^0-9]/
20
+ if Numeric === value || !value.match?(/[^0-9]/)
21
21
  !value.to_i.zero?
22
22
  else
23
23
  return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
@@ -31,11 +31,8 @@ module ActiveRecord
31
31
  end
32
32
  end
33
33
 
34
- private
35
- # Dispatch target for <tt>*?</tt> attribute methods.
36
- def attribute?(attribute_name)
37
- query_attribute(attribute_name)
38
- end
34
+ alias :attribute? :query_attribute
35
+ private :attribute?
39
36
  end
40
37
  end
41
38
  end
@@ -7,16 +7,14 @@ module ActiveRecord
7
7
 
8
8
  module ClassMethods # :nodoc:
9
9
  private
10
- def define_method_attribute(name)
10
+ def define_method_attribute(name, owner:)
11
11
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
- generated_attribute_methods, name
12
+ owner, name
13
13
  ) do |temp_method_name, attr_name_expr|
14
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
15
- def #{temp_method_name}
16
- name = #{attr_name_expr}
17
- _read_attribute(name) { |n| missing_attribute(n, caller) }
18
- end
19
- RUBY
14
+ owner <<
15
+ "def #{temp_method_name}" <<
16
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
+ "end"
20
18
  end
21
19
  end
22
20
  end
@@ -29,14 +27,13 @@ module ActiveRecord
29
27
  name = self.class.attribute_aliases[name] || name
30
28
 
31
29
  name = @primary_key if name == "id" && @primary_key
32
- _read_attribute(name, &block)
30
+ @attributes.fetch_value(name, &block)
33
31
  end
34
32
 
35
33
  # This method exists to avoid the expensive primary_key check internally, without
36
34
  # breaking compatibility with the read_attribute API
37
35
  def _read_attribute(attr_name, &block) # :nodoc
38
- sync_with_transaction_state if @transaction_state&.finalized?
39
- @attributes.fetch_value(attr_name.to_s, &block)
36
+ @attributes.fetch_value(attr_name, &block)
40
37
  end
41
38
 
42
39
  alias :attribute :_read_attribute
@@ -69,12 +69,12 @@ module ActiveRecord
69
69
  Coders::YAMLColumn.new(attr_name, class_name_or_coder)
70
70
  end
71
71
 
72
- decorate_attribute_type(attr_name, :serialize) do |type|
73
- if type_incompatible_with_serialize?(type, class_name_or_coder)
74
- raise ColumnNotSerializableError.new(attr_name, type)
72
+ decorate_attribute_type(attr_name.to_s) do |cast_type|
73
+ if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
74
+ raise ColumnNotSerializableError.new(attr_name, cast_type)
75
75
  end
76
76
 
77
- Type::Serialized.new(type, coder)
77
+ Type::Serialized.new(cast_type, coder)
78
78
  end
79
79
  end
80
80
 
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+
3
5
  module ActiveRecord
4
6
  module AttributeMethods
5
7
  module TimeZoneConversion
6
8
  class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
9
+ def self.new(subtype)
10
+ self === subtype ? subtype : super
11
+ end
12
+
7
13
  def deserialize(value)
8
14
  convert_time_to_time_zone(super)
9
15
  end
@@ -62,21 +68,14 @@ module ActiveRecord
62
68
  end
63
69
 
64
70
  module ClassMethods # :nodoc:
65
- private
66
- def inherited(subclass)
67
- super
68
- # We need to apply this decorator here, rather than on module inclusion. The closure
69
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
70
- # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
71
- # `skip_time_zone_conversion_for_attributes` would not be picked up.
72
- subclass.class_eval do
73
- matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
74
- decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
75
- TimeZoneConverter.new(type)
76
- end
77
- end
71
+ def define_attribute(name, cast_type, **)
72
+ if create_time_zone_conversion_attribute?(name, cast_type)
73
+ cast_type = TimeZoneConverter.new(cast_type)
78
74
  end
75
+ super
76
+ end
79
77
 
78
+ private
80
79
  def create_time_zone_conversion_attribute?(name, cast_type)
81
80
  enabled_for_column = time_zone_aware_attributes &&
82
81
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -11,16 +11,14 @@ module ActiveRecord
11
11
 
12
12
  module ClassMethods # :nodoc:
13
13
  private
14
- def define_method_attribute=(name)
14
+ def define_method_attribute=(name, owner:)
15
15
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
16
- generated_attribute_methods, name, writer: true,
16
+ owner, name, writer: true,
17
17
  ) do |temp_method_name, attr_name_expr|
18
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
19
- def #{temp_method_name}(value)
20
- name = #{attr_name_expr}
21
- _write_attribute(name, value)
22
- end
23
- RUBY
18
+ owner <<
19
+ "def #{temp_method_name}(value)" <<
20
+ " _write_attribute(#{attr_name_expr}, value)" <<
21
+ "end"
24
22
  end
25
23
  end
26
24
  end
@@ -33,27 +31,21 @@ module ActiveRecord
33
31
  name = self.class.attribute_aliases[name] || name
34
32
 
35
33
  name = @primary_key if name == "id" && @primary_key
36
- _write_attribute(name, value)
34
+ @attributes.write_from_user(name, value)
37
35
  end
38
36
 
39
37
  # This method exists to avoid the expensive primary_key check internally, without
40
38
  # breaking compatibility with the write_attribute API
41
39
  def _write_attribute(attr_name, value) # :nodoc:
42
- sync_with_transaction_state if @transaction_state&.finalized?
43
- @attributes.write_from_user(attr_name.to_s, value)
44
- value
40
+ @attributes.write_from_user(attr_name, value)
45
41
  end
46
42
 
43
+ alias :attribute= :_write_attribute
44
+ private :attribute=
45
+
47
46
  private
48
47
  def write_attribute_without_type_cast(attr_name, value)
49
- sync_with_transaction_state if @transaction_state&.finalized?
50
- @attributes.write_cast_value(attr_name.to_s, value)
51
- value
52
- end
53
-
54
- # Dispatch target for <tt>*=</tt> attribute methods.
55
- def attribute=(attribute_name, value)
56
- _write_attribute(attribute_name, value)
48
+ @attributes.write_cast_value(attr_name, value)
57
49
  end
58
50
  end
59
51
  end