activerecord 6.1.7 → 7.1.5

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  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 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -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 _internal?
14
- true
15
- end
8
+ class SchemaMigration # :nodoc:
9
+ class NullSchemaMigration # :nodoc:
10
+ end
16
11
 
17
- def primary_key
18
- "version"
19
- end
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
20
24
 
21
- def table_name
22
- "#{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)
23
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
24
45
 
25
- def create_table
26
- unless connection.table_exists?(table_name)
27
- connection.create_table(table_name, id: false) do |t|
28
- t.string :version, **connection.internal_string_options_for_primary_key
29
- 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
30
50
  end
31
51
  end
52
+ end
32
53
 
33
- def drop_table
34
- connection.drop_table table_name, if_exists: true
35
- end
54
+ def drop_table
55
+ connection.drop_table table_name, if_exists: true
56
+ end
36
57
 
37
- def normalize_migration_number(number)
38
- "%.3d" % number.to_i
39
- end
58
+ def normalize_migration_number(number)
59
+ "%.3d" % number.to_i
60
+ end
40
61
 
41
- def normalized_versions
42
- all_versions.map { |v| normalize_migration_number v }
43
- end
62
+ def normalized_versions
63
+ versions.map { |v| normalize_migration_number v }
64
+ end
44
65
 
45
- def all_versions
46
- order(:version).pluck(:version)
47
- 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)
70
+
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
@@ -2,6 +2,15 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Scoping
5
+ class DefaultScope # :nodoc:
6
+ attr_reader :scope, :all_queries
7
+
8
+ def initialize(scope, all_queries = nil)
9
+ @scope = scope
10
+ @all_queries = all_queries
11
+ end
12
+ end
13
+
5
14
  module Default
6
15
  extend ActiveSupport::Concern
7
16
 
@@ -15,14 +24,22 @@ module ActiveRecord
15
24
  # Returns a scope for the model without the previously set scopes.
16
25
  #
17
26
  # class Post < ActiveRecord::Base
27
+ # belongs_to :user
28
+ #
18
29
  # def self.default_scope
19
30
  # where(published: true)
20
31
  # end
21
32
  # end
22
33
  #
34
+ # class User < ActiveRecord::Base
35
+ # has_many :posts
36
+ # end
37
+ #
23
38
  # Post.all # Fires "SELECT * FROM posts WHERE published = true"
24
39
  # Post.unscoped.all # Fires "SELECT * FROM posts"
25
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"
26
43
  #
27
44
  # This method also accepts a block. All queries inside the block will
28
45
  # not use the previously set scopes.
@@ -30,8 +47,8 @@ module ActiveRecord
30
47
  # Post.unscoped {
31
48
  # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
32
49
  # }
33
- def unscoped
34
- block_given? ? relation.scoping { yield } : relation
50
+ def unscoped(&block)
51
+ block_given? ? relation.scoping(&block) : relation
35
52
  end
36
53
 
37
54
  # Are there attributes associated with this scope?
@@ -39,8 +56,15 @@ module ActiveRecord
39
56
  super || default_scopes.any? || respond_to?(:default_scope)
40
57
  end
41
58
 
42
- def before_remove_const #:nodoc:
43
- self.current_scope = nil
59
+ # Checks if the model has any default scopes. If all_queries
60
+ # is set to true, the method will check if there are any
61
+ # default_scopes for the model where +all_queries+ is true.
62
+ def default_scopes?(all_queries: false)
63
+ if all_queries
64
+ self.default_scopes.any?(&:all_queries)
65
+ else
66
+ self.default_scopes.any?
67
+ end
44
68
  end
45
69
 
46
70
  private
@@ -51,14 +75,30 @@ module ActiveRecord
51
75
  # default_scope { where(published: true) }
52
76
  # end
53
77
  #
54
- # Article.all # => SELECT * FROM articles WHERE published = true
78
+ # Article.all
79
+ # # SELECT * FROM articles WHERE published = true
55
80
  #
56
81
  # The #default_scope is also applied while creating/building a record.
57
- # It is not applied while updating a record.
82
+ # It is not applied while updating or deleting a record.
58
83
  #
