Rails controllerで早期return
アプリケーションの規模が大きくなるにつれてcontrollerもfatになってくることが多いと思いますが、 リファクタする際に早期returnを使われる方も多いと思います。 controllerで早期returnをするTipsをまとめました。
Guard Clause(早期return)とは
によると下記のような説明がありました。
ガード (Guard) とは、コンピュータ・プログラミング言語において、条件式ないし条件分岐のような意味を持つもので、ある分岐で処理を続けるために真 (true) と評価されなければならない[1]式である。
つまり、条件分岐や関数の途中、早期に脱出する手段の一つです。ソースコードの可読性が向上し、性能もアップすることが期待されています。「ガード節」、「早期復帰」、「早期リターン」なんて呼んだりすると思います。
controllerで早期リターンするパターン
例に使ってるコード自体に意味はありません。あくまで早期returnの方法を説明することに関心を置いています。
古典的な方法
まずは古典的な方法です。
class UsersController < ApplicationController def show unless @user.logged_in? redirect_to sign_in_path and return end if invalid_user? redirect_to users_path and return end # 何らかの処理 end end
これは古典的な方法だと思います。
ある条件が真だった場合に、redirectさせるために return
を使っています。
メソッド and 早期リターン(パターン2)
次はメソッドに切りだしたパターンです。
class UsersController < ApplicationController def show verify_user and return # 何らかの処理 end private def verify_user unless @user.logged_in? redirect_to sign_in_path and return true end if invalid_user? redirect_to users_path and return true end end end
メソッドに処理を切り出した場合、例えば concerns
などに切り出した処理を置いていろいろな controller で使いたい場合には注意が必要です。
このパターンはメソッド内で return true
しないと新たなバグを生み出す可能性があります。
メソッドに切り出して早期リターンする場合、 true
を返さないと呼び出し元の and
の左辺が偽あつかいになるので処理が途中で中断されません。その場合、 DoubleRenderError
が発生する場合があるので注意です。
verify_user and return
という書き方が直感に反する気がしました。
メソッド or 早期リターン(パターン3)
メソッドに切り出した場合に and
ではなく or
を使うパターンもあります。
class UsersController < ApplicationController def show verify_user or return # 何らかの処理 end private def verify_user unless @user.logged_in? redirect_to sign_in_path and return end if invalid_user? redirect_to users_path and return end return true end end
この場合は、メソッドに切り出したときにメソッドの最後に return true
を追加する必要があります。
しかし、切り出した部分に関しては修正する必要がないので良いですね。
メソッド { return }(パターン4)
次はブロックをとるパターンです。
class UsersController < ApplicationController def show verify_user { return } # 何らかの処理 end private def verify_user unless @user.logged_in? redirect_to sign_in_path and yield end if invalid_user? redirect_to users_path and yield end end end
メソッド内で return
ではなく yield
を使います。ブロックで return
を実行している部分が yield
で実行されます。
これは好みが分かれる書き方かもしれませんね。
メソッド; return if performed?(パターン5)
最後は ActionController::Metal#performed? メソッドを使用するパターンです。
このメソッドは render
あるいは redirect
が実行されているかどうかを判定するメソッドです。
個人的にはこの方法が1番Rails Wayっぽい感じもするけどどうなんでしょう。
class UsersController < ApplicationController def show verify_user; return if performed? # 何らかの処理 end private def verify_user unless @user.logged_in? redirect_to sign_in_path and return end if invalid_user? redirect_to users_path and return end end end
まとめ
controller内で早期returnをする方法を見てきました。 特にメソッドに切り出す必要がない場合は古典的な方法を使うのが1番シンプルですね。