activerecord 7.0.8.7 → 7.1.5.1

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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1795 -1424
  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 +19 -13
  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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +319 -217
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +21 -8
  33. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  35. data/lib/active_record/attribute_methods/write.rb +6 -6
  36. data/lib/active_record/attribute_methods.rb +145 -21
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +59 -10
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +10 -24
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  84. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  85. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  88. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  89. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  90. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  91. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  92. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  93. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  94. data/lib/active_record/connection_adapters.rb +3 -1
  95. data/lib/active_record/connection_handling.rb +72 -95
  96. data/lib/active_record/core.rb +181 -154
  97. data/lib/active_record/counter_cache.rb +52 -27
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +15 -10
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +3 -1
  106. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  108. data/lib/active_record/encryption/config.rb +25 -1
  109. data/lib/active_record/encryption/configurable.rb +12 -19
  110. data/lib/active_record/encryption/context.rb +10 -3
  111. data/lib/active_record/encryption/contexts.rb +5 -1
  112. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  113. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  114. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  115. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  116. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  117. data/lib/active_record/encryption/key_generator.rb +12 -1
  118. data/lib/active_record/encryption/message_serializer.rb +2 -0
  119. data/lib/active_record/encryption/properties.rb +3 -3
  120. data/lib/active_record/encryption/scheme.rb +22 -21
  121. data/lib/active_record/encryption.rb +3 -0
  122. data/lib/active_record/enum.rb +112 -28
  123. data/lib/active_record/errors.rb +112 -18
  124. data/lib/active_record/explain.rb +23 -3
  125. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  126. data/lib/active_record/fixture_set/render_context.rb +2 -0
  127. data/lib/active_record/fixture_set/table_row.rb +29 -8
  128. data/lib/active_record/fixtures.rb +135 -71
  129. data/lib/active_record/future_result.rb +40 -5
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +30 -16
  132. data/lib/active_record/insert_all.rb +57 -10
  133. data/lib/active_record/integration.rb +8 -8
  134. data/lib/active_record/internal_metadata.rb +120 -30
  135. data/lib/active_record/locking/optimistic.rb +1 -1
  136. data/lib/active_record/locking/pessimistic.rb +5 -2
  137. data/lib/active_record/log_subscriber.rb +29 -12
  138. data/lib/active_record/marshalling.rb +59 -0
  139. data/lib/active_record/message_pack.rb +124 -0
  140. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  141. data/lib/active_record/middleware/database_selector.rb +6 -8
  142. data/lib/active_record/middleware/shard_selector.rb +3 -1
  143. data/lib/active_record/migration/command_recorder.rb +104 -5
  144. data/lib/active_record/migration/compatibility.rb +145 -5
  145. data/lib/active_record/migration/default_strategy.rb +23 -0
  146. data/lib/active_record/migration/execution_strategy.rb +19 -0
  147. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  148. data/lib/active_record/migration.rb +219 -111
  149. data/lib/active_record/model_schema.rb +69 -44
  150. data/lib/active_record/nested_attributes.rb +37 -8
  151. data/lib/active_record/normalization.rb +167 -0
  152. data/lib/active_record/persistence.rb +188 -37
  153. data/lib/active_record/promise.rb +84 -0
  154. data/lib/active_record/query_cache.rb +4 -22
  155. data/lib/active_record/query_logs.rb +77 -52
  156. data/lib/active_record/query_logs_formatter.rb +41 -0
  157. data/lib/active_record/querying.rb +15 -2
  158. data/lib/active_record/railtie.rb +107 -45
  159. data/lib/active_record/railties/controller_runtime.rb +12 -6
  160. data/lib/active_record/railties/databases.rake +144 -150
  161. data/lib/active_record/railties/job_runtime.rb +23 -0
  162. data/lib/active_record/readonly_attributes.rb +32 -5
  163. data/lib/active_record/reflection.rb +181 -45
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  165. data/lib/active_record/relation/batches.rb +190 -61
  166. data/lib/active_record/relation/calculations.rb +187 -63
  167. data/lib/active_record/relation/delegation.rb +23 -9
  168. data/lib/active_record/relation/finder_methods.rb +77 -16
  169. data/lib/active_record/relation/merger.rb +2 -0
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  173. data/lib/active_record/relation/predicate_builder.rb +26 -14
  174. data/lib/active_record/relation/query_attribute.rb +2 -1
  175. data/lib/active_record/relation/query_methods.rb +371 -68
  176. data/lib/active_record/relation/spawn_methods.rb +18 -1
  177. data/lib/active_record/relation.rb +103 -37
  178. data/lib/active_record/result.rb +19 -5
  179. data/lib/active_record/runtime_registry.rb +24 -1
  180. data/lib/active_record/sanitization.rb +51 -11
  181. data/lib/active_record/schema.rb +2 -3
  182. data/lib/active_record/schema_dumper.rb +46 -7
  183. data/lib/active_record/schema_migration.rb +68 -33
  184. data/lib/active_record/scoping/default.rb +15 -5
  185. data/lib/active_record/scoping/named.rb +2 -2
  186. data/lib/active_record/scoping.rb +2 -1
  187. data/lib/active_record/secure_password.rb +60 -0
  188. data/lib/active_record/secure_token.rb +21 -3
  189. data/lib/active_record/signed_id.rb +7 -5
  190. data/lib/active_record/store.rb +8 -8
  191. data/lib/active_record/suppressor.rb +3 -1
  192. data/lib/active_record/table_metadata.rb +10 -1
  193. data/lib/active_record/tasks/database_tasks.rb +152 -108
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  197. data/lib/active_record/test_fixtures.rb +114 -96
  198. data/lib/active_record/timestamp.rb +30 -16
  199. data/lib/active_record/token_for.rb +113 -0
  200. data/lib/active_record/touch_later.rb +11 -6
  201. data/lib/active_record/transactions.rb +36 -10
  202. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  203. data/lib/active_record/type/internal/timezone.rb +7 -2
  204. data/lib/active_record/type/time.rb +4 -0
  205. data/lib/active_record/validations/absence.rb +1 -1
  206. data/lib/active_record/validations/numericality.rb +5 -4
  207. data/lib/active_record/validations/presence.rb +5 -28
  208. data/lib/active_record/validations/uniqueness.rb +47 -2
  209. data/lib/active_record/validations.rb +8 -4
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/active_record.rb +122 -17
  212. data/lib/arel/errors.rb +10 -0
  213. data/lib/arel/factory_methods.rb +4 -0
  214. data/lib/arel/nodes/binary.rb +6 -1
  215. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  216. data/lib/arel/nodes/cte.rb +36 -0
  217. data/lib/arel/nodes/fragments.rb +35 -0
  218. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  219. data/lib/arel/nodes/leading_join.rb +8 -0
  220. data/lib/arel/nodes/node.rb +111 -2
  221. data/lib/arel/nodes/sql_literal.rb +6 -0
  222. data/lib/arel/nodes/table_alias.rb +4 -0
  223. data/lib/arel/nodes.rb +4 -0
  224. data/lib/arel/predications.rb +2 -0
  225. data/lib/arel/table.rb +9 -5
  226. data/lib/arel/tree_manager.rb +5 -1
  227. data/lib/arel/visitors/mysql.rb +8 -1
  228. data/lib/arel/visitors/to_sql.rb +83 -18
  229. data/lib/arel/visitors/visitor.rb +2 -2
  230. data/lib/arel.rb +16 -2
  231. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  232. data/lib/rails/generators/active_record/migration.rb +3 -1
  233. data/lib/rails/generators/active_record/model/USAGE +113 -0
  234. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  235. metadata +46 -10
  236. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  237. data/lib/active_record/null_relation.rb +0 -63