59
84
  # Article.new.published # => true
60
85
  # Article.create.published # => true
61
86
  #
87
+ # To apply a #default_scope when updating or deleting a record, add
88
+ # <tt>all_queries: true</tt>:
89
+ #
90
+ # class Article < ActiveRecord::Base
91
+ # default_scope -> { where(blog_id: 1) }, all_queries: true
92
+ # end
93
+ #
94
+ # Applying a default scope to all queries will ensure that records
95
+ # are always queried by the additional conditions. Note that only
96
+ # where clauses apply, as it does not make sense to add order to
97
+ # queries that return a single object by primary key.
98
+ #
99
+ # Article.find(1).destroy
100
+ # # DELETE ... FROM `articles` where ID = 1 AND blog_id = 1;
101
+ #
62
102
  # (You can also pass any object which responds to +call+ to the
63
103
  # +default_scope+ macro, and it will be called when building the
64
104
  # default scope.)
@@ -71,7 +111,8 @@ module ActiveRecord
71
111
  # default_scope { where(rating: 'G') }
72
112
  # end
73
113
  #
74
- # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
114
+ # Article.all
115
+ # # SELECT * FROM articles WHERE published = true AND rating = 'G'
75
116
  #
76
117
  # This is also the case with inheritance and module includes where the
77
118
  # parent or module defines a #default_scope and the child or including
@@ -85,7 +126,7 @@ module ActiveRecord
85
126
  # # Should return a scope, you can call 'super' here etc.
86
127
  # end
87
128
  # end
88
- def default_scope(scope = nil, &block) # :doc:
129
+ def default_scope(scope = nil, all_queries: nil, &block) # :doc:
89
130
  scope = block if block_given?
90
131
 
91
132
  if scope.is_a?(Relation) || !scope.respond_to?(:call)
@@ -96,10 +137,12 @@ module ActiveRecord
96
137
  "self.default_scope.)"
97
138
  end
98
139
 
99
- self.default_scopes += [scope]
140
+ default_scope = DefaultScope.new(scope, all_queries)
141
+
142
+ self.default_scopes += [default_scope]
100
143
  end
101
144
 
102
- def build_default_scope(relation = relation())
145
+ def build_default_scope(relation = relation(), all_queries: nil)
103
146
  return if abstract_class?
104
147
 
105
148
  if default_scope_override.nil?
@@ -113,20 +156,34 @@ module ActiveRecord
113
156
  end
114
157
  elsif default_scopes.any?
115
158
  evaluate_default_scope do
116
- default_scopes.inject(relation) do |default_scope, scope|
117
- scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
118
- default_scope.instance_exec(&scope) || default_scope
159
+ default_scopes.inject(relation) do |combined_scope, scope_obj|
160
+ if execute_scope?(all_queries, scope_obj)
161
+ scope = scope_obj.scope.respond_to?(:to_proc) ? scope_obj.scope : scope_obj.scope.method(:call)
162
+
163
+ combined_scope.instance_exec(&scope) || combined_scope
164
+ else
165
+ combined_scope
166
+ end
119
167
  end
120
168
  end
121
169
  end
122
170
  end
123
171
 
172
+ # If all_queries is nil, only execute on select and insert queries.
173
+ #
174
+ # If all_queries is true, check if the default_scope object has
175
+ # all_queries set, then execute on all queries; select, insert, update,
176
+ # delete, and reload.
177
+ def execute_scope?(all_queries, default_scope_obj)
178
+ all_queries.nil? || all_queries && default_scope_obj.all_queries
179
+ end
180
+
124
181
  def ignore_default_scope?
125
- ScopeRegistry.value_for(:ignore_default_scope, base_class)
182
+ ScopeRegistry.ignore_default_scope(base_class)
126
183
  end
127
184
 
128
185
  def ignore_default_scope=(ignore)
129
- ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
186
+ ScopeRegistry.set_ignore_default_scope(base_class, ignore)
130
187
  end
131
188
 
132
189
  # The ignore_default_scope flag is used to prevent an infinite recursion
@@ -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
 
@@ -42,8 +42,8 @@ module ActiveRecord
42
42
  end
