activerecord 7.0.8.7 → 7.1.0.beta1

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -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 +18 -3
  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 +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  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 +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  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 +193 -97
  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 +40 -26
  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 +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  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 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -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 +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -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 +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -1,54 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/scoping/default"
4
- require "active_record/scoping/named"
5
-
6
3
  module ActiveRecord
7
4
  # This class is used to create a table that keeps track of which migrations
8
5
  # have been applied to a given database. When a migration is run, its schema
9
- # number is inserted in to the `SchemaMigration.table_name` so it doesn't need
6
+ # number is inserted in to the schema migrations table so it doesn't need
10
7
  # to be executed the next time.
11
- class SchemaMigration < ActiveRecord::Base # :nodoc:
12
- class << self
13
- def primary_key
14
- "version"
15
- end
8
+ class SchemaMigration # :nodoc:
9
+ class NullSchemaMigration
10
+ end
11
+
12
+ attr_reader :connection, :arel_table
13
+
14
+ def initialize(connection)
15
+ @connection = connection
16
+ @arel_table = Arel::Table.new(table_name)
17
+ end
18
+
19
+ def create_version(version)
20
+ im = Arel::InsertManager.new(arel_table)
21
+ im.insert(arel_table[primary_key] => version)
22
+ connection.insert(im, "#{self.class} Create", primary_key, version)
23
+ end
16
24
 
17
- def table_name
18
- "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
25
+ def delete_version(version)
26
+ dm = Arel::DeleteManager.new(arel_table)
27
+ dm.wheres = [arel_table[primary_key].eq(version)]
28
+
29
+ connection.delete(dm, "#{self.class} Destroy")
30
+ end
31
+
32
+ def delete_all_versions
33
+ versions.each do |version|
34
+ delete_version(version)
19
35
  end
36
+ end
37
+
38
+ def primary_key
39
+ "version"
40
+ end
41
+
42
+ def table_name
43
+ "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{ActiveRecord::Base.table_name_suffix}"
44
+ end
20
45
 
21
- def create_table
22
- unless connection.table_exists?(table_name)
23
- connection.create_table(table_name, id: false) do |t|
24
- t.string :version, **connection.internal_string_options_for_primary_key
25
- end
46
+ def create_table
47
+ unless connection.table_exists?(table_name)
48
+ connection.create_table(table_name, id: false) do |t|
49
+ t.string :version, **connection.internal_string_options_for_primary_key
26
50
  end
27
51
  end
52
+ end
28
53
 
29
- def drop_table
30
- connection.drop_table table_name, if_exists: true
31
- end
54
+ def drop_table
55
+ connection.drop_table table_name, if_exists: true
56
+ end
32
57
 
33
- def normalize_migration_number(number)
34
- "%.3d" % number.to_i
35
- end
58
+ def normalize_migration_number(number)
59
+ "%.3d" % number.to_i
60
+ end
36
61
 
37
- def normalized_versions
38
- all_versions.map { |v| normalize_migration_number v }
39
- end
62
+ def normalized_versions
63
+ versions.map { |v| normalize_migration_number v }
64
+ end
40
65
 
41
- def all_versions
42
- order(:version).pluck(:version)
43
- end
66
+ def versions
67
+ sm = Arel::SelectManager.new(arel_table)
68
+ sm.project(arel_table[primary_key])
69
+ sm.order(arel_table[primary_key].asc)
44
70
 
45
- def table_exists?
46
- connection.data_source_exists?(table_name)
47
- end
71
+ connection.select_values(sm, "#{self.class} Load")
72
+ end
73
+
74
+ def integer_versions
75
+ versions.map(&:to_i)
76
+ end
77
+
78
+ def count
79
+ sm = Arel::SelectManager.new(arel_table)
80
+ sm.project(*Arel::Nodes::Count.new([Arel.star]))
81
+
82
+ connection.select_values(sm, "#{self.class} Count").first
48
83
  end
49
84
 
50
- def version
51
- super.to_i
85
+ def table_exists?
86
+ connection.data_source_exists?(table_name)
52
87
  end
53
88
  end
54
89
  end
@@ -24,14 +24,22 @@ module ActiveRecord
24
24
  # Returns a scope for the model without the previously set scopes.
25
25
  #
