activerecord 7.0.8.1 → 7.2.2.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -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
+ with_connection { |c| c.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?
@@ -148,7 +162,7 @@ module ActiveRecord
148
162
 
149
163
  def max_updated_column_timestamp
150
164
  timestamp_attributes_for_update_in_model
151
- .filter_map { |attr| self[attr]&.to_time }
165
+ .filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
152
166
  .max
153
167
  end
154
168
 
@@ -0,0 +1,123 @@
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 RelationMethods
39
+ # Finds a record using a given +token+ for a predefined +purpose+. Returns
40
+ # +nil+ if the token is invalid or the record was not found.
41
+ def find_by_token_for(purpose, token)
42
+ raise UnknownPrimaryKey.new(self) unless model.primary_key
43
+ model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => id) }
44
+ end
45
+
46
+ # Finds a record using a given +token+ for a predefined +purpose+. Raises
47
+ # ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
48
+ # (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
49
+ # the token is valid but the record was not found.
50
+ def find_by_token_for!(purpose, token)
51
+ model.token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
52
+ (raise ActiveSupport::MessageVerifier::InvalidSignature)
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ # Defines the behavior of tokens generated for a specific +purpose+.
58
+ # A token can be generated by calling TokenFor#generate_token_for on a
59
+ # record. Later, that record can be fetched by calling #find_by_token_for
60
+ # (or #find_by_token_for!) with the same purpose and token.
61
+ #
62
+ # Tokens are signed so that they are tamper-proof. Thus they can be
63
+ # exposed to outside world as, for example, password reset tokens.
64
+ #
65
+ # By default, tokens do not expire. They can be configured to expire by
66
+ # specifying a duration via the +expires_in+ option. The duration becomes
67
+ # part of the token's signature, so changing the value of +expires_in+
68
+ # will automatically invalidate previously generated tokens.
69
+ #
70
+ # A block may also be specified. When generating a token with
71
+ # TokenFor#generate_token_for, the block will be evaluated in the context
72
+ # of the record, and its return value will be embedded in the token as
73
+ # JSON. Later, when fetching the record with #find_by_token_for, the block
74
+ # will be evaluated again in the context of the fetched record. If the two
75
+ # JSON values do not match, the token will be treated as invalid. Note
76
+ # that the value returned by the block <b>should not contain sensitive
77
+ # information</b> because it will be embedded in the token as
78
+ # <b>human-readable plaintext JSON</b>.
79
+ #
80
+ # ==== Examples
81
+ #
82
+ # class User < ActiveRecord::Base
83
+ # has_secure_password
84
+ #
85
+ # generates_token_for :password_reset, expires_in: 15.minutes do
86
+ # # Last 10 characters of password salt, which changes when password is updated:
87
+ # password_salt&.last(10)
88
+ # end
89
+ # end
90
+ #
91
+ # user = User.first
92
+ #
93
+ # token = user.generate_token_for(:password_reset)
94
+ # User.find_by_token_for(:password_reset, token) # => user
95
+ # # 16 minutes later...
96
+ # User.find_by_token_for(:password_reset, token) # => nil
97
+ #
98
+ # token = user.generate_token_for(:password_reset)
99
+ # User.find_by_token_for(:password_reset, token) # => user
100
+ # user.update!(password: "new password")
101
+ # User.find_by_token_for(:password_reset, token) # => nil
102
+ def generates_token_for(purpose, expires_in: nil, &block)
103
+ self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
104
+ end
105
+
106
+ def find_by_token_for(purpose, token) # :nodoc:
107
+ all.find_by_token_for(purpose, token)
108
+ end
109
+
110
+ def find_by_token_for!(purpose, token) # :nodoc:
111
+ all.find_by_token_for!(purpose, token)
112
+ end
113
+ end
114
+
115
+ # Generates a token for a predefined +purpose+.
116
+ #
117
+ # Use ClassMethods#generates_token_for to define a token purpose and
118
+ # behavior.
119
+ def generate_token_for(purpose)
120
+ self.class.token_definitions.fetch(purpose).generate_token(self)
121
+ end
122
+ end
123
+ 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)
@@ -55,11 +64,7 @@ module ActiveRecord
55
64
  end
56
65
 
57
66
  def has_defer_touch_attrs?
58
- defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
59
- end
60
-
61
- def belongs_to_touch_method
62
- :touch_later
67
+ @_defer_touch_attrs.present?
63
68
  end
64
69
  end
65
70
  end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/digest"
