activerecord 7.0.8 → 7.1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. data/lib/active_record/null_relation.rb +0 -63
@@ -17,13 +17,26 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  included do
20
- class_attribute :fixture_path, instance_writer: false
20
+ ##
21
+ # :singleton-method: fixture_paths
22
+ #
23
+ # Returns the ActiveRecord::FixtureSet collection
24
+
25
+ ##
26
+ # :singleton-method: fixture_paths=
27
+ #
28
+ # :call-seq:
29
+ # fixture_paths=(fixture_paths)
30
+ class_attribute :fixture_paths, instance_writer: false, default: []
21
31
  class_attribute :fixture_table_names, default: []
22
32
  class_attribute :fixture_class_names, default: {}
23
33
  class_attribute :use_transactional_tests, default: true
24
34
  class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
25
35
  class_attribute :pre_loaded_fixtures, default: false
26
36
  class_attribute :lock_threads, default: true
37
+ class_attribute :fixture_sets, default: {}
38
+
39
+ ActiveSupport.run_load_hooks(:active_record_fixtures, self)
27
40
  end
28
41
 
29
42
  module ClassMethods
@@ -39,12 +52,28 @@ module ActiveRecord
39
52
  self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
40
53
  end
41
54
 
55
+ def fixture_path # :nodoc:
56
+ ActiveRecord.deprecator.warn(<<~WARNING)
57
+ TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
58
+ If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
59
+ the first path.
60
+ WARNING
61
+ fixture_paths.first
62
+ end
63
+
64
+ def fixture_path=(path) # :nodoc:
65
+ ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
66
+ self.fixture_paths = Array(path)
67
+ end
68
+
42
69
  def fixtures(*fixture_set_names)
43
70
  if fixture_set_names.first == :all
44
- raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
45
- fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq
46
- fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
47
- fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") }
71
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
72
+ fixture_set_names = fixture_paths.flat_map do |path|
73
+ names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq
74
+ names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
75
+ names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") }
76
+ end.uniq
48
77
  else
49
78
  fixture_set_names = fixture_set_names.flatten.map(&:to_s)
50
79
  end
@@ -55,35 +84,15 @@ module ActiveRecord
55
84
 
56
85
  def setup_fixture_accessors(fixture_set_names = nil)
57
86
  fixture_set_names = Array(fixture_set_names || fixture_table_names)
58
- methods = Module.new do
87
+ unless fixture_set_names.empty?
88
+ self.fixture_sets = fixture_sets.dup
59
89
  fixture_set_names.each do |fs_name|
60
- fs_name = fs_name.to_s
61
- accessor_name = fs_name.tr("/", "_").to_sym
62
-
63
- define_method(accessor_name) do |*fixture_names|
64
- force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
65
- return_single_record = fixture_names.size == 1
66
- fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
67
-
68
- @fixture_cache[fs_name] ||= {}
69
-
70
- instances = fixture_names.map do |f_name|
71
- f_name = f_name.to_s if f_name.is_a?(Symbol)
72
- @fixture_cache[fs_name].delete(f_name) if force_reload
73
-
74
- if @loaded_fixtures[fs_name][f_name]
75
- @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
76
- else
77
- raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
78
- end
79
- end
80
-
81
- return_single_record ? instances.first : instances
82
- end
83
- private accessor_name
90
+ key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name
91
+ key = -key.to_s if key.is_a?(Symbol)
92
+ fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
93
+ fixture_sets[key] = fs_name
84
94
  end
85
95
  end
86
- include methods
87
96
  end
88
97
 
89
98
  # Prevents automatically wrapping each specified test in a transaction,
@@ -100,6 +109,15 @@ module ActiveRecord
100
109
  end
101
110
  end
102
111
 
112
+ def fixture_path # :nodoc:
113
+ ActiveRecord.deprecator.warn(<<~WARNING)
114
+ TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2. Use #fixture_paths instead.
115
+ If multiple fixture paths have been configured with #fixture_paths, then #fixture_path will just return
116
+ the first path.
117
+ WARNING
118
+ fixture_paths.first
119
+ end
120
+
103
121
  def run_in_transaction?
104
122
  use_transactional_tests &&
105
123
  !self.class.uses_transaction?(name)