@@ -13,17 +13,31 @@ module ActiveRecord
13
13
 
14
14
  def after_teardown # :nodoc:
15
15
  super
16
+ ensure
16
17
  teardown_fixtures
17
18
  end
18
19
 
19
20
  included do
20
- class_attribute :fixture_path, instance_writer: false
21
+ ##
22
+ # :singleton-method: fixture_paths
23
+ #
24
+ # Returns the ActiveRecord::FixtureSet collection
25
+
26
+ ##
27
+ # :singleton-method: fixture_paths=
28
+ #
29
+ # :call-seq:
30
+ # fixture_paths=(fixture_paths)
31
+ class_attribute :fixture_paths, instance_writer: false, default: []
21
32
  class_attribute :fixture_table_names, default: []
22
33
  class_attribute :fixture_class_names, default: {}
23
34
  class_attribute :use_transactional_tests, default: true
24
35
  class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
25
36
  class_attribute :pre_loaded_fixtures, default: false
26
37
  class_attribute :lock_threads, default: true
38
+ class_attribute :fixture_sets, default: {}
39
+
40
+ ActiveSupport.run_load_hooks(:active_record_fixtures, self)
27
41
  end
28
42
 
29
43
  module ClassMethods
@@ -39,12 +53,28 @@ module ActiveRecord
39
53
  self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
40
54
  end
41
55
 