4
+
5
+ module ActiveRecord
6
+ # Class specifies the interface to interact with the current transaction state.
7
+ #
8
+ # It can either map to an actual transaction/savepoint, or represent the
9
+ # absence of a transaction.
10
+ #
11
+ # == State
12
+ #
13
+ # We say that a transaction is _finalized_ when it wraps a real transaction
14
+ # that has been either committed or rolled back.
15
+ #
16
+ # A transaction is _open_ if it wraps a real transaction that is not finalized.
17
+ #
18
+ # On the other hand, a transaction is _closed_ when it is not open. That is,
19
+ # when it represents absence of transaction, or it wraps a real but finalized
20
+ # one.
21
+ #
22
+ # You can check whether a transaction is open or closed with the +open?+ and
23
+ # +closed?+ predicates:
24
+ #
25
+ # if Article.current_transaction.open?
26
+ # # We are inside a real and not finalized transaction.
27
+ # end
28
+ #
29
+ # Closed transactions are `blank?` too.
30
+ #
31
+ # == Callbacks
32
+ #
33
+ # After updating the database state, you may sometimes need to perform some extra work, or reflect these
34
+ # changes in a remote system like clearing or updating a cache:
35
+ #
36
+ # def publish_article(article)
37
+ # article.update!(published: true)
38
+ # NotificationService.article_published(article)
39
+ # end
40
+ #
41
+ # The above code works but has one important flaw, which is that it no longer works properly if called inside
42
+ # a transaction, as it will interact with the remote system before the changes are persisted:
43
+ #
44
+ # Article.transaction do
45
+ # article = create_article(article)
46
+ # publish_article(article)
47
+ # end
48
+ #
49
+ # The callbacks offered by ActiveRecord::Transaction allow to rewriting this method in a way that is compatible
50
+ # with transactions:
51
+ #
52
+ # def publish_article(article)
53
+ # article.update!(published: true)
54
+ # Article.current_transaction.after_commit do
55
+ # NotificationService.article_published(article)
56
+ # end
57
+ # end
58
+ #
59
+ # In the above example, if +publish_article+ is called inside a transaction, the callback will be invoked
60
+ # after the transaction is successfully committed, and if called outside a transaction, the callback will be invoked
61
+ # immediately.
62
+ #
63
+ # == Caveats
64
+ #
65
+ # When using after_commit callbacks, it is important to note that if the callback raises an error, the transaction
66
+ # won't be rolled back as it was already committed. Relying solely on these to synchronize state between multiple
67
+ # systems may lead to consistency issues.
68
+ class Transaction
69
+ def initialize(internal_transaction) # :nodoc:
70
+ @internal_transaction = internal_transaction
71
+ @uuid = nil
72
+ end
73
+
74
+ # Registers a block to be called after the transaction is fully committed.
75
+ #
76
+ # If there is no currently open transactions, the block is called
77
+ # immediately, unless the transaction is finalized, in which case attempting
78
+ # to register the callback raises ActiveRecord::ActiveRecordError.
79
+ #
80
+ # If the transaction has a parent transaction, the callback is transferred to
81
+ # the parent when the current transaction commits, or dropped when the current transaction
82
+ # is rolled back. This operation is repeated until the outermost transaction is reached.
83
+ #
84
+ # If the callback raises an error, the transaction remains committed.
85
+ def after_commit(&block)
86
+ if @internal_transaction.nil?
87
+ yield
88
+ else
89
+ @internal_transaction.after_commit(&block)
90
+ end
91
+ end
92
+
93
+ # Registers a block to be called after the transaction is rolled back.
94
+ #
95
+ # If there is no currently open transactions, the block is not called. But
96
+ # if the transaction is finalized, attempting to register the callback
97
+ # raises ActiveRecord::ActiveRecordError.
98
+ #
99
+ # If the transaction is successfully committed but has a parent
100
+ # transaction, the callback is automatically added to the parent transaction.
101
+ #
102
+ # If the entire chain of nested transactions are all successfully committed,
103
+ # the block is never called.
104
+ #
105
+ # If the transaction is already finalized, attempting to register a callback
106
+ # will raise ActiveRecord::ActiveRecordError.
107
+ def after_rollback(&block)
108
+ @internal_transaction&.after_rollback(&block)
109
+ end
110
+
111
+ # Returns true if the transaction exists and isn't finalized yet.
112
+ def open?
113
+ !closed?
114
+ end
115
+
116
+ # Returns true if the transaction doesn't exist or is finalized.
117
+ def closed?
118
+ @internal_transaction.nil? || @internal_transaction.state.finalized?
119
+ end
120
+
121
+ alias_method :blank?, :closed?
122
+
123
+ # Returns a UUID for this transaction or +nil+ if no transaction is open.
124
+ def uuid
125
+ if @internal_transaction
126
+ @uuid ||= Digest::UUID.uuid_v4
127
+ end
128
+ end
129
+
130
+ NULL_TRANSACTION = new(nil).freeze
131
+ end
132
+ 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
@@ -185,6 +188,27 @@ module ActiveRecord
185
188
  # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from