@@ -114,7 +132,6 @@ module ActiveRecord
114
132
  @fixture_connections = []
115
133
  @@already_loaded_fixtures ||= {}
116
134
  @connection_subscriber = nil
117
- @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
118
135
  @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
119
136
 
120
137
  # Load fixtures once and begin transaction.
@@ -135,19 +152,18 @@ module ActiveRecord
135
152
 
136
153
  # When connections are established in the future, begin a transaction too
137
154
  @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
138
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
155
+ connection_name = payload[:connection_name] if payload.key?(:connection_name)
139
156
  shard = payload[:shard] if payload.key?(:shard)
140
- setup_shared_connection_pool if ActiveRecord.legacy_connection_handling
141
157
 
142
- if spec_name
158
+ if connection_name
143
159
  begin
144
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
160
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(connection_name, shard: shard)
145
161
  rescue ConnectionNotEstablished
146
162
  connection = nil
147
163
  end
148
164
 
149
165
  if connection
150
- setup_shared_connection_pool unless ActiveRecord.legacy_connection_handling
166
+ setup_shared_connection_pool
151
167
 
152
168
  if !@fixture_connections.include?(connection)
153
169
  connection.begin_transaction joinable: false, _lazy: false
@@ -183,13 +199,13 @@ module ActiveRecord
183
199
  ActiveRecord::FixtureSet.reset_cache
184
200
  end
185
201
 
186
- ActiveRecord::Base.clear_active_connections!
202
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
187
203
  end
188
204
 
189
205
  def enlist_fixture_connections
190
206
  setup_shared_connection_pool
191
207
 
192
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
208
+ ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
193
209
  end
194
210
 
195
211
  private
@@ -200,79 +216,43 @@ module ActiveRecord
200
216
  # need to share a connection pool so that the reading connection
201
217
  # can see data in the open transaction on the writing connection.
202
218
  def setup_shared_connection_pool
203
- if ActiveRecord.legacy_connection_handling
204
- writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord.writing_role]
205
-
206
- ActiveRecord::Base.connection_handlers.values.each do |handler|
207
- if handler != writing_handler
208
- handler.connection_pool_names.each do |name|
209
- writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name]
210
- return unless writing_pool_manager
211
-
212
- pool_manager = handler.send(:owner_to_pool_manager)[name]
213
- @legacy_saved_pool_configs[handler][name] ||= {}
214
- pool_manager.shard_names.each do |shard_name|
215
- writing_pool_config = writing_pool_manager.get_pool_config(nil, shard_name)
216
- pool_config = pool_manager.get_pool_config(nil, shard_name)
217
- next if pool_config == writing_pool_config
218
-
219
- @legacy_saved_pool_configs[handler][name][shard_name] = pool_config
220
- pool_manager.set_pool_config(nil, shard_name, writing_pool_config)
221
- end
222
- end
223
- end
224
- end
225
- else
226
- handler = ActiveRecord::Base.connection_handler
227
-
228
- handler.connection_pool_names.each do |name|
229
- pool_manager = handler.send(:owner_to_pool_manager)[name]
230
- pool_manager.shard_names.each do |shard_name|
231
- writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
232
- @saved_pool_configs[name][shard_name] ||= {}
233
- pool_manager.role_names.each do |role|
234
- next unless pool_config = pool_manager.get_pool_config(role, shard_name)
235
- next if pool_config == writing_pool_config
236
-
237
- @saved_pool_configs[name][shard_name][role] = pool_config
238
- pool_manager.set_pool_config(role, shard_name, writing_pool_config)
239
- end
219
+ handler = ActiveRecord::Base.connection_handler
220
+
221
+ handler.connection_pool_names.each do |name|
222
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
223
+ pool_manager.shard_names.each do |shard_name|
224
+ writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
225
+ @saved_pool_configs[name][shard_name] ||= {}
226
+ pool_manager.role_names.each do |role|
227
+ next unless pool_config = pool_manager.get_pool_config(role, shard_name)
228
+ next if pool_config == writing_pool_config
229
+
230
+ @saved_pool_configs[name][shard_name][role] = pool_config
231
+ pool_manager.set_pool_config(role, shard_name, writing_pool_config)
240
232
  end
