背景
RailsのDeviseライブラリを使用している際、Companyユーザーの情報を更新する際に関連データが誤って更新される問題が発生しました。特に、パスワードの検証が失敗しても、その他の関連モデルの変更(今回の場合はaccept_nested_attributes_forで関連付けられた CompanyBusinessDay
や CompanyBusinessHours
)が保存されてしまいます。
問題の原因
この問題の根本的な原因は、Deviseの更新処理(update_with_password
メソッド)が単一のトランザクション内で実行されていないため、パスワードの検証に失敗しても他のデータベース変更がコミットされてしまうことにあります。
解決策
解決策として、Deviseの update
アクションをオーバーライドし、全ての更新処理をカスタムトランザクションブロック内で実行します。これにより、すべての変更が正しいパスワードの検証を通過した後にのみコミットされるようになります。
実装方法
以下は、カスタムのトランザクションを用いた update
アクションのサンプルコードです。
# app/controllers/companies/registrations_controller.rb
class Companies::RegistrationsController < Devise::RegistrationsController
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
ActiveRecord::Base.transaction do
if update_resource(resource, account_update_params)
yield resource if block_given?
if is_flashing_format?
set_flash_message :notice, :updated
end
sign_in resource_name, resource, bypass: true
respond_with resource, location: after_update_path_for(resource)
else
# 更新が失敗した場合、ロールバック
raise ActiveRecord::Rollback
end
end
end
private
def update_resource(resource, params)
resource.update_with_password(params)
end
end
説明
-
update_resource
: Deviseの標準的なパスワードによる更新メソッドを呼び出しています。 - トランザクションブロック:
ActiveRecord::Base.transaction
を使用して、すべてのデータベース操作を一つのトランザクションとして管理します。これにより、何らかのステップで失敗した場合に全てのデータベース変更をロールバックすることができます。
この手法により、パスワードの検証が正しく行われ、関連データの安全な更新が保証されます。また、この方法はDeviseを使用している多くのRailsアプリケーションに適用可能です。