186
189
  # within a transaction could trigger the cache to be regenerated before the database is updated.
187
190
  #
191
+ # ==== NOTE: Callbacks are deduplicated per callback by filter.
192
+ #
193
+ # Trying to define multiple callbacks with the same filter will result in a single callback being run.
194
+ #
195
+ # For example:
196
+ #
197
+ # after_commit :do_something
198
+ # after_commit :do_something # only the last one will be called
199
+ #
200
+ # This applies to all variations of <tt>after_*_commit</tt> callbacks as well.
201
+ #
202
+ # after_commit :do_something
203
+ # after_create_commit :do_something
204
+ # after_save_commit :do_something
205
+ #
206
+ # It is recommended to use the +on:+ option to specify when the callback should be run.
207
+ #
208
+ # after_commit :do_something, on: [:create, :update]
209
+ #
210
+ # This is equivalent to using +after_create_commit+ and +after_update_commit+, but will not be deduplicated.
211
+ #
188
212
  # === Caveats
189
213
  #
190
214
  # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
@@ -195,9 +219,9 @@ module ActiveRecord
195
219
  # database error will occur because the savepoint has already been
196
220
  # automatically released. The following example demonstrates the problem:
197
221
  #
198
- # Model.connection.transaction do # BEGIN
199
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
200
- # Model.connection.create_table(...) # active_record_1 now automatically released
222
+ # Model.lease_connection.transaction do # BEGIN
223
+ # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
224
+ # Model.lease_connection.create_table(...) # active_record_1 now automatically released
201
225
  # end # RELEASE SAVEPOINT active_record_1
202
226
  # # ^^^^ BOOM! database error!
203
227
  # end
@@ -206,7 +230,20 @@ module ActiveRecord
206
230
  module ClassMethods
207
231
  # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
208
232
  def transaction(**options, &block)
209
- connection.transaction(**options, &block)
233
+ with_connection do |connection|
234
+ connection.transaction(**options, &block)
235
+ end
236
+ end
237
+
238
+ # Returns a representation of the current transaction state,
239
+ # which can be a top level transaction, a savepoint, or the absence of a transaction.
240
+ #
241
+ # An object is always returned, whether or not a transaction is currently active.
242
+ # To check if a transaction was opened, use <tt>current_transaction.open?</tt>.
243
+ #
244
+ # See the ActiveRecord::Transaction documentation for detailed behavior.
245
+ def current_transaction
246
+ connection_pool.active_connection&.current_transaction&.user_transaction || Transaction::NULL_TRANSACTION
210
247
  end
211
248
 
212
249
  def before_commit(*args, &block) # :nodoc:
@@ -227,31 +264,31 @@ module ActiveRecord
227
264
  # after_commit :do_bar_baz, on: [:update, :destroy]
228
265
  #
229
266
  def after_commit(*args, &block)
230
- set_options_for_callbacks!(args)
267
+ set_options_for_callbacks!(args, prepend_option)
231
268
  set_callback(:commit, :after, *args, &block)
232
269
  end
233
270
 
234
271
  # Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
235
272
  def after_save_commit(*args, &block)
236
- set_options_for_callbacks!(args, on: [ :create, :update ])
273
+ set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option)
237
274
  set_callback(:commit, :after, *args, &block)
238
275
  end
239
276
 
240
277
  # Shortcut for <tt>after_commit :hook, on: :create</tt>.
241
278
  def after_create_commit(*args, &block)
242
- set_options_for_callbacks!(args, on: :create)
279
+ set_options_for_callbacks!(args, on: :create, **prepend_option)
243
280
  set_callback(:commit, :after, *args, &block)
244
281
  end
245
282
 
246
283
  # Shortcut for <tt>after_commit :hook, on: :update</tt>.
247
284
  def after_update_commit(*args, &block)
248
- set_options_for_callbacks!(args, on: :update)
285
+ set_options_for_callbacks!(args, on: :update, **prepend_option)
249
286
  set_callback(:commit, :after, *args, &block)
250
287
  end
251
288
 
252
289
  # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
253
290
  def after_destroy_commit(*args, &block)
254
- set_options_for_callbacks!(args, on: :destroy)
291
+ set_options_for_callbacks!(args, on: :destroy, **prepend_option)
255
292
  set_callback(:commit, :after, *args, &block)
256
293
  end
257
294
 
@@ -259,11 +296,38 @@ module ActiveRecord
259
296
  #
260
297
  # Please check the documentation of #after_commit for options.
261
298
  def after_rollback(*args, &block)
