Railsチュートリアル11章リスト11.40にて
以下のコードがあった。
def show
@user = User.find(params[:id])
redirect_to root_url and return unless @user.activated?
end
このコードの中のand return unlessの挙動が分からなかったので、調べて考えたことをまとめておく。
and returnについて
まずはand returnについて見てみよう。Railsガイドにて以下の解説と参考コードがあった。
Rails開発をやっていれば、一度は "Can only render or redirect once per action" エラーに遭遇したことがあるでしょう。いまいましいエラーですが、修正は比較的簡単です。このエラーはほとんどの場合、開発者がrenderメソッドの基本的な動作を誤って理解していることが原因です。
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
上のコードでは、@book.special?がtrueの時、if文内のrenderとその外のrenderも実行されてしまい、二重レンダリングというエラーになる。そこで、and returnを使って明示的にメソッドを抜けることで二重レンダリングを避ける。
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show" and return
end
render action: "regular_show"
end
挙動としては、renderやredirect_toの戻り値がtrueなので、andの後ろのreturnが実行されるということだ。
なお、&& returnを使うとこの場合正しく動作しない。というのも、&&は優先度が高いのでrender action: ("special_show" && return)ということになるからだろう。一方のandの優先度は超低い。and returnはこのandの優先度の低さを上手に利用している。
and return unlessについて
and returnの働きとandの優先度について書いたので、あとはそれを組み合わせるだけだ。もう一度最初のコードを見てみよう。
def show
@user = User.find(params[:id])
redirect_to root_url and return unless @user.activated?
end
andの優先順位は低いので、redirect_to root_urlが先に評価されるので、andが評価するのはredirect_to root_urlとreturnになる。()でくくってやると以下のようになる。
(redirect_to root_url) and (return)
あとは、上のカタマリがunlessの後置修飾がついているだけだ。つまり、redirect_to root_url and return unless @user.activated?は以下と同じになる。
unless @user.activated?
redirect_to root_url and return
end
おわり
and return unlessというカタマリで見てしまったので挙動が見えず分からなくなってしまったが、ひとつずつ分解して見てみるとそこまで恐ろしいものでもなかった。「巨大で恐ろしい相手でも、ひとつひとつは取るに足らないものだぞ」と人生の教訓をプログラミングが教えてくれた。