Generate secure token only once regardless of `on: :initialize` or `on: :create`

Follow-up to #47420.

Whereas the original behavior (`on: :create`) is invoked only once
before a record is persisted, the new behavior (`on: :initialize`) is
invoked not only new record but also persisted records.

It should be invoked only once for new record consistently.
This commit is contained in:
Ryuta Kamizono 2023-09-05 17:43:41 +09:00
parent 2fbb25b771
commit 2df70ddb96
2 changed files with 19 additions and 1 deletions

View File

@ -52,7 +52,9 @@ module ActiveRecord
require "active_support/core_ext/securerandom"
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
set_callback on, on == :initialize ? :after : :before do
send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) if has_attribute?(attribute) && !send("#{attribute}?")
if new_record? && !query_attribute(attribute)
write_attribute(attribute, self.class.generate_unique_secure_token(length: length))
end
end
end

View File

@ -25,6 +25,22 @@ class SecureTokenTest < ActiveRecord::TestCase
assert_equal token, User.find(@user.id).token
end
def test_generating_token_on_initialize_happens_only_once
model = Class.new(ActiveRecord::Base) do
self.table_name = "users"
has_secure_token on: :initialize
end
token = " "
user = model.new
user.update!(token: token)
assert_equal token, user.token
assert_equal token, user.reload.token
assert_equal token, model.find(user.id).token
end
def test_generating_token_on_initialize_is_skipped_if_column_was_not_selected
model = Class.new(ActiveRecord::Base) do
self.table_name = "users"