概要
Rails7でdevise_token_authを使用してユーザー認証機能を実装しようとしたところ
サインアップはうまく動作するがなぜかログイン時にRuntimeError (Locking a record with unpersisted changes is not supported. Use save
to persist the changes, or reload
to discard them explicitly.)というエラーが出る。
devise_token_authのissueでも同様の事例が報告されているが修正されていないため原因を調査。
発生箇所の特定
以下のようにエラーとなっている箇所をオーバライドしてpryしどのような問題が起きているか把握する。
class Api::V1::User::Auth::SessionsController < DeviseTokenAuth::SessionsController
def create
if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
q_value = get_case_insensitive_field_from_resource_params(field)
@resource = find_resource(field, q_value)
end
binding.pry
if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
valid_password = @resource.valid_password?(resource_params[:password])
if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
return render_create_error_bad_credentials
end
create_and_assign_token
sign_in(@resource, scope: :user, store: false, bypass: false)
yield @resource if block_given?
render_create_success
elsif @resource && !Devise.paranoid && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
if @resource.respond_to?(:locked_at) && @resource.locked_at
render_create_error_account_locked
else
render_create_error_not_confirmed
end
else
hash_password_in_paranoid_mode
render_create_error_bad_credentials
end
end
private
def create_and_assign_token
if @resource.respond_to?(:with_lock)
@resource.with_lock do
@token = @resource.create_token
@resource.save!
end
else
@token = @resource.create_token
@resource.save!
end
end
end
するとcreate_and_assign_tokenメソッドの@resource.with_lockで発生していることがわかった。
原因の調査
.with_lockでは対象のインスタンスが変更されているが保存済みでない場合にこのエラーがでるらしいのでpryで実際の変更内容を見てみる
@resource.changes
=> {"uuid"=>["3984f665-0550-43d8-9495-4cc3c42c7b1d", "390556e3-fde4-4d65-ab00-6e7a2e7f39e6"]}
uuidがなぜか変更されている。
uuidはUserモデルを作成する際にafter_initializeを使用して作成しているが、ログイン時にもなぜか変更されている。
今回はその原因までは調査しないが、ログイン時にすでに存在するuserモデルがinitializeされているため、すでにuuidが存在する場合はuuidを作成しないように修正することで問題が解決した。
# frozen_string_literal: true
class User < ActiveRecord::Base
after_initialize :add_uuid
# Include default devise modules. Others available are:
# :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
def add_uuid
self.uuid = SecureRandom.uuid unless uuid <=ここにunless uuidを追加
end
end