241
233
  end
242
234
  end
243
235
  end
244
236
 
245
237
  def teardown_shared_connection_pool
246
- if ActiveRecord.legacy_connection_handling
247
- @legacy_saved_pool_configs.each_pair do |handler, names|
248
- names.each_pair do |name, shards|
249
- shards.each_pair do |shard_name, pool_config|
250
- pool_manager = handler.send(:owner_to_pool_manager)[name]
251
- pool_manager.set_pool_config(nil, shard_name, pool_config)
252
- end
253
- end
254
- end
255
- else
256
- handler = ActiveRecord::Base.connection_handler
238
+ handler = ActiveRecord::Base.connection_handler
257
239
 
258
- @saved_pool_configs.each_pair do |name, shards|
259
- pool_manager = handler.send(:owner_to_pool_manager)[name]
260
- shards.each_pair do |shard_name, roles|
261
- roles.each_pair do |role, pool_config|
262
- next unless pool_manager.get_pool_config(role, shard_name)
240
+ @saved_pool_configs.each_pair do |name, shards|
241
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
242
+ shards.each_pair do |shard_name, roles|
243
+ roles.each_pair do |role, pool_config|
244
+ next unless pool_manager.get_pool_config(role, shard_name)
263
245
 
264
- pool_manager.set_pool_config(role, shard_name, pool_config)
265
- end
246
+ pool_manager.set_pool_config(role, shard_name, pool_config)
266
247
  end
267
248
  end
268
249
  end
269
250
 
270
- @legacy_saved_pool_configs.clear
271
251
  @saved_pool_configs.clear
272
252
  end
273
253
 
274
254
  def load_fixtures(config)
275
- ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
255
+ ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
276
256
  end
277
257
 
278
258
  def instantiate_fixtures
@@ -290,5 +270,42 @@ module ActiveRecord
290
270
  def load_instances?
291
271
  use_instantiated_fixtures != :no_instances
292
272
  end
273
+
274
+ def method_missing(name, *args, **kwargs, &block)
275
+ if fs_name = fixture_sets[name.to_s]
276
+ access_fixture(fs_name, *args, **kwargs, &block)
277
+ else
278
+ super
279
+ end
280
+ end
281
+
282
+ def respond_to_missing?(name, include_private = false)
283
+ if include_private && fixture_sets.key?(name.to_s)
284
+ true
285
+ else
286
+ super
287
+ end
288
+ end
289
+
290
+ def access_fixture(fs_name, *fixture_names)
291
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
292
+ return_single_record = fixture_names.size == 1
293
+
294
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
295
+ @fixture_cache[fs_name] ||= {}
296
+
297
+ instances = fixture_names.map do |f_name|
298
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
299
+ @fixture_cache[fs_name].delete(f_name) if force_reload
300
+
301
+ if @loaded_fixtures[fs_name][f_name]
302
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
303
+ else
304
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
305
+ end
306
+ end
307
+
308
+ return_single_record ? instances.first : instances
309
+ end
293
310
  end
294
311
  end
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  #
31
31
  # ActiveRecord::Base.time_zone_aware_types = [:datetime]
32
32
  #
33
- # You can also add database specific timezone aware types. For example, for PostgreSQL:
33
+ # You can also add database-specific timezone aware types. For example, for PostgreSQL:
34
34
  #
35
35
  # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
36
36
  #
@@ -75,9 +75,17 @@ module ActiveRecord
75
75
  end
76
76
 
77
77
  def current_time_from_proper_timezone
78
- ActiveRecord.default_timezone == :utc ? Time.now.utc : Time.now
78
+ connection.default_timezone == :utc ? Time.now.utc : Time.now
79
79
  end
80
80
 
81
+ protected
82
+ def reload_schema_from_cache(recursive = true)
83
+ @timestamp_attributes_for_create_in_model = nil
84
+ @timestamp_attributes_for_update_in_model = nil
85
+ @all_timestamp_attributes_in_model = nil
86
+ super
87
+ end
88
+
81
89
  private
82
90
  def timestamp_attributes_for_create