43
43
 
44
44
  # Returns a scope for the model with default scopes.
45
- def default_scoped(scope = relation)
46
- build_default_scope(scope) || scope
45
+ def default_scoped(scope = relation, all_queries: nil)
46
+ build_default_scope(scope, all_queries: all_queries) || scope
47
47
  end
48
48
 
49
49
  def default_extensions # :nodoc:
@@ -168,7 +168,6 @@ module ActiveRecord
168
168
  "an instance method with the same name."
169
169
  end
170
170
 
171
- valid_scope_name?(name)
172
171
  extension = Module.new(&block) if block
173
172
 
174
173
  if body.respond_to?(:to_proc)
@@ -184,7 +183,7 @@ module ActiveRecord
184
183
  scope
185
184
  end
186
185
  end
187
- singleton_class.send(:ruby2_keywords, name) if respond_to?(:ruby2_keywords, true)
186
+ singleton_class.send(:ruby2_keywords, name)
188
187
 
189
188
  generate_relation_method(name)
190
189
  end
@@ -193,13 +192,6 @@ module ActiveRecord
193
192
  def singleton_method_added(name)
194
193
  generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
195
194
  end
196
-
197
- def valid_scope_name?(name)
198
- if respond_to?(name, true) && logger
199
- logger.warn "Creating scope :#{name}. " \
200
- "Overwriting existing method #{self.name}.#{name}."
201
- end
202
- end
203
195
  end
204
196
  end
205
197
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/per_thread_registry"
3
+ require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ActiveRecord
6
6
  module Scoping
@@ -24,11 +24,23 @@ module ActiveRecord
24
24
  end
25
25
 
26
26
  def current_scope(skip_inherited_scope = false)
27
- ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
27
+ ScopeRegistry.current_scope(self, skip_inherited_scope)
28
28
  end
29
29
 
30
30
  def current_scope=(scope)
31
- ScopeRegistry.set_value_for(:current_scope, self, scope)
31
+ ScopeRegistry.set_current_scope(self, scope)
32
+ end
33
+
34
+ def global_current_scope(skip_inherited_scope = false)
35
+ ScopeRegistry.global_current_scope(self, skip_inherited_scope)
36
+ end
37
+
38
+ def global_current_scope=(scope)
39
+ ScopeRegistry.set_global_current_scope(self, scope)
40
+ end
41
+
42
+ def scope_registry
43
+ ScopeRegistry.instance
32
44
  end
33
45
  end
34
46
 
@@ -45,8 +57,8 @@ module ActiveRecord
45
57
  end
46
58
 
47
59
  # This class stores the +:current_scope+ and +:ignore_default_scope+ values
48
- # for different classes. The registry is stored as a thread local, which is
49
- # accessed through +ScopeRegistry.current+.
60
+ # for different classes. The registry is stored as either a thread or fiber
61
+ # local depending on the application configuration.
50
62
  #
51
63
  # This class allows you to store and get the scope values on different
52
64
  # classes and different types of scopes. For example, if you are attempting
@@ -54,51 +66,70 @@ module ActiveRecord
54
66
  # following code:
55
67
  #
56
68
  # registry = ActiveRecord::Scoping::ScopeRegistry
57
- # registry.set_value_for(:current_scope, Board, some_new_scope)
69
+ # registry.set_current_scope(Board, some_new_scope)
58
70
  #
59
71
  # Now when you run:
60
72
  #
61
- # registry.value_for(:current_scope, Board)
62
- #
63
- # You will obtain whatever was defined in +some_new_scope+. The #value_for
64
- # and #set_value_for methods are delegated to the current ScopeRegistry
65
- # object, so the above example code can also be called as:
73
+ # registry.current_scope(Board)
66
74
  #
67
- # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
68
- # Board, some_new_scope)
75
+ # You will obtain whatever was defined in +some_new_scope+.
69
76
  class ScopeRegistry # :nodoc:
70
- extend ActiveSupport::PerThreadRegistry
77
+ class << self
78
+ delegate :current_scope, :set_current_scope, :ignore_default_scope, :set_ignore_default_scope,
79
+ :global_current_scope, :set_global_current_scope, to: :instance
71
80
 