26
26
  # class Post < ActiveRecord::Base
27
+ # belongs_to :user
28
+ #
27
29
  # def self.default_scope
28
30
  # where(published: true)
29
31
  # end
30
32
  # end
31
33
  #
34
+ # class User < ActiveRecord::Base
35
+ # has_many :posts
36
+ # end
37
+ #
32
38
  # Post.all # Fires "SELECT * FROM posts WHERE published = true"
33
39
  # Post.unscoped.all # Fires "SELECT * FROM posts"
34
40
  # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
41
+ # User.find(1).posts # Fires "SELECT * FROM posts WHERE published = true AND posts.user_id = 1"
42
+ # User.find(1).posts.unscoped # Fires "SELECT * FROM posts"
35
43
  #
36
44
  # This method also accepts a block. All queries inside the block will
37
45
  # not use the previously set scopes.
@@ -67,7 +75,8 @@ module ActiveRecord
67
75
  # default_scope { where(published: true) }
68
76
  # end
69
77
  #
70
- # Article.all # => SELECT * FROM articles WHERE published = true
78
+ # Article.all
79
+ # # SELECT * FROM articles WHERE published = true
71
80
  #
72
81
  # The #default_scope is also applied while creating/building a record.
73
82
  # It is not applied while updating or deleting a record.
@@ -88,7 +97,7 @@ module ActiveRecord
88
97
  # queries that return a single object by primary key.
89
98
  #
90
99
  # Article.find(1).destroy
91
- # => DELETE ... FROM `articles` where ID = 1 AND blog_id = 1;
100
+ # # DELETE ... FROM `articles` where ID = 1 AND blog_id = 1;
92
101
  #
93
102
  # (You can also pass any object which responds to +call+ to the
94
103
  # +default_scope+ macro, and it will be called when building the
@@ -102,7 +111,8 @@ module ActiveRecord
102
111
  # default_scope { where(rating: 'G') }
103
112
  # end
104
113
  #
105
- # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
114
+ # Article.all
115
+ # # SELECT * FROM articles WHERE published = true AND rating = 'G'
106
116
  #
107
117
  # This is also the case with inheritance and module includes where the
108
118
  # parent or module defines a #default_scope and the child or including
@@ -162,8 +172,8 @@ module ActiveRecord
162
172
  # If all_queries is nil, only execute on select and insert queries.
163
173
  #
164
174
  # If all_queries is true, check if the default_scope object has
165
- # all_queries set, then execute on all queries; select, insert, update
166
- # and delete.
175
+ # all_queries set, then execute on all queries; select, insert, update,
176
+ # delete, and reload.
167
177
  def execute_scope?(all_queries, default_scope_obj)
168
178
  all_queries.nil? || all_queries && default_scope_obj.all_queries
169
179
  end
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  #
20
20
  # You can define a scope that applies to all finders using
21
21
  # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
22
- def all
22
+ def all(all_queries: nil)
23
23
  scope = current_scope
24
24
 
25
25
  if scope
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  relation.merge!(scope)
30
30
  end
31
31
  else
32
- default_scoped
32
+ default_scoped(all_queries: all_queries)
33
33
  end
34
34
  end
35
35
 
@@ -119,11 +119,12 @@ module ActiveRecord
119
119
  return scope_type[model.name] if skip_inherited_scope
120
120
  klass = model
121
121
  base = model.base_class
122
- while klass <= base
122
+ while klass != base
123
123
  value = scope_type[klass.name]
124
124
  return value if value
125
125
  klass = klass.superclass
126
126
  end
127
+ scope_type[klass.name]
127
128
  end
128
129
 
129
130
  # Sets the +value+ for a given +scope_type+ and +model+.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module SecurePassword