83
91
  ["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
@@ -86,16 +94,14 @@ module ActiveRecord
86
94
  def timestamp_attributes_for_update
87
95
  ["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
88
96
  end
89
-
90
- def reload_schema_from_cache
91
- @timestamp_attributes_for_create_in_model = nil
92
- @timestamp_attributes_for_update_in_model = nil
93
- @all_timestamp_attributes_in_model = nil
94
- super
95
- end
96
97
  end
97
98
 
98
99
  private
100
+ def init_internals
101
+ super
102
+ @_touch_record = nil
103
+ end
104
+
99
105
  def _create_record
100
106
  if record_timestamps
101
107
  current_time = current_time_from_proper_timezone
@@ -109,6 +115,17 @@ module ActiveRecord
109
115
  end
110
116
 
111
117
  def _update_record
118
+ record_update_timestamps
119
+
120
+ super
121
+ end
122
+
123
+ def create_or_update(touch: true, **)
124
+ @_touch_record = touch
125
+ super
126
+ end
127
+
128
+ def record_update_timestamps
112
129
  if @_touch_record && should_record_timestamps?
113
130
  current_time = current_time_from_proper_timezone
114
131
 
@@ -118,12 +135,7 @@ module ActiveRecord
118
135
  end
119
136
  end
120
137
 
121
- super
122
- end
123
-
124
- def create_or_update(touch: true, **)
125
- @_touch_record = touch
126
- super
138
+ yield if block_given?
127
139
  end
128
140
 
129
141
  def should_record_timestamps?
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/json"
4
+
5
+ module ActiveRecord
6
+ module TokenFor
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :token_definitions, instance_accessor: false, instance_predicate: false, default: {}
11
+ class_attribute :generated_token_verifier, instance_accessor: false, instance_predicate: false
12
+ end
13
+
14
+ TokenDefinition = Struct.new(:defining_class, :purpose, :expires_in, :block) do # :nodoc:
15
+ def full_purpose
16
+ @full_purpose ||= [defining_class.name, purpose, expires_in].join("\n")
17
+ end
18
+
19
+ def message_verifier
20
+ defining_class.generated_token_verifier
21
+ end
22
+
23
+ def payload_for(model)
24
+ block ? [model.id, model.instance_eval(&block).as_json] : [model.id]
25
+ end
26
+
27
+ def generate_token(model)
28
+ message_verifier.generate(payload_for(model), expires_in: expires_in, purpose: full_purpose)
29
+ end
30
+
31
+ def resolve_token(token)
32
+ payload = message_verifier.verified(token, purpose: full_purpose)
33
+ model = yield(payload[0]) if payload
34
+ model if model && payload_for(model) == payload
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ # Defines the behavior of tokens generated for a specific +purpose+.
40
+ # A token can be generated by calling TokenFor#generate_token_for on a
41
+ # record. Later, that record can be fetched by calling #find_by_token_for
42
+ # (or #find_by_token_for!) with the same purpose and token.
43
+ #
44
+ # Tokens are signed so that they are tamper-proof. Thus they can be
45
+ # exposed to outside world as, for example, password reset tokens.
46
+ #
47
+ # By default, tokens do not expire. They can be configured to expire by
48
+ # specifying a duration via the +expires_in+ option. The duration becomes
49
+ # part of the token's signature, so changing the value of +expires_in+
50
+ # will automatically invalidate previously generated tokens.
51
+ #
52
+ # A block may also be specified. When generating a token with
53
+ # TokenFor#generate_token_for, the block will be evaluated in the context
54
+ # of the record, and its return value will be embedded in the token as
55
+ # JSON. Later, when fetching the record with #find_by_token_for, the block
56
+ # will be evaluated again in the context of the fetched record. If the two
57
+ # JSON values do not match, the token will be treated as invalid. Note
58
+ # that the value returned by the block <b>should not contain sensitive
59
+ # information</b> because it will be embedded in the token as
60
+ # <b>human-readable plaintext JSON</b>.
61
+ #
62
+ # ==== Examples
63
+ #
64
+ # class User < ActiveRecord::Base
65
+ # has_secure_password
66
+ #
67
+ # generates_token_for :password_reset, expires_in: 15.minutes do
68
+ # # Last 10 characters of password salt, which changes when password is updated:
69
+ # password_salt&.last(10)
70
+ # end
71
+ # end
72
+ #
73
+ # user = User.first
74
+ #
75
+ # token = user.generate_token_for(:password_reset)
76
+ # User.find_by_token_for(:password_reset, token) # => user
77
+ # # 16 minutes later...
78
+ # User.find_by_token_for(:password_reset, token) # => nil
79
+ #
80
+ # token = user.generate_token_for(:password_reset)
81
+ # User.find_by_token_for(:password_reset, token) # => user
82
+ # user.update!(password: "new password")
83
+ # User.find_by_token_for(:password_reset, token) # => nil
84
+ def generates_token_for(purpose, expires_in: nil, &block)
85
+ self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
86
+ end
87
+
88
+ # Finds a record using a given +token+ for a predefined +purpose+. Returns
89
+ # +nil+ if the token is invalid or the record was not found.
90
+ def find_by_token_for(purpose, token)
91
+ raise UnknownPrimaryKey.new(self) unless primary_key
92
+ token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(primary_key => id) }
93
+ end
94
+
95
+ # Finds a record using a given +token+ for a predefined +purpose+. Raises
96
+ # ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
97
+ # (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
98
+ # the token is valid but the record was not found.
99
+ def find_by_token_for!(purpose, token)
100
+ token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
101
+ (raise ActiveSupport::MessageVerifier::InvalidSignature)
102
+ end
103
+ end
104
+
105
+ # Generates a token for a predefined +purpose+.
106
+ #
107
+ # Use ClassMethods#generates_token_for to define a token purpose and
108
+ # behavior.
109
+ def generate_token_for(purpose)
110
+ self.class.token_definitions.fetch(purpose).generate_token(self)
111
+ end
112
+ end
113
+ end
@@ -24,9 +24,13 @@ module ActiveRecord
24
24
  @_new_record_before_last_commit ||= false
25
25
 
26
26
  # touch the parents as we are not calling the after_save callbacks
27
- self.class.reflect_on_all_associations(:belongs_to).each do |r|
27
+ self.class.reflect_on_all_associations.each do |r|
28
28
  if touch = r.options[:touch]
29
- ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later)
29
+ if r.macro == :belongs_to
30
+ ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch)
31
+ elsif r.macro == :has_one
32
+ ActiveRecord::Associations::Builder::HasOne.touch_record(self, r.name, touch)
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -42,6 +46,11 @@ module ActiveRecord
42
46
  end