262
- set_options_for_callbacks!(args)
299
+ set_options_for_callbacks!(args, prepend_option)
263
300
  set_callback(:rollback, :after, *args, &block)
264
301
  end
265
302
 
303
+ # Similar to ActiveSupport::Callbacks::ClassMethods#set_callback, but with
304
+ # support for options available on #after_commit and #after_rollback callbacks.
305
+ def set_callback(name, *filter_list, &block)
306
+ options = filter_list.extract_options!
307
+ filter_list << options
308
+
309
+ if name.in?([:commit, :rollback]) && options[:on]
310
+ fire_on = Array(options[:on])
311
+ assert_valid_transaction_action(fire_on)
312
+ options[:if] = [
313
+ -> { transaction_include_any_action?(fire_on) },
314
+ *options[:if]
315
+ ]
316
+ end
317
+
318
+
319
+ super(name, *filter_list, &block)
320
+ end
321
+
266
322
  private
323
+ def prepend_option
324
+ if ActiveRecord.run_after_transaction_callbacks_in_order_defined
325
+ { prepend: true }
326
+ else
327
+ {}
328
+ end
329
+ end
330
+
267
331
  def set_options_for_callbacks!(args, enforced_options = {})
268
332
  options = args.extract_options!.merge!(enforced_options)
269
333
  args << options
@@ -343,18 +407,19 @@ module ActiveRecord
343
407
  # This method is available within the context of an ActiveRecord::Base
344
408
  # instance.
345
409
  def with_transaction_returning_status
346
- status = nil
347
- connection = self.class.connection
348
- ensure_finalize = !connection.transaction_open?
410
+ self.class.with_connection do |connection|
411
+ status = nil
412
+ ensure_finalize = !connection.transaction_open?
349
413
 
350
- connection.transaction do
351
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
352
- remember_transaction_record_state
414
+ connection.transaction do
415
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
416
+ remember_transaction_record_state
353
417
 
354
- status = yield
355
- raise ActiveRecord::Rollback unless status
418
+ status = yield
419
+ raise ActiveRecord::Rollback unless status
420
+ end
421
+ status
356
422
  end
357
- status
358
423
  end
359
424
 
360
425
  def trigger_transactional_callbacks? # :nodoc:
@@ -365,6 +430,13 @@ module ActiveRecord
365
430
  private
366
431
  attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
367
432
 
433
+ def init_internals
434
+ super
435
+ @_start_transaction_state = nil
436
+ @_committed_already_called = nil
437
+ @_new_record_before_last_commit = nil
438
+ end
439
+
368
440
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
369
441
  def remember_transaction_record_state
370
442
  @_start_transaction_state ||= {
@@ -406,8 +478,16 @@ module ActiveRecord
406
478
  end
407
479
  @mutations_from_database = nil
408
480
  @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])
481
+ if self.class.composite_primary_key?
482
+ if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) }
483
+ @primary_key.zip(restore_state[:id]).each do |col, val|
484
+ @attributes.write_from_user(col, val)
485
+ end
486
+ end
487
+ else
488
+ if @attributes.fetch_value(@primary_key) != restore_state[:id]
489
+ @attributes.write_from_user(@primary_key, restore_state[:id])
490
+ end
411
491
  end
412
492
  freeze if restore_state[:frozen?]
413
493
  end
@@ -431,7 +511,9 @@ module ActiveRecord
431
511
  # Add the record to the current transaction so that the #after_rollback and #after_commit
432
512
  # callbacks can be called.
433
513
  def add_to_transaction(ensure_finalize = true)
434
- self.class.connection.add_transaction_record(self, ensure_finalize)
514
+ self.class.with_connection do |connection|
515
+ connection.add_transaction_record(self, ensure_finalize)
516
+ end
435
517
  end
436
518
 
437
519
  def has_transactional_callbacks?
@@ -2,8 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Translation
5
- include ActiveModel::Translation
6
-
7
5
  # Set the lookup ancestors for ActiveModel.
8
6
  def lookup_ancestors # :nodoc:
9
7
  klass = self
@@ -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)
@@ -4,12 +4,17 @@ module ActiveRecord
4
4
  module Type
5
5
  module Internal
6
6
  module Timezone
7
+ def initialize(timezone: nil, **kwargs)
8
+ super(**kwargs)
9
+ @timezone = timezone
10
+ end
11
+
7
12
  def is_utc?
8
- ActiveRecord.default_timezone == :utc
13
+ default_timezone == :utc
9
14
  end
10
15
 
11
16
  def default_timezone
12
- ActiveRecord.default_timezone
17
+ @timezone || ActiveRecord.default_timezone
13
18
  end
14
19
  end
15
20
  end