【Rails】デプロイのたびにseed処理でパスワードが更新され強制ログアウトされていた

問題の概要

先日、開発・検証環境でデプロイするたびにユーザーが強制的にログアウトする問題に当たりました。

関連して使っていたgemは以下です。

原因を調査すると、以下のことがわかりました。

  • この現象が起きるユーザーはseedで作成したユーザーであること
  • デプロイ時にdb:seed_doを実行している際に起きている現象であること

これを踏まえてseedファイルを確認すると、書き方に原因があったことがわかりました。

問題のあった書き方

User.seed(
  :email,
  { :email => "jon@example.com",   :password => "pass1234" },
  { :email => "emily@example.com", :password => "pass1234" }
)

seed-doのgemを素直に使うとこんなコードになりますが、素のパスワード情報は当然DB側で持っていないので、元のパスワードが何であれ(pass1234であっても)、毎回パスワードを変更する処理がかかります。
パスワード変更することでセッションが切れて、seed_do実行するごとにログアウトされていました。

パスワードも常にリセットしたい場合はこれでもいいのですが、私の場合はログアウトされないようにしたかったのでログアウトされないような対応をすることにしました。

対応策

1. find_or_create_byを使う

User.find_or_create_by!(email: "jon@example.com") do |user|
  user.password = "pass1234"
end

上記のようなコードであれば、同じemailのユーザーが存在すれば処理をスキップするので、passwordの更新はかかりません。

2. bulk_insertを使う

私が遭遇したプロダクトにはすでに activerecord-import gemがインストールされていたので、このgemを使うことにしました。

※ 前提として、Userモデルのテーブルのemailカラムにユニーク制約が設定されているとします。

users = [
  User.new(email: 'jon@example.com', password: 'pass1234'),
  User.new(email: 'emily@example.com',  password: 'pass1234'),
]

# on_duplicate_key_ignore: 一意制制約に違反する場合は処理をスキップする(パスワードを上書きしない)
User.import!(users, on_duplicate_key_ignore: true)

on_duplicate_key_ignore オプションを使えば、ユニーク制約をかけているemailを見て、重複レコードになりそうであればスキップしてパスワードを上書きしません。

注意点

もしseed実行時に間違いなく設定ファイル通りにデータをリセットしたい場合は、今回のような方法だとかえって混乱する場合がありますので、その辺りは理解した上で修正する必要があります。
(氏名レコードが別途ある場合、emailだけ見てスキップするコードだとパスワードの他にも氏名もリセットされないなど)