43
47
 
44
48
  private
49
+ def init_internals
50
+ super
51
+ @_defer_touch_attrs = nil
52
+ end
53
+
45
54
  def surreptitiously_touch(attr_names)
46
55
  attr_names.each do |attr_name|
47
56
  _write_attribute(attr_name, @_touch_time)
@@ -57,9 +66,5 @@ module ActiveRecord
57
66
  def has_defer_touch_attrs?
58
67
  defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
59
68
  end
60
-
61
- def belongs_to_touch_method
62
- :touch_later
63
- end
64
69
  end
65
70
  end
@@ -13,7 +13,9 @@ module ActiveRecord
13
13
  scope: [:kind, :name]
14
14
  end
15
15
 
16
- # = Active Record Transactions
16
+ attr_accessor :_new_record_before_last_commit # :nodoc:
17
+
18
+ # = Active Record \Transactions
17
19
  #
18
20
  # \Transactions are protective blocks where SQL statements are only permanent
19
21
  # if they can all succeed as one atomic action. The classic example is a
@@ -98,7 +100,8 @@ module ActiveRecord
98
100
  # catch those in your application code.
99
101
  #
100
102
  # One exception is the ActiveRecord::Rollback exception, which will trigger
101
- # a ROLLBACK when raised, but not be re-raised by the transaction block.
103
+ # a ROLLBACK when raised, but not be re-raised by the transaction block. Any
104
+ # other exception will be re-raised.
102
105
  #
103
106
  # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
104
107
  # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
@@ -227,31 +230,31 @@ module ActiveRecord
227
230
  # after_commit :do_bar_baz, on: [:update, :destroy]
228
231
  #
229
232
  def after_commit(*args, &block)
230
- set_options_for_callbacks!(args)
233
+ set_options_for_callbacks!(args, prepend_option)
231
234
  set_callback(:commit, :after, *args, &block)
