はじめに
スクールにて、コールバックの設定に手間取り、その際調べることで挙動の違いを理解したつもりなので、同じようなことにハマった人のためになればいいなと、健忘録として残します。
開発環境
Rails 6.1.7.4
ruby 3.0.1
目的
管理者が、最後の1人のとき、管理者が0人とならないように権限の編集を制限する。
つまり、1人もいなくなっちゃ困るので、必ず1人は権限を在籍させたい訳です。
最初に試したこと(失敗したこと)
- Qiita記事や調べてまずは下記のように設定
def admin_cannot_update
if User.where(admin: true).count == 1 && self.saved_change_to_admin == [true,false]
throw :abort
end
end
adminに設定されたユーザーがデータベースに1人だけであり、かつ対象のオブジェクトのadminカラムをtrueからfalseに変更しようとしたとき、 thow :abortで更新を中止する。と設定
内容的には合っているはずなのだが、いくら試してみても最後の1人の権限を外せてしまう。
ログを見るときちんと呼び出されているにも関わらず、ロールバックが起きない。
指定の仕方が間違っているのかと、コードを修正したりするもどうにもならなかった。
メソッドを調べる
根本的に間違っているのでは? と使用しているメソッドを調べ直す
saved_change_to_attribute(attr_name)
前回の保存時の属性への変更を返します。属性が変更された場合、結果は元の値と保存された値を含む配列になります。 ※参考 RailsAPI
理解力の足りない自分には???となってしまったが、要するに、変更を検知、保存はされる。保存された後に何かを設定するメソッド?(素人解釈です)
このメソッドでは、どうやっても(現状の自分の理解力)編集はされてしまう!と結論に至る。
保存前に動くメソッドを探す
will_save_change_to_attribute?(attr_name, options)
次回保存するときにこの属性は変更されますか?
このメソッドは、検証時やコールバックの前に、次の呼び出しでsave特定の属性が変更されるかどうかを判断するのに役立ちます。 ※参考 RailsAPI
保存しようとすることを検知するっぽいぞ!これだ!と早速使用
def admin_cannot_update
if User.where(admin: true).count == 1 && will_save_change_to_admin? && !admin
throw :abort
end
end
データベースにadminが1人だけであり、かつadminを、admin権をなくそうとしているときに更新を中止させる。
変更検知だけだと、管理者が1人になったら編集ができなくなり、!adminがないと付与もできなくなる。
結論
saved_change_to_attribute(attr_name)
- 変更がすでに保存された後に実行されるコールバック内で使用する。変更が保存された後に処理を行う(つまり変更はしてしまう)ため、変更されたら何か処理を行うという場合に使用する。
※使用用途は今すぐには思いつかない
will_save_change_to_attribute?(attr_name, options)
- 変更が保存される前に実行されるコールバック内で使用する。変更が保存される前に何らかのチェックや操作を行いたい場合に使用する。
追記
対象のオブジェクトを示すselfは、上記2つのメソッドについては、暗黙的に指定してくれており、いらないようです。