意識低いRuby on Rails再入門5
2015/07/03
http://railstutorial.jp/chapters/sign-in-sign-out?version=4.0#top
この辺を読んだメモ。
Controllerに書かれたモデル系のロジックをHelperに切り出す
Controllerに書くのがなぜ悪いのだろう。。
と、はじめは思う。
正直『その追加する処理が1行程度なんだけど。。』とかであれば、Controllerにはモデル系のロジックを書いてはいけない!!!と、神経質にならずに書いちゃっても良いと思う。
ただ、その追加する処理があらゆるControllerで使われるとか、行数が複数行になりそうといった場合はモデルの中に押し込むか、HelperやDecoratorに押し込めないか検討する。
仮にモデルやHelperに処理Aを押し込んでおくと、その処理Aが変更になったとしても処理Aの中身を変えれば処理Aを参照してる箇所全てに変更が反映される。
処理AをいろんなControllerにベタ書きすると、処理Aを変更するときにあらゆるControllerを書き直す必要がある。
書き換え作業が面倒だし、あるControllerで書き換えをミスったためにバグが出てしまった、なんてことが想像できる。
共通する処理はどこかに集約することで変更に強くなる。
一方で、集約しすぎると複数人が1つのクラスを同時に編集することが起きる。
すると、gitなどを使ってる場合コンフリクトが発生することが想像できる。
この辺のバランスやテクニックについてまだまだ自分の中で確立できてないので学びの余地がある。
それから単純にControllerの中で処理がベタ書きされているとコードの見通しが悪くなる。
Controllerの外に追い出せるなら追い出したいし、それが難しそうであれば処理をメソッド化してベタ書きされた『どうやるか』コードを、メソッドの呼び出しに変更して『なにをしているか』に変えてゆくと見やすくなる。
前回SessionsControllerというログイン系を管理するControllerを書いた。こんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class SessionsController < ApplicationController include SessionsHelper def new end def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) sign_in(@user) redirect_to items_index_path else render 'new' end end def destroy cookies.delete(:remember_token) redirect_to 'new' end private def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token,remember_token) end end |
このsign_inをhelperに委譲する。
SessionsHelperにsign_inというメソッドを追加する
1 2 3 4 5 6 7 |
module SessionsHelper def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token,remember_token) end end |
Controllerに書いてた処理をSessionsHelperにコピペしただけ。
これでログイン処理をControllerから分離できた。
ログイン状態であるか確認したい
ログインの処理はできた。
何度か触れているが、ウェブアプリを作っているとログインが必須な画面やアクションと遭遇する。
それらを対応するたびにログイン画面を表示させられて、必要な情報を入力することをユーザ側は煩わしいと思う。
なので、セッションという手形情報を追加した。
ログイン状態が必須な画面・アクションではセッションの状態を確認する処理が必要になる、ということが分かると思う。
そうするとログイン状態が必要な何かしらのControllerの中でCookieのremember_tokenを引っ張ってきて、
1 2 3 4 5 6 |
@user = User.find_by(remember_token: cookies[:remember_token]) if @user.nil? // セッションが無効 else // セッションが有効なので○○する end |
こんな感じの照合作業を行う必要がある。
ログインが必要な様々な画面で↑のような処理を書くのは煩雑だろう。
この辺りの処理をSessionsHelperに任せられないだろうか。
def sign_in?
User.find_by(remember_token: cookies[:remember_token]).nil?
end
こんな感じ。
これで、sign_in?をController内で呼べるようになった。
ログイン状態の確認が必要なControllerにinclude SessionsHelperと書けばsign_in?を呼び出せる。
いちいちControllerにinclude SessionsHelperを書くのも面倒だ、という場合は
- ApplicationControllerでinclude
- LoginRequiredControllerみたいなモノを作って、その中にSessionsHelperをincludeしログインが必要なControllerはLoginRequiredControllerを継承するようにする
などなど、楽にするやり方はいろいろあると思うのでやってみるとよい。
メモ化を使ってログイン状態の確認を少し効率化する
先ほどSessionsHelperに追加したsign_in?メソッドは毎回find_byを呼び出している。
特にキャッシュしていない限りは毎度DBにアクセスしている、ということだ。
- ログイン状態であるか確認する
- ログイン状態であればユーザの名前を表示する
このような2つの処理をController内で行うと、Userを探す処理を2回実行することになる。
一度取得したUserを使い回すと効率は良さそうだ。
『メモ化』という方法を使うと良いらしい。
SessionsHelperに次のようなメソッドを追加する。
1 2 3 4 |
def current_user remember_token = cookies[:remember_token] @current_user ||= User.find_by(remember_token: remember_token) end |
current_userを呼ぶと@current_userの値が返ってくる。
@current_userはSessionsHelperのインスタンス変数ではなく、includeしているControllerのインスタンス変数、という扱いになる。
このcurrent_userをつかってsign_in?を次のように書き換える。
1 2 3 |
def sign_in? !current_user.nil? end |
参考元だと要素代入を利用したコードになってる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
module SessionsHelper def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token,remember_token) self.current_user = user // ログインしたときにもuserを代入するようにしてる end def sign_in? !current_user.nil? end // self.current_user = XXXで呼ばれるメソッド def current_user=(user) @current_user = user end def current_user remember_token = cookies[:remember_token] @current_user ||= User.find_by(remember_token: remember_token) end end |