HTTPリクエストとrenderの違いを知らないと痛い目に合う話

ミナミ([@minami_nakasato](https://twitter.com/MinamiNakasato))です。

今日も出来たてホヤホヤのエラーを共有します。

# 経緯
作成中のチャットアプリの構成からご紹介。

まず、ユーザーやチャットグループの登録・編集・削除をするページがいくつか。

(これらには特に問題ありません)

それを除けば、残りはたった１ページ↓

***

#### 上段
グループ名とグループメンバーの名前を列挙する部分

#### 中段
グループメンバーの投稿内容を表示する部分

#### 下段
グループメンバーが投稿するフォーム

***

これら３つの要素を１ページにまとめたシンプルなアプリだ。

ログイン後には当ページが表示される。

そこで、ページ上段において**「グループメンバーの表示」**をするにあたり、コントローラにこんなコードを書いた↓

```ruby:messages_controller.rb
def index
  @message = Message.new
  @messages = @group.messages.includes(:user)
  @members = @group.users
end
```

そしてビューファイルの上段の部分はこう↓

```ruby:index.html.haml
.header
 .header__box
  - @members.each do |member|
   = member.name
  end
```

データベースには「特定のグループと紐づいたユーザー」が１人〜たくさん登録されていて、彼らの名前はeachメソッドで一人一人表示される。

こんな具合に↓

<img width="280" alt="スクリーンショット 2019-11-11 23.01.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242355/c1c44e56-75ac-5cc1-cb5e-626f59f986fb.png">


そして、*link_to* や *redirect_to* などHTTPリクエストをルーティングに届けるメソッドを使って、当たり前にページを表示することができた。

今度は、実際にメッセージを投稿してみよう↓

# 投稿する
同じ画面が投稿フォームも兼ねているので、コントローラのcreateアクションにはこう書いた↓

```ruby:messages_controller.rb
  def create
    @message = @group.messages.new(message_params)
    if @message.save
      redirect_to group_messages_path(@group)
```

「グループテーブルと紐づいているメッセージテーブルのカラムに、新しい投稿内容を保存する」

という、ごく普通のActive Recordメソッドだ。

そして、投稿が成功した際には

<font color="red">**「redirect_to」**</font>

で元のページに戻る。

「redirect_to」が行う処理はHTTPリクエスト。

つまり、

1. HTTPメソッドとURIがルーティングに届き、仕事の命令は対応するコントローラ(messages_controller)ひいてはアクション(index)に届く。

2. indexアクションは定義されたインスタンス変数にしたがって行動。データベースからデータを読み取り、その結果をビューに反映。

3. 上記のレスポンスがブラウザに届き、無事に新しい投稿内容が元の画面が表示される。

という一連の処理だ。

テキストを打ち込み投稿し、無事に期待通りの処理が行われた。

<font color="DarkGray">*(非同期通信は未実装)*</font>

問題はここから。

# エラー発生
このアプリには、「フォームに何も書き込まない状態では投稿が保存されない」バリデーションをモデルファイル内でかけている。

内容はこの通り↓

```ruby:message.rb
class Message < ApplicationRecord
  belongs_to :group
  belongs_to :user
  validates :content, presence: true
end
```

そして上記のcreateアクション内のif文はこう続く。

```ruby:messages_controller.rb
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
```

投稿の保存に失敗した時は、

<font color="red">**「render」**</font>

を使って元のページのビューファイルを呼び出す魂胆だった。

バリデーションが機能するかテストするために、何もテキストを入力せずに投稿ボタンを押してみたところ……

<img width="564" alt="スクリーンショット 2019-11-08 22.55.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242355/f8b5dedc-c457-ebcf-4ea5-f8aa1018a3e4.png">

**「undefined method `each' for nil:NilClass」**

つまり

**「nil(何もない)というクラスに対してeachというメソッドは定義されていませんよ」**

とのお叱り。

**「グループメンバーをeachで列挙しろと言われても、そもそも何もないのに何かを列挙するなんて出来ませんやん」**

というわけだ。

ここでのrenderの仕事はHTTPリクエストを送ることではなく、単に**「指定のビューを表示すること」**だけ。

indexアクションだけでなく、<font color="blue">**ビューで表示すべきデータが代入されたインスタンス変数を、createアクション内にも定義しなければ、エラーが出るのは当然**</font>だ。

そこで

```
@members = @group.users
```

を追加して

```ruby:messages_controller.rb
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](https://twitter.com/MinamiNakasato))です。都内のベンチャー企業でwebデザイン／プログラミング／動画撮影や編集などをやっています。