5
+ extend ActiveSupport::Concern
6
+
7
+ include ActiveModel::SecurePassword
8
+
9
+ module ClassMethods
10
+ # Given a set of attributes, finds a record using the non-password
11
+ # attributes, and then authenticates that record using the password
12
+ # attributes. Returns the record if authentication succeeds; otherwise,
13
+ # returns +nil+.
14
+ #
15
+ # Regardless of whether a record is found, +authenticate_by+ will
16
+ # cryptographically digest the given password attributes. This behavior
17
+ # helps mitigate timing-based enumeration attacks, wherein an attacker can
18
+ # determine if a passworded record exists even without knowing the
19
+ # password.
20
+ #
21
+ # Raises an ArgumentError if the set of attributes doesn't contain at
22
+ # least one password and one non-password attribute.
23
+ #
24
+ # ==== Examples
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # has_secure_password
28
+ # end
29
+ #
30
+ # User.create(name: "John Doe", email: "jdoe@example.com", password: "abc123")
31
+ #
32
+ # User.authenticate_by(email: "jdoe@example.com", password: "abc123").name # => "John Doe" (in 373.4ms)
33
+ # User.authenticate_by(email: "jdoe@example.com", password: "wrong") # => nil (in 373.9ms)
34
+ # User.authenticate_by(email: "wrong@example.com", password: "abc123") # => nil (in 373.6ms)
35
+ #
36
+ # User.authenticate_by(email: "jdoe@example.com", password: nil) # => nil (no queries executed)
37
+ # User.authenticate_by(email: "jdoe@example.com", password: "") # => nil (no queries executed)
38
+ #
39
+ # User.authenticate_by(email: "jdoe@example.com") # => ArgumentError
40
+ # User.authenticate_by(password: "abc123") # => ArgumentError
41
+ def authenticate_by(attributes)
42
+ passwords, identifiers = attributes.to_h.partition do |name, value|
43
+ !has_attribute?(name) && has_attribute?("#{name}_digest")
44
+ end.map(&:to_h)
45
+
46
+ raise ArgumentError, "One or more password arguments are required" if passwords.empty?
47
+ raise ArgumentError, "One or more finder arguments are required" if identifiers.empty?
48
+
49
+ return if passwords.any? { |name, value| value.nil? || value.empty? }
50
+
51
+ if record = find_by(identifiers)
52
+ record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size
53
+ else
54
+ new(passwords)
55
+ nil
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -24,12 +24,26 @@ module ActiveRecord
24
24
  # user.regenerate_token # => true
25
25
  # user.regenerate_auth_token # => true
26
26
  #
27
- # <tt>SecureRandom::base58</tt> is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
27
+ # +SecureRandom::base58+ is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
28
28
  #
29
29
  # Note that it's still possible to generate a race condition in the database in the same way that
30
30
  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
31
  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
- def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH)
32
+ #
33
+ # === Options
34
+ #
35
+ # [:length]
36
+ # Length of the Secure Random, with a minimum of 24 characters. It will
37
+ # default to 24.
38
+ #
39
+ # [:on]
40
+ # The callback when the value is generated. When called with <tt>on:
41
+ # :initialize</tt>, the value is generated in an
42
+ # <tt>after_initialize</tt> callback, otherwise the value will be used
43
+ # in a <tt>before_</tt> callback. When not specified, +:on+ will use the value of
44
+ # <tt>config.active_record.generate_secure_token_on</tt>, which defaults to +:initialize+
45
+ # starting in \Rails 7.1.
46
+ def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: ActiveRecord.generate_secure_token_on)
33
47
  if length < MINIMUM_TOKEN_LENGTH
34
48
  raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
35
49
  end
@@ -37,7 +51,11 @@ module ActiveRecord
37
51
  # Load securerandom only when has_secure_token is used.
38
52
  require "active_support/core_ext/securerandom"
39
53
  define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
40
- before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
54
+ set_callback on, on == :initialize ? :after : :before do
55
+ if new_record? && !query_attribute(attribute)
56
+ write_attribute(attribute, self.class.generate_unique_secure_token(length: length))
57
+ end
58
+ end
41
59
  end
42
60
 
43
61
  def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
@@ -8,8 +8,8 @@ module ActiveRecord
8
8
  included do
9
9
  ##
10
10
  # :singleton-method:
11
- # Set the secret used for the signed id verifier instance when using Active Record outside of Rails.
12
- # Within Rails, this is automatically set using the Rails application key generator.
11
+ # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
12
+ # Within \Rails, this is automatically set using the \Rails application key generator.
13
13
  class_attribute :signed_id_verifier_secret, instance_writer: false
14
14
  end
15
15
 
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
  end
67
67
 
68
68
  # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
69
- # with the class-level +signed_id_verifier_secret+, which within Rails comes from the
69
+ # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
70
70
  # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
