【学習】投稿失敗後の画面表示。フォームがおかしい問題。


## 環境
- Windows, WSL
- Docker
- Ruby 3.2.3
- Rails 7.1.3


## 投稿失敗後のフォームが入力できなくなっていた
👇下記画面は投稿に失敗した時、駅名、都道府県、路線名を選択できなくなっている状態の画面になります。
都道府県に至ってはそもそもの入力フォーム自体消失！！:tired_face:
※新規投稿段階ではプルダウン形式でそれぞれ選択できるようにしています。
[![Image from Gyazo](https://i.gyazo.com/be06f6e3472180ef58cc98a1bc9354fe.png)](https://gyazo.com/be06f6e3472180ef58cc98a1bc9354fe)

<br>

### 主な原因:

1. **データリスト（`datalist`）がエラー時に再描画されない**
    投稿に失敗したときに、通常ならページが再度レンダリングされるが、都道府県、路線、駅名などの選択肢を提供するデータ（`@prefectures`, `@lines`, `@stations`）がリロードされない、またはJavaScriptでの依存関係が正しく再初期化されないことが問題。
    
2. **エラーハンドリング後のJavaScriptの再初期化が不足している**
    エラー発生時、AjaxやJavaScriptで行っている処理が再実行されない可能性がある。特に、フォーム要素に動的なリストを表示する場合、それを管理しているスクリプトがもう一度動作するようにしなければならない。


#### こういうこと？
上記原因より、`@post,@lines`があり、投稿後の動作を設定したファイルはコントローラー関係。ポストコントローラーで`new`アクションや`create`アクションで投稿失敗後の動作について入力したコードに原因があるかもしれない。
```ruby
# app/cotrollers/posts_controller.rb
  def create
    @post = current_user.posts.build(post_params)
    # 駅名や路線名からIDを抽出する処理
    station_name = params[:post][:station_id].split(" (ID: ").first # 駅名を取得
    line_name = params[:post][:line_id].split(" (ID: ").first # 路線名を取得
  
    # 駅名からIDを取得
    station = Station.find_by(name: station_name)
    line = Line.find_by(name: line_name)
  
    # IDが見つかった場合に設定
    @post.station = station if station
    @post.line = line if line
  
    if @post.save
      # 投稿数を取得
      post_count = current_user.posts_count
  
      # 通常の投稿メッセージ
      flash[:success] = t('defaults.flash_message.created', item: Post.model_name.human)
  
      # 特定の投稿数に達したときに追加メッセージを表示
      if [1, 5, 10, 20, 30].include?(post_count)
        flash[:success] += " マイバッジページを確認しよう！"
      end
  
      redirect_to posts_path
    else
      flash.now[:danger] = t('defaults.flash_message.not_created', item: Post.model_name.human)
      render :new, status: :unprocessable_entity
    end
  end
```

投稿が成功しなかった時にrender :newとすることでnewメソッドに飛んでしまっている？
```ruby
  def new
    @post = Post.new
    @stations = Station.all
    @prefectures = Prefecture.all
    @lines = Line.all
  end
```

これが原因で投稿が成功しなかった時に表示されるページで路線と駅名はもともとプルダウンで選択できるようになっていたのがただの入力フォームとなってしまい、都道府県選択フォームは選択も入力もできなくなってしまうのか。

> `render :new`を使って投稿エラー時に新規投稿フォームを再表示すると、newメソッドが再度呼び出されるが、現在のコードではnewメソッド内でフォームに必要なデータ（@stations, @prefectures, @lines）が設定されていない。

ここまで読んで誤認していたこと👇
**「render :new」はページを再描画するだけで、newメソッドを呼び出すのではない！**

つまり、**render :new**を使用する際に、newメソッドで行っているようなデータのセットを行う必要がある。＝**createメソッド内でエラーが発生した際にも、`@stations, @prefectures, @lines`をセットする必要がある**

こういう事か？？！（else以下追加）:thinking:
```ruby
    else
      # 投稿失敗時も必要なデータをセット
      @stations = Station.all
      @prefectures = Prefecture.all
      @lines = Line.all

      flash.now[:danger] = t('defaults.flash_message.not_created', item: Post.model_name.human)
      render :new, status: :unprocessable_entity
    end
  end
```

開発環境（ローカル）では上記変更を加えたら、投稿失敗時の次に表示されるページで選択式のフォームを保ったまま再度表示されるようになりました。
そして再デプロイ後のサイトもただの入力欄になることなく、新規投稿時と同じようなフォームで表示するようになりました！


### だがしかし、うまく表示されないは続く
たしかに、先ほどの修正により、投稿失敗した後のページでも新規投稿と同じようにフォームを維持できましたが、今度は内容の一部だけ保持する形となってしまっています
（駅名で「函館 (ID: 1)」を選択していたのが、「1」の表示だけになってしまう。）
[![Image from Gyazo](https://i.gyazo.com/681e34fbfd53983b7006d76c19b4fdb4.png)](https://gyazo.com/681e34fbfd53983b7006d76c19b4fdb4)

つまり**フォームに表示されるフィールドがIDだけを保持している**状態

#### 解決のために
投稿失敗時には、選択された駅名や路線名を再度表示させるために、**フォームで表示する値を組み立て直す必要がある**

再び`create`アクションの`else`ブロック内で、投稿失敗時に入力された値を再度表示させるための処理を追加する
駅名や路線名を保持するために、`station_id`や`line_id`を再設定し、元の形式で表示されるようにする必要がある

```ruby
  else
    # 投稿失敗時も必要なデータをセット
    @stations = Station.all
    @prefectures = Prefecture.all
    @lines = Line.all

    # 駅名と路線名を再設定して、フォームに正しく表示されるようにする
    if @post.station
      @post.station_id = "#{@post.station.name} (ID: #{@post.station.id})"
    end

    if @post.line
      @post.line_id = "#{@post.line.name} (ID: #{@post.line.id})"
    end

    flash.now[:danger] = t('defaults.flash_message.not_created', item: Post.model_name.human)
    render :new, status: :unprocessable_entity
  end
end
```

## 疑問
#### 追加したコードについて
- 追加した部分のコードは、投稿失敗時にフォームが再描画される際に、再度駅名や路線名を「名前 (ID: 〇〇)」の形式で表示するためのもの。この情報は、フォームに渡されている@postオブジェクトから取得。
- 具体的には、`@post.station`や`@post.line`が、stationやlineのオブジェクトを保持しているかどうかを確認し、保持している場合にその名前（station.nameやline.name）とID（station.idやline.id）を使って値を組み立てている。

#### 前に配置してあるコードとの関係
```ruby
# 駅名や路線名からIDを抽出する処理
station_name = params[:post][:station_id].split(" (ID: ").first # 駅名を取得
line_name = params[:post][:line_id].split(" (ID: ").first # 路線名を取得

# 駅名からIDを取得
station = Station.find_by(name: station_name)
line = Line.find_by(name: line_name)
```
- この部分の処理では、投稿時に入力された「駅名 (ID: 〇〇)」や「路線名 (ID: 〇〇)」の形式から、駅名と路線名を取得
- 見つかったstationやlineオブジェクトの情報を@post.stationや@post.lineにセット
- 失敗した後もその情報を使って再度駅名や路線名を「名前 (ID: 〇〇)」の形式で表示できるようになる

#### 投稿失敗時にフォームに値を保存させておく仕組み
何度か行っているはずなのに忘れてしまっているので確認含めて・・・。
- フォームの入力値が投稿失敗後も保持されるのは、Railsのフォームヘルパーである`form_with`が`@post`オブジェクトに対して自動的に値を保持しているため
- **`form_with`**
    `form_with model: @post` の部分で、`@post`オブジェクトがフォームのモデルとして渡されている。Railsの`form_with`は、デフォルトでモデルの属性に含まれている値をフォームのフィールドに自動的に反映させる仕組みがある。
    
    例えば、`@post.station_id`に値が入っていれば、その値がテキストフィールドや選択メニューの初期値として表示される。
    
- **`@post`オブジェクト**
    投稿が失敗しても、`@post`オブジェクトは引き続き保持される。`else`ブロックの中で`render :new`を呼ぶことで、新規投稿画面に戻る際にも、`@post`オブジェクトがそのまま使われ、入力された内容がフォームのフィールドに再表示される。
    
- **`form_with`内での各フィールド**
各フォームフィールドは`@post`の属性に基づいて自動的に値を保持。例えば、次のようにテキストフィールドを定義している部分では、`station_id`が保持されている限り、投稿失敗時にもその値がフォームに表示される。
```
<%= f.text_field :station_id, id: 'station_input' %>
```
この`station_id`には、`@post.station_id`の値が入っているため、失敗後もその値がフィールドに残ることとなる。

## さいごにまとめ
- `render :new`はページの再描写。newメソッドとは関係ない？
- `else`で投稿失敗時の状態をちゃんと設定しておかないといけない
- ビューファイルで入力するRailsのフォームヘルパー`form_with`が`@post`オブジェクトに対して自動的に値を保持。

失敗してみて改めて気づく。今回の記事が何か参考になれば幸いです。