activerecord 7.0.6 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1356 -1425
  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 +18 -10
  15. data/lib/active_record/associations/collection_proxy.rb +21 -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 -7
  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 +6 -8
  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 +60 -18
  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 +496 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -113
  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 +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  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 +14 -8
  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 +9 -5
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +163 -81
  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 +142 -58
  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 +265 -112
  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 +186 -34
  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 +12 -8
  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 +27 -16
  165. data/lib/active_record/relation/query_attribute.rb +25 -1
  166. data/lib/active_record/relation/query_methods.rb +377 -69
  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 +11 -2
  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 +39 -13
  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/serialized.rb +4 -0
  196. data/lib/active_record/type/time.rb +4 -0
  197. data/lib/active_record/validations/absence.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +5 -4
  199. data/lib/active_record/validations/presence.rb +5 -28
  200. data/lib/active_record/validations/uniqueness.rb +47 -2
  201. data/lib/active_record/validations.rb +8 -4
  202. data/lib/active_record/version.rb +1 -1
  203. data/lib/active_record.rb +121 -16
  204. data/lib/arel/errors.rb +10 -0
  205. data/lib/arel/factory_methods.rb +4 -0
  206. data/lib/arel/nodes/and.rb +4 -0
  207. data/lib/arel/nodes/binary.rb +6 -1
  208. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  209. data/lib/arel/nodes/cte.rb +36 -0
  210. data/lib/arel/nodes/fragments.rb +35 -0
  211. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  212. data/lib/arel/nodes/leading_join.rb +8 -0
  213. data/lib/arel/nodes/node.rb +111 -2
  214. data/lib/arel/nodes/sql_literal.rb +6 -0
  215. data/lib/arel/nodes/table_alias.rb +4 -0
  216. data/lib/arel/nodes.rb +4 -0
  217. data/lib/arel/predications.rb +2 -0
  218. data/lib/arel/table.rb +9 -5
  219. data/lib/arel/visitors/mysql.rb +8 -1
  220. data/lib/arel/visitors/to_sql.rb +81 -17
  221. data/lib/arel/visitors/visitor.rb +2 -2
  222. data/lib/arel.rb +16 -2
  223. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  224. data/lib/rails/generators/active_record/migration.rb +3 -1
  225. data/lib/rails/generators/active_record/model/USAGE +113 -0
  226. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  227. metadata +52 -17
  228. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  229. 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
 
@@ -19,11 +19,20 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def has_column?(column_name)
22
- klass&.columns_hash.key?(column_name)
22
+ klass&.columns_hash&.key?(column_name)
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)