71
71
  def signed_id_verifier
72
72
  @signed_id_verifier ||= begin
@@ -109,8 +109,10 @@ module ActiveRecord
109
109
  #
110
110
  # And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
111
111
  # created with the purpose will no longer find the record.
112
- def signed_id(expires_in: nil, purpose: nil)
113
- self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose)
112
+ def signed_id(expires_in: nil, expires_at: nil, purpose: nil)
113
+ raise ArgumentError, "Cannot get a signed_id for a new record" if new_record?
114
+
115
+ self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose)
114
116
  end
115
117
  end
116
118
  end
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/hash/indifferent_access"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Store
7
+ #
6
8
  # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
7
9
  # It's like a simple key/value store baked into your record when you don't care about being able to
8
10
  # query that store outside the context of a single record.
@@ -102,7 +104,8 @@ module ActiveRecord
102
104
 
103
105
  module ClassMethods
104
106
  def store(store_attribute, options = {})
105
- serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
107
+ coder = build_column_serializer(store_attribute, options[:coder], Object, options[:yaml])
108
+ serialize store_attribute, coder: IndifferentCoder.new(store_attribute, coder)
106
109
  store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
107
110
  end
108
111
 
@@ -160,19 +163,19 @@ module ActiveRecord
160
163
 
161
164
  define_method("saved_change_to_#{accessor_key}?") do
162
165
  return false unless saved_change_to_attribute?(store_attribute)
163
- prev_store, new_store = saved_change_to_attribute(store_attribute)
166
+ prev_store, new_store = saved_changes[store_attribute]
164
167
  prev_store&.dig(key) != new_store&.dig(key)
165
168
  end
166
169
 
167
170
  define_method("saved_change_to_#{accessor_key}") do
168
171
  return unless saved_change_to_attribute?(store_attribute)
169
- prev_store, new_store = saved_change_to_attribute(store_attribute)
172
+ prev_store, new_store = saved_changes[store_attribute]
170
173
  [prev_store&.dig(key), new_store&.dig(key)]
171
174
  end
172
175
 
173
176
  define_method("#{accessor_key}_before_last_save") do
174
177
  return unless saved_change_to_attribute?(store_attribute)
175
- prev_store, _new_store = saved_change_to_attribute(store_attribute)
178
+ prev_store, _new_store = saved_changes[store_attribute]
176
179
  prev_store&.dig(key)
177
180
  end
178
181
  end
@@ -225,10 +228,7 @@ module ActiveRecord
225
228
 
226
229
  def self.write(object, attribute, key, value)
227
230
  prepare(object, attribute)
228
- if value != read(object, attribute, key)
229
- object.public_send :"#{attribute}_will_change!"
230
- object.public_send(attribute)[key] = value
231
- end
231
+ object.public_send(attribute)[key] = value if value != read(object, attribute, key)
232
232
  end
233
233
 
234
234
  def self.prepare(object, attribute)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record \Suppressor
5
+ #
4
6
  # ActiveRecord::Suppressor prevents the receiver from being saved during
5
7
  # a given block.
6
8
  #
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
 
33
35
  class << self
34
36
  def registry # :nodoc:
35
- ActiveSupport::IsolatedExecutionState[:active_record_suppresor_registry] ||= {}
37
+ ActiveSupport::IsolatedExecutionState[:active_record_suppressor_registry] ||= {}
36
38
  end
37
39
  end
38
40
 
@@ -23,7 +23,16 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def associated_with?(table_name)
26
- klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
26
+ if reflection = klass&._reflect_on_association(table_name)
27
+ reflection
28
+ elsif ActiveRecord.allow_deprecated_singular_associations_name && reflection = klass&._reflect_on_association(table_name.singularize)
29
+ ActiveRecord.deprecator.warn(<<~MSG)
30
+ Referring to a singular association (e.g. `#{reflection.name}`) by its plural name (e.g. `#{reflection.plural_name}`) is deprecated.
31
+
32
+ To convert this deprecation warning to an error and enable more performant behavior, set config.active_record.allow_deprecated_singular_associations_name = false.
33
+ MSG
34
+ reflection
35
+ end
27
36
  end
28
37
 
29
38
  def associated_table(table_name)