56
+ def fixture_path # :nodoc:
57
+ ActiveRecord.deprecator.warn(<<~WARNING)
58
+ TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
59
+ If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
60
+ the first path.
61
+ WARNING
62
+ fixture_paths.first
63
+ end
64
+
65
+ def fixture_path=(path) # :nodoc:
66
+ ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
67
+ self.fixture_paths = Array(path)
68
+ end
69
+
42
70
  def fixtures(*fixture_set_names)
43
71
  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("/") }
72
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
73
+ fixture_set_names = fixture_paths.flat_map do |path|
74
+ names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq
75
+ names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
76
+ names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") }
77
+ end.uniq
48
78
  else
49
79
  fixture_set_names = fixture_set_names.flatten.map(&:to_s)
50
80
  end
@@ -55,35 +85,15 @@ module ActiveRecord
55
85
 
56
86
  def setup_fixture_accessors(fixture_set_names = nil)
57
87
  fixture_set_names = Array(fixture_set_names || fixture_table_names)
58
- methods = Module.new do
88
+ unless fixture_set_names.empty?
89
+ self.fixture_sets = fixture_sets.dup
59
90
  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
91
+ key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name
92
+ key = -key.to_s if key.is_a?(Symbol)
93
+ fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
94
+ fixture_sets[key] = fs_name
84
95
  end
85
96
  end
86
- include methods
87
97
  end
88
98
 
89
99
  # Prevents automatically wrapping each specified test in a transaction,
@@ -100,6 +110,15 @@ module ActiveRecord
100
110
  end
101
111
  end
102
112
 
113
+ def fixture_path # :nodoc:
114
+ ActiveRecord.deprecator.warn(<<~WARNING)
115
+ TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2. Use #fixture_paths instead.
116
+ If multiple fixture paths have been configured with #fixture_paths, then #fixture_path will just return
117
+ the first path.
118
+ WARNING
119
+ fixture_paths.first
120
+ end
121
+
103
122
  def run_in_transaction?
104
123
  use_transactional_tests &&
105
124
  !self.class.uses_transaction?(name)
@@ -114,7 +133,6 @@ module ActiveRecord
114
133
  @fixture_connections = []
115
134
  @@already_loaded_fixtures ||= {}
116
135
  @connection_subscriber = nil
117
- @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
118
136
  @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
119
137
 
120
138
  # Load fixtures once and begin transaction.
@@ -135,19 +153,18 @@ module ActiveRecord
135
153
 
136
154
  # When connections are established in the future, begin a transaction too
137
155
  @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
138
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
156
+ connection_name = payload[:connection_name] if payload.key?(:connection_name)
139
157
  shard = payload[:shard] if payload.key?(:shard)
140
- setup_shared_connection_pool if ActiveRecord.legacy_connection_handling
141
158
 
142
- if spec_name
159
+ if connection_name
143
160
  begin
144
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
161
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(connection_name, shard: shard)
145
162
  rescue ConnectionNotEstablished
146
163
  connection = nil
147
164
  end
148
165
 
149
166
  if connection
150
- setup_shared_connection_pool unless ActiveRecord.legacy_connection_handling
167
+ setup_shared_connection_pool
151
168
 
152
169
  if !@fixture_connections.include?(connection)
153
170
  connection.begin_transaction joinable: false, _lazy: false
@@ -183,13 +200,13 @@ module ActiveRecord
183
200
  ActiveRecord::FixtureSet.reset_cache
184
201
  end
185
202
 
186
- ActiveRecord::Base.clear_active_connections!
203
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
187
204
  end
188
205
 
189
206
  def enlist_fixture_connections
190
207
  setup_shared_connection_pool
191
208
 
192
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
209
+ ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
193
210
  end
194
211
 
195
212
  private
@@ -200,79 +217,43 @@ module ActiveRecord
200
217
  # need to share a connection pool so that the reading connection
201
218
  # can see data in the open transaction on the writing connection.
202
219
  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
220
+ handler = ActiveRecord::Base.connection_handler
221
+
222
+ handler.connection_pool_names.each do |name|
223
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
224
+ pool_manager.shard_names.each do |shard_name|
225
+ writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
226
+ @saved_pool_configs[name][shard_name] ||= {}
227
+ pool_manager.role_names.each do |role|
228
+ next unless pool_config = pool_manager.get_pool_config(role, shard_name)
229
+ next if pool_config == writing_pool_config
230
+
231
+ @saved_pool_configs[name][shard_name][role] = pool_config
232
+ pool_manager.set_pool_config(role, shard_name, writing_pool_config)
240
233
  end
241
234
  end
242
235
  end
243
236
  end
244
237
 
245
238
  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