232
235
  end
233
236
 
234
237
  # Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
235
238
  def after_save_commit(*args, &block)
236
- set_options_for_callbacks!(args, on: [ :create, :update ])
239
+ set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option)
237
240
  set_callback(:commit, :after, *args, &block)
238
241
  end
239
242
 
240
243
  # Shortcut for <tt>after_commit :hook, on: :create</tt>.
241
244
  def after_create_commit(*args, &block)
242
- set_options_for_callbacks!(args, on: :create)
245
+ set_options_for_callbacks!(args, on: :create, **prepend_option)
243
246
  set_callback(:commit, :after, *args, &block)
244
247
  end
245
248
 
246
249
  # Shortcut for <tt>after_commit :hook, on: :update</tt>.
247
250
  def after_update_commit(*args, &block)
248
- set_options_for_callbacks!(args, on: :update)
251
+ set_options_for_callbacks!(args, on: :update, **prepend_option)
249
252
  set_callback(:commit, :after, *args, &block)
250
253
  end
251
254
 
252
255
  # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
253
256
  def after_destroy_commit(*args, &block)
254
- set_options_for_callbacks!(args, on: :destroy)
257
+ set_options_for_callbacks!(args, on: :destroy, **prepend_option)
255
258
  set_callback(:commit, :after, *args, &block)
256
259
  end
257
260
 
@@ -259,11 +262,19 @@ module ActiveRecord
259
262
  #
260
263
  # Please check the documentation of #after_commit for options.
261
264
  def after_rollback(*args, &block)
262
- set_options_for_callbacks!(args)
265
+ set_options_for_callbacks!(args, prepend_option)
263
266
  set_callback(:rollback, :after, *args, &block)
264
267
  end
265
268
 
266
269
  private
270
+ def prepend_option
271
+ if ActiveRecord.run_after_transaction_callbacks_in_order_defined
272
+ { prepend: true }
273
+ else
274
+ {}
275
+ end
276
+ end
277
+
267
278
  def set_options_for_callbacks!(args, enforced_options = {})
268
279
  options = args.extract_options!.merge!(enforced_options)
269
280
  args << options
@@ -365,6 +376,13 @@ module ActiveRecord
365
376
  private
366
377
  attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
367
378
 
379
+ def init_internals
380
+ super
381
+ @_start_transaction_state = nil
382
+ @_committed_already_called = nil
383
+ @_new_record_before_last_commit = nil
384
+ end
385
+
368
386
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
369
387
  def remember_transaction_record_state
370
388
  @_start_transaction_state ||= {
@@ -406,8 +424,16 @@ module ActiveRecord
406
424
  end
407
425
  @mutations_from_database = nil
408
426
  @mutations_before_last_save = nil
409
- if @attributes.fetch_value(@primary_key) != restore_state[:id]
410
- @attributes.write_from_user(@primary_key, restore_state[:id])
427
+ if self.class.composite_primary_key?
428
+ if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) }
429
+ @primary_key.zip(restore_state[:id]).each do |col, val|
430
+ @attributes.write_from_user(col, val)
431
+ end
432
+ end
433
+ else
434
+ if @attributes.fetch_value(@primary_key) != restore_state[:id]
435
+ @attributes.write_from_user(@primary_key, restore_state[:id])
436
+ end
411
437
  end
412
438
  freeze if restore_state[:frozen?]
413
439
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_model/type/registry"
4
-
5
3
  module ActiveRecord
6
4
  # :stopdoc:
7
5
  module Type
@@ -12,7 +10,6 @@ module ActiveRecord
12
10
 
13
11
  def initialize_copy(other)
14
12
  @registrations = @registrations.dup
15
- super
16
13
  end
17
14
 
18
15
  def add_modifier(options, klass, **args)
@@ -56,11 +53,7 @@ module ActiveRecord
56
53
  end
57
54
 
58
55
  def call(_registry, *args, adapter: nil, **kwargs)
59
- if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
60
- block.call(*args, **kwargs)
61
- else
62
- block.call(*args)
63
- end
56
+ block.call(*args, **kwargs)
64
57
  end
65
58
 
66
59
  def matches?(type_name, *args, **kwargs)