こんにちは、TECH DRIVEのTedです。
今回はコントローラーで使っている値をモデルに渡すときに、初心者の方がつまずくポイントを抑えつつレクチャしたいと思います。
初心者や未経験の方が一歩先に踏み出す、そして同じレベルの人達と1味違うことをアピールできる内容になっているはずなのでお楽しみ下さいー
はじめに
今回もRuby on Railsを題材に使ってご紹介してきますが、Railsのgem devise を利用されている方で、次のようなことを思った方もいることでしょう。
「なんでモデルの中で current_user が使えないんだよ!」
筆者もはじめは同じような悲痛な叫びを心の中でやったものです(笑)
Ruby on Railsは色々よしなにやってくれる便利なフレームワークですが、それでもモデルの中ではcurrent_userは使えないんです。
先に答えを書いておくと、引数を使用することでこの問題は解決できます。
でも、引数を使うと言われてもピンとこない。。。という方もいらっしゃるかもしれません。
なので今回は、deviseのcurrent_userメソッド(厳密にはcurrent_〇〇)を題材に「メソッドの実行結果を上手にモデルに渡す方法」を一緒に学んでいきましょう!
*今回もRuby on Railsを題材にしてのご紹介になりますのでご了承ください。
本題
モデルに処理を書くのを怠ってコントローラに処理を書き続けると、可読性の低いかつ保守性の低いコードになっていきます。
そこでコントローラーの処理をモデルに引っ越しさせる場面です。
Userモデルのオブジェクトが親、Postモデルのオブジェクトが子の関係があるとします。
ここではよく使われるgem devise を採用していることとします。
class Post < ApplicationRecord belongs_to :user end
そしてユーザーの投稿の更新するためのコントローラーのアクションがあります。
ここで、自分の投稿(ここでは@post)以外は更新できないようにしたいとします。
deviseが提供してくれるログイン中のユーザーを取得するメソッド(currenrt_〇〇、ここではcurrent_userとします。)を使って投稿主がログイン中のユーザーであるかどうかをチェックしてこれを実現しています。
class PostController < ApplicationController def update @post = Post.find(params[:id]) if @post.user_id == current_user.id #この1行の処理をモデルに移したい return redirect_to posts_path end if @post.update(post_parameter) # 更新成功時の処理 else # 更新失敗時の処理 end end end
今回はこの「ログインユーザーか否か」を判定する処理をコントローラーからモデルに移したいと思います。
この場合、Postモデルのインスタンスメソッドに自分のユーザー自身の投稿かどうかを確認するメソッドがあると嬉しいはずなので実装してみます。
class Post < ApplicationRecord def owner? self.user_id == current_user.id end end
こうすると
@post.owner?
と使えそうです。
ですが、これでは冒頭で書いたように動かないんです。
current_userはログイン中のユーザーを呼び出すおまじないではなく、deviseで提供されているヘルパーメソッドという1つのメソッドなのです。
なのでモデルの中で呼び出すことができないのです。
しかし、コントローラーで使っていたcurrent_user、つまりログイン中のユーザーの情報をモデル内で使えないと困ります。
なので、こういう場合はowner?
メソッドにcurrent_userの実行結果を引数として渡して呼んであげましょう。
では書いてみましょう。
user = current_user
@post.owner?(user)
モデルでは以下のように修正します。
class Post < ApplicationRecord def owner?(user) # 引数をつけた self.user_id == user.id # current_userは引数名のuserに変わった end end
user
という名前で引数を受け取ることを定義しました。
(self
は省略できますが、説明のため敢えて書いてます。)
ここで呼び出し側(コントローラー側)でのowner?
メソッドの呼び方に混乱するポイントがあります。
モデルでは
owner?(user)
と定義しているので呼び出し側でも引数はモデルと同じようにuser
と書いて渡さなければいけないと思いがちになってしまうことです。
なので引数がuser
であることにこだわって
user= current_user @post.owner?(user)
とか
@post.owner?(user = current_user)
のように描きたくなってしまいます。
実はこれはどちらも問題なく動きます。ですが、ここで大事なことは引数user
の中身なのです。
今回の場合、current_user
の実行結果である、ログイン中のユーザーが引数として入っていることが大事なのです。
例えばcurrent_user == User.find(1)
の場合、以下の引数の渡し方でも問題はないでしょう。
@post.owner?(User.find(1))
このように、引数の渡し方が違っていても中身が同じであれば良いのです。
そしてメソッドを呼ぶときに呼び出し側(今回はコントローラー内)は、モデルに定義したメソッドの引数名と同じである必要はないということです。
ここの理解ができていると「自分はちゃんとわかってるよ!」とアピールできるでしょう。
さらには、意味のある引数をつけることができるようになり、可読性を意識できるコードが書けるでしょう。
実は、モデル内にメソッドを定義するときの引数名もなんでも良いのです。
例えば
def owner?(aaaa) self.user_id == aaaa.id end
でも問題ありません。(可読性が悪いという問題はありますが(笑))
大事なことなので再度になりますが、重要なことは引数で渡す、渡ってくる中身です。
まとめ
混乱する内容になってしまったので整理します。
- コントローラーからモデルに引数という形で値を渡すことができる。
- モデルに定義したメソッドの引数名と呼び出し側の引数名は同じでなくても良い
- 引数で渡す、渡ってくる値が重要
この3つを順に抑えていると実装するときや、誰かのコードを読むときの混乱が軽くなるはずです。
慣れてくると違和感なくコードの読み書きができるようになるのでぜひ挑戦してみてください!
今回ご紹介した内容は、「オブジェクト思考」について学んでみるとより理解が深まると思います。
PR
TECH DRIVE協賛企業のサークルアラウンド株式会社では、プログラマーの成長を加速させるためのトレーニングを行なっています。フロントエンド/バックエンド問わず各種バリエーションがございますので、ご興味がある方は是非以下のリンクより詳細をご覧ください。
Ruby Climbing
週1からはじめられる「Ruby」でWEB開発の基礎が習得できる塾です。現役のプログラミング講師&Rubyエンジニアがプログラミング入門からフレームワーク(Sinatra/Ruby on Rails)を使用した本格的なWEB開発の学習までをしっかりとサポートします。
個別トレーニング
短期間でぐっと成長したい方は弊社主催の個別トレーニングがおすすめです。 トレーニング内容は、受講者の方の課題/要望をお伺いした上で、フルオーダメイドで作成させていただきます。 詳細は以下のリンクよりご確認ください。(応募者多数の場合には時間を別途ご用意する予定です)。