239
+ handler = ActiveRecord::Base.connection_handler
257
240
 
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)
241
+ @saved_pool_configs.each_pair do |name, shards|
242
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
243
+ shards.each_pair do |shard_name, roles|
244
+ roles.each_pair do |role, pool_config|
245
+ next unless pool_manager.get_pool_config(role, shard_name)
263
246
 
264
- pool_manager.set_pool_config(role, shard_name, pool_config)
265
- end
247
+ pool_manager.set_pool_config(role, shard_name, pool_config)
266
248
  end
267
249
  end
268
250
  end
269
251
 
270
- @legacy_saved_pool_configs.clear
271
252
  @saved_pool_configs.clear
272
253
  end
273
254
 
274
255
  def load_fixtures(config)
275
- ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
256
+ ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
276
257
  end
277
258
 
278
259
  def instantiate_fixtures
@@ -290,5 +271,42 @@ module ActiveRecord
290
271
  def load_instances?
291
272
  use_instantiated_fixtures != :no_instances
292
273
  end
274
+
275
+ def method_missing(name, *args, **kwargs, &block)
276
+ if fs_name = fixture_sets[name.to_s]
277
+ access_fixture(fs_name, *args, **kwargs, &block)
278
+ else
279
+ super
280
+ end
281
+ end
282
+
283
+ def respond_to_missing?(name, include_private = false)
284
+ if include_private && fixture_sets.key?(name.to_s)
285
+ true
286
+ else
287
+ super
288
+ end
289
+ end
290
+
291
+ def access_fixture(fs_name, *fixture_names)
292
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
293
+ return_single_record = fixture_names.size == 1
294
+
295
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
296
+ @fixture_cache[fs_name] ||= {}
297
+
298
+ instances = fixture_names.map do |f_name|
299
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
300
+ @fixture_cache[fs_name].delete(f_name) if force_reload
301
+
302
+ if @loaded_fixtures[fs_name][f_name]
303
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
304
+ else
305
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
306
+ end
307
+ end
308
+
309
+ return_single_record ? instances.first : instances
310
+ end
293
311
  end
294
312
  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
  #
@@ -54,8 +54,10 @@ module ActiveRecord
54
54
 
55
55
  module ClassMethods # :nodoc:
56
56
  def touch_attributes_with_time(*names, time: nil)
57
+ names = names.map(&:to_s)
58
+ names = names.map { |name| attribute_aliases[name] || name }
57
59
  attribute_names = timestamp_attributes_for_update_in_model
58
- attribute_names |= names.map(&:to_s)
60
+ attribute_names |= names
59
61
  attribute_names.index_with(time || current_time_from_proper_timezone)
60
62
  end
61
63
 
@@ -75,9 +77,17 @@ module ActiveRecord
75
77
  end
76
78
 
77
79
  def current_time_from_proper_timezone
78
- ActiveRecord.default_timezone == :utc ? Time.now.utc : Time.now
80
+ connection.default_timezone == :utc ? Time.now.utc : Time.now
79
81
  end
80
82
 
83
+ protected
84
+ def reload_schema_from_cache(recursive = true)
85
+ @timestamp_attributes_for_create_in_model = nil
86
+ @timestamp_attributes_for_update_in_model = nil
87
+ @all_timestamp_attributes_in_model = nil
88
+ super
89
+ end
90
+
81
91
  private
82
92
  def timestamp_attributes_for_create
83
93
  ["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
@@ -86,16 +96,14 @@ module ActiveRecord
86
96
  def timestamp_attributes_for_update
87
97
  ["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
88
98
  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
99
  end
97
100
 
98
101
  private
102
+ def init_internals
103
+ super
104
+ @_touch_record = nil
105
+ end
106
+
99
107
  def _create_record
100
108
  if record_timestamps
101
109
  current_time = current_time_from_proper_timezone
@@ -109,6 +117,17 @@ module ActiveRecord
109
117
  end
110
118
 
111
119
  def _update_record
120
+ record_update_timestamps
121
+
122
+ super
123
+ end
124
+
125
+ def create_or_update(touch: true, **)
126
+ @_touch_record = touch
127
+ super
128
+ end
129
+
130
+ def record_update_timestamps
112
131
  if @_touch_record && should_record_timestamps?
113
132
  current_time = current_time_from_proper_timezone
114
133
 
@@ -118,12 +137,7 @@ module ActiveRecord
118
137
  end
119
138
  end
120
139
 
121
- super
122
- end
123
-
124
- def create_or_update(touch: true, **)
125
- @_touch_record = touch
126
- super
140
+ yield if block_given?
127
141
  end
128
142
 
129
143
  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