72
- VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
81
+ def instance
82
+ ActiveSupport::IsolatedExecutionState[:active_record_scope_registry] ||= new
83
+ end
84
+ end
73
85
 
74
86
  def initialize
75
- @registry = Hash.new { |hash, key| hash[key] = {} }
87
+ @current_scope = {}
88
+ @ignore_default_scope = {}
89
+ @global_current_scope = {}
76
90
  end
77
91
 
78
- # Obtains the value for a given +scope_type+ and +model+.
79
- def value_for(scope_type, model, skip_inherited_scope = false)
80
- raise_invalid_scope_type!(scope_type)
81
- return @registry[scope_type][model.name] if skip_inherited_scope
82
- klass = model
83
- base = model.base_class
84
- while klass <= base
85
- value = @registry[scope_type][klass.name]
86
- return value if value
87
- klass = klass.superclass
88
- end
92
+ def current_scope(model, skip_inherited_scope = false)
93
+ value_for(@current_scope, model, skip_inherited_scope)
89
94
  end
90
95
 
91
- # Sets the +value+ for a given +scope_type+ and +model+.
92
- def set_value_for(scope_type, model, value)
93
- raise_invalid_scope_type!(scope_type)
94
- @registry[scope_type][model.name] = value
96
+ def set_current_scope(model, value)
97
+ set_value_for(@current_scope, model, value)
98
+ end
99
+
100
+ def ignore_default_scope(model, skip_inherited_scope = false)
101
+ value_for(@ignore_default_scope, model, skip_inherited_scope)
102
+ end
103
+
104
+ def set_ignore_default_scope(model, value)
105
+ set_value_for(@ignore_default_scope, model, value)
106
+ end
107
+
108
+ def global_current_scope(model, skip_inherited_scope = false)
109
+ value_for(@global_current_scope, model, skip_inherited_scope)
110
+ end
111
+
112
+ def set_global_current_scope(model, value)
113
+ set_value_for(@global_current_scope, model, value)
95
114
  end
96
115
 
97
116
  private
98
- def raise_invalid_scope_type!(scope_type)
99
- if !VALID_SCOPE_TYPES.include?(scope_type)
100
- raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
117
+ # Obtains the value for a given +scope_type+ and +model+.
118
+ def value_for(scope_type, model, skip_inherited_scope = false)
119
+ return scope_type[model.name] if skip_inherited_scope
120
+ klass = model
121
+ base = model.base_class
122
+ while klass != base
123
+ value = scope_type[klass.name]
124
+ return value if value
125
+ klass = klass.superclass
101
126
  end
127
+ scope_type[klass.name]
128
+ end
129
+
130
+ # Sets the +value+ for a given +scope_type+ and +model+.
131
+ def set_value_for(scope_type, model, value)
132
+ scope_type[model.name] = value
102
133
  end
103
134
  end
104
135
  end
@@ -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
+ send("#{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)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecord #:nodoc:
3
+ module ActiveRecord # :nodoc:
4
4
  # = Active Record \Serialization
5
5
  module Serialization
6
6
  extend ActiveSupport::Concern
@@ -20,5 +20,10 @@ module ActiveRecord #:nodoc:
20
20
 
21
21
  super(options)
22
22
  end
23
+
24
+ private
25
+ def attribute_names_for_serialization
26
+ attribute_names
27
+ end
23
28
  end
24
29
  end
@@ -8,9 +8,9 @@ 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.
13
- mattr_accessor :signed_id_verifier_secret, instance_writer: false
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
+ class_attribute :signed_id_verifier_secret, instance_writer: false
14
14
  end
15
15
 
16
16
  module ClassMethods
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
  end
48
48
  end
49
49
 
50
- # Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
50
+ # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
51
51
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
52
52
  # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
53
53
  # the valid signed id can't find a record.
@@ -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
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
 
98
98
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
99
99
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
100
- # It can further more be set to expire (the default is not to expire), and scoped down with a specific purpose.
100
+ # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
101
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
102
  # record. If a purpose is set, this too must match.
103
103
  #
@@ -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