はじめに
ファットコントローラーを回避するためにモデルにメソッドの定義する方法を、調べても調べても理解出来ず諦めていましたが
先日個人的には超わかりやすい方法教えて頂けたため記事にしています。
簡単なものを書き出せるようになるだけでインスタンスメソッドとかクラスメソッドとかがほんのちょっと理解できるようになりました!
おまけ:クラスやインスタンスについてはこちらでまとめてみました!
メソッド化のメリット
同じ記述を何度もしなくて済む
誰もが同じ効果を発揮できる
セキュリティ問題 など様々あります
特に最後のセキュリティ問題については重要で
<% if @user == current_user %>
<%= @user.email %>
<% end %>
この様な記述があった際に
仮にタイポしてif @user = current_user
にしてしまったとします。これはただ代入しているだけの記述になってしまいエラーが出ないのに本来自分しか見られない情報が全ての人に見られてしまう致命的なセキュリティ問題の可能性が出てしまいます。
メソッド化しておくとメソッドが呼び出せなかった場合にはエラーが出るので、タイポでの問題の心配がなくなります!
手順
この手順のおかげでやっと理解ができました!
①モデルに移行したいコードをまるっとコピー
②主語(レシーバー)を決める
③メソッド名を決める
④主誤(レシーバー)のモデルに移動・コードを貼り付ける
⑤動作するようにする
解説
簡単なコードですが、冒頭にご紹介した頻出するメソッドを定義してみます
<% if @user == current_user %>
①モデルに移行したいコードをまるっとコピー
そのまんまです。まず@user == current_user
をコピーします。
(慣れてきたらコピーしたコードを消してもいいです)
##### ②主語(レシーバー)を決める
まず何をしているコードなのか日本語でいいので言語してみます
「@userがcurrent_userだったら」
(そのままですね、すみません)
主語は@userになりそうです。
先程のコードから@user以外を消すか、どこかに避難させておきます。
<% if @user %>
#退避させてるコード <% if @user == current_user %>
正直、これはコード見た感じ@userしかいないのでわかりやすいですが…
③メソッド名を決める
メソッド名は何でもいいのですが、元々用意してあるメソッド名(allとか)にかぶったり、わかりにくい名前は避ける必要があります。
先ほど言語化した「userがcurrent_userだったら」を
「userがcurrent_userと同じ(same)だったら」に変えます。
あとで引数を渡すためにsame?
の後ろにとりあえず()
も付けておきます。
(引数が必要ない場合もあります!)
<% if @user.same?() %>
#退避させてるコード <% if @user == current_user %>
?
はメソッドの結果がtrueかfalseのときに、何が結果として戻ってくるか(返り値)がわかりやすいので付けます。
④主役(レシーバー)のモデルに移動・コードを貼り付ける
ここまできたら
退避させていたコードを消して、主語(レシーバー)のモデルへ移動します。
今回の場合はUserモデルです。
まずメソッドを定義(def ~)してから、先程のコードをまるっと貼り付けます。
def @user.same?()
@user == current_user
end
ただこれでは動きません!
⑤動作するようにする
先ほど貼り付けたコードの問題点を洗い出します。
- @userはモデルで書いても通用しない
- current_userは使えない!
◯@userはモデルで書いても通用しない
私が一番理解できなかったself
がここで出てきます。
self
は自分という意味ですが、
今回メソッド化したコードは@user.same?()
のsame?()
の部分です。必ず@userにくっついています。
この@userをモデルではself
で示すことになります。
主語はselfってことです。
(難しい!わかりにくくてすみません。この説明が理解できなくても大丈夫です)
モデルを書き換えます
def self.same?()
self == current_user
end
ここでポイントですが、メソッドチェーンになっているself.~
は省略が出来ます!
(これすごいな〜と思います。コードの途中でもどこでもself.~
ってなってるなら省力できちゃうみたいです)
ただしdef self.メソッド名
のselfの省略はインスタンスメソッドの場合のみです。
クラスメソッドの場合はdef self.メソッド名
のままにしなくてはなりません。
→簡単に言うと
@user.メソッド名ならdef メソッド名
User.メソッド名ならdef self.メソッド名
にすればいいだけです。
それではもう一度モデルを書き換えます
def same?()
self == current_user
end
真ん中のself == current_user
はメソッドチェーンになっていないので省力しません!
◯current_userはモデルでは使えない問題を解消します。
これはcurrent_user
自体がdeviseが定義してくれてるメソッドなのですが、sessionの有無?を参照してログインしているかしていない判断しています。
sessionはviewとcontrollerまでしか入ってこれないためmodelは使えないメソッドらしいです。
どうするかと言うと引数の登場です!
()
で残していた部分です。
引数についても理解できていなかったのですが、私の解釈的には
modelで使えなかったりする値を引数に設定しておくことで予約?しておく。後でその値渡してあげるからね〜的な
→どうせメソッドを呼び出すところはviewかcontrollerなので呼び出す際にはちゃんと使えるようになっている
(これも説明が難しい、すみません!)
書き換えましょう!
def same?(current_user)
self == current_user
end
引数の中身を任意の文字列で大丈夫ですが変える必要もないのでこのままcurrent_userにしておきます。
重要なことは、ここのcurrent_userはdeviseのメソッドではなく現時点では中身が空っぽということです。
ラストです!
view側でメソッドを呼び出す際に必要な引数を書きに行きましょう!
<% if @user.same?(current_user) %>
完成!!
最後に
説明が思ったよりも長くなってしまいましたが、やってる事自体はとてもシンプルで、なぜこんな事理解できなかったんだろう?と思うくらい簡単です!
もっと複雑なコードになるとこんな簡単にいかないでしょうが…
最近できるようになったことなので間違ったことを言ってるようでしたらぜひ教えて下さい!
最後まで閲覧頂きありがとうございました!