どうも、ばばです。
destroyするときにバリデーションを走らせたい
たとえば、『destroy出来る条件』みたいなのがあって、その条件をパスしないと消せない、的な感じにしたいとする。
あり得るとしたら、『TwitterとGitHubの両方の認証手段がある(Qiitaみたいにね)んだけど、両方消しちゃったらログインする方法が無くなっちゃうから、両方とも消すことは出来ない』みたいな事がしたかったとする。
方法としては、
-
destroy
アクションの中で弾く - バリデーションを書く
の2つの方法がぱっと思いつくと思う。ただ、バリデーションの方はちょっと特殊なことをやらなきゃいけないし、アクションの中で書くのは『えーそれモデルの仕事でしょー』感がハンパない。負けた感ある。
というわけで、今回はバリデーション側でなんとかスマートに解決させることは出来ないか、っていうのをやってみようと思う。
before_destroyを活用する
before_destroy
については、ActiveRecord::Callbacksに詳しく書いてある。
たとえば、こんな使い方をする。
class User < ActiveRecord::Base
before_destroy { |record| Post.destroy_all(user_id: record.id) }
end
いや、dependent
オプションでやれよ、っていうのは気にしないで。
また、こいつ、メソッド名を渡すことも可能だ。
class User < ActiveRecord::Base
before_destroy :delete_all_his_posts
private
def destroy_all_his_posts
self.posts.delete_all
end
end
これを利用してどうにかしてみよう。
たとえば、save
メソッドは、true
かfalse
を返してくる。true
なら保存成功、false
なら保存失敗だ。
しかし、destroyは基本的に自分自身を返してくる。これに『もし何かに引っかかってエラーになったら、false
を返す』ようにしたら、save
と同じような挙動にすることが出来るのではないか。
class LoginProvider < ActiveRecord::Base
belongs_to :user
before_destroy :user_should_have_at_least_one_login_provider
private
def user_should_have_at_least_one_login_provider
# もしユーザーのLoginProviderが1つしか無いのに、消そうとしていたら
if self.user.login_providers.length == 1
errors.add :base, '少なくとも1つ、ログイン用の認証が必要です'
return false
end
end
end
すると、
if @login_provider.destroy
redirect_to user_login_providers_path, notice: '削除しました'
else
redirect_to user_login_providers_path, notice: '削除できませんでした'
end
こんなことが出来るようになる。
もちろん、Viewで@login_provider.errors
を表示すれば、ちゃんと『少なくとも1つ、ログイン用の認証が必要です』と表示される。
ちょっと強引だけど、こんなことをしなきゃいけない時点でだいぶレール踏み抜いてる感があるので、まぁ仕方ないかなぁ。
だいたいそんな感じ( ˘ω˘)