ミナミ(@minami_nakasato)です。
今日も出来たてホヤホヤのエラーを共有します。
経緯
作成中のチャットアプリの構成からご紹介。
まず、ユーザーやチャットグループの登録・編集・削除をするページがいくつか。
(これらには特に問題ありません)
それを除けば、残りはたった1ページ↓
上段
グループ名とグループメンバーの名前を列挙する部分
中段
グループメンバーの投稿内容を表示する部分
下段
グループメンバーが投稿するフォーム
これら3つの要素を1ページにまとめたシンプルなアプリだ。
ログイン後には当ページが表示される。
そこで、ページ上段において「グループメンバーの表示」をするにあたり、コントローラにこんなコードを書いた↓
def index
@message = Message.new
@messages = @group.messages.includes(:user)
@members = @group.users
end
そしてビューファイルの上段の部分はこう↓
.header
.header__box
- @members.each do |member|
= member.name
end
データベースには「特定のグループと紐づいたユーザー」が1人〜たくさん登録されていて、彼らの名前はeachメソッドで一人一人表示される。
こんな具合に↓
そして、link_to や redirect_to などHTTPリクエストをルーティングに届けるメソッドを使って、当たり前にページを表示することができた。
今度は、実際にメッセージを投稿してみよう↓
投稿する
同じ画面が投稿フォームも兼ねているので、コントローラのcreateアクションにはこう書いた↓
def create
@message = @group.messages.new(message_params)
if @message.save
redirect_to group_messages_path(@group)
「グループテーブルと紐づいているメッセージテーブルのカラムに、新しい投稿内容を保存する」
という、ごく普通のActive Recordメソッドだ。
そして、投稿が成功した際には
「redirect_to」
で元のページに戻る。
「redirect_to」が行う処理はHTTPリクエスト。
つまり、
HTTPメソッドとURIがルーティングに届き、仕事の命令は対応するコントローラ(messages_controller)ひいてはアクション(index)に届く。
indexアクションは定義されたインスタンス変数にしたがって行動。データベースからデータを読み取り、その結果をビューに反映。
上記のレスポンスがブラウザに届き、無事に新しい投稿内容が元の画面が表示される。
という一連の処理だ。
テキストを打ち込み投稿し、無事に期待通りの処理が行われた。
(非同期通信は未実装)
問題はここから。
エラー発生
このアプリには、「フォームに何も書き込まない状態では投稿が保存されない」バリデーションをモデルファイル内でかけている。
内容はこの通り↓
class Message < ApplicationRecord
belongs_to :group
belongs_to :user
validates :content, presence: true
end
そして上記のcreateアクション内のif文はこう続く。
def create
@message = @group.messages.new(message_params)
if @message.save
redirect_to group_messages_path(@group)
else
@messages = @group.messages.includes(:user)
render :index
end
end
投稿の保存に失敗した時は、
「render」
を使って元のページのビューファイルを呼び出す魂胆だった。
バリデーションが機能するかテストするために、何もテキストを入力せずに投稿ボタンを押してみたところ……
「undefined method `each' for nil:NilClass」
つまり
「nil(何もない)というクラスに対してeachというメソッドは定義されていませんよ」
とのお叱り。
「グループメンバーをeachで列挙しろと言われても、そもそも何もないのに何かを列挙するなんて出来ませんやん」
というわけだ。
ここでのrenderの仕事はHTTPリクエストを送ることではなく、単に「指定のビューを表示すること」だけ。
indexアクションだけでなく、ビューで表示すべきデータが代入されたインスタンス変数を、createアクション内にも定義しなければ、エラーが出るのは当然だ。
そこで
@members = @group.users
を追加して
def create
@message = @group.messages.new(message_params)
if @message.save
redirect_to group_messages_path(@group)
else
@members = @group.users
@messages = @group.messages.includes(:user)
render :index
end
end
に変更したところ、無事に解決。
なんてことないミスなのだが、デバッグツールをかますまで気付けなかった。
binding.pryよ、生まれてきてくれてありがとう。
そして当惑させてごめんね、renderメソッドちゃん。
======================================
ミナミ(@minami_nakasato)です。都内のベンチャー企業でwebデザイン/プログラミング/動画撮影や編集などをやっています。