【Rails】DBと連携してプルダウン(ドロップダウン)メニューをつくる(勉強中)


SIer企業で働く社会人4年目OLです。
仕事をやめる予定はないのですが、手に職をつけていつか副職ができるといいなと思いWEBアプリ開発の勉強し始めました。(2ヵ月くらい)

練習でWEBアプリ開発をしていますが、初めてつまったのがプルダウンメニューの作成なので勉強がてらまとめてみます。同じように悩んでる人の役に立てたらいいなと思います。

勉強始めたばかりなのでここ違うよ!っていうところがあればご指摘いただけると嬉しいです。

やりたいこと

DB(Gameモデル)に格納しているデータをプルダウン(ドロップダウン)で選択して別のDB(Stageモデル)に保存したい。

モデル設計

Gameモデル

カラム
id integer
game_name text

Stageモデル

カラム
id integer
game_id ※ integer
stage_name text

※Stageのgame_idがGameのidを外部参照しているイメージ

form_tagで実装

これまでユーザからの入力は全部form_tagで実現していたので、まずはそれでむりやり実装したみた。

新規登録用ページ(stage_a)のView

<!-- 新規登録アクション(stage_n)にデータ渡す -->
<%= form_tag("/stage_n") do %>
  <div class="form">
    <div class="form-body">
      <p>game_name</p>
      <select name="game_name">
        <%@games.each do |game|%>
          <option value=<%="#{game.game_name}"%>><%=game.game_name%></option>
        <%end%>
      </select>
      <p>stage_name</p>
      <input type="text" name="stage_name">
      <input type="submit" value="保存">
    </div>
  </div>
<% end %>

新規登録用のController

  def stage_a
    @games = Game.all
  end

  def stage_n
    @game = Game.find_by(game_name: params[:game_name])
    @stage = Stage.new(game_id: @game.id,stage_name: params[:stage_name])
    @stage.save
  end

新規登録用ページ(stage_a)のブラウザでの見え方

期待値通りにできた。のでこれでいいのかとも思ったが、ネットで検索してみるとそもそもモデルに紐づいたユーザ入力はform_tagではなくてform_forを使うのが一般的らしい。

form_forで実装

RailsのインプットはProgateでしかしていないのでform_forは初見だが、見よう見まねで実装してみた。

新規登録用ページ(stage_a)のView

<!-- 新規登録アクション(stage_n)にデータ渡す -->
<%= form_for("@stage", url: {controller: 'home', action: 'stage_n' }) do |f|%>
  <div class="form">
    <div class="form-body">
      <p>game_name</p>
      <%= f.collection_select(:game_id,@games,:id,:game_name)%>
      <p>stage_name</p>
      <%= f.text_field :stage_name%>
      <%= f.submit "保存"%>
    </div>
  </div>
<% end %>

新規登録用のController

  def stage_a
    @games = Game.all
    @stage = Stage.new
  end

  def stage_n
    Stage.create(stage_params)
    redirect_to("/stage_view")
  end

    private
    def stage_params
      params.require(:@stage).permit(:stage_name, :game_id)
    end

新規登録用ページ(stage_a)のブラウザでの見え方

なんとか期待値通りになった。無理にループを自分で実装しなくてもよくてすっきりと記述できた。

form_for("保存するモデルのインスタンス", url: {送信先のアクション}) do |f|

本来なら送信先のアクションについても記載しなくても自動でルーティングしてくれるらしいが、うまくルーティングできなかったため記載した。
プルダウン形式は以下のf.collection.selectで実現できた。

f.collection_select(保存カラム,参照インスタンス,保存する参照先カラム,表示する参照先カラム)

ついでに、通常のテキストボックス(1行)は以下のf.text_field

f.text_field 保存カラム

form_forの使い方については【Rails】form_forの使い方をマスターしよう!のページがとても分かりやすかった。というよりRailsのヘルパーメソッドに基本的にこのサイトにまとめられているので今後参考にしていきたい。

form_withで実装

from_forについて調べていたら、Rails5.1以降はform_withを使用することが推奨されているとのこと。これについても見よう見まねで実装してみた。

新規登録用ページ(stage_a)のView

<!-- 新規登録アクション(stage_n)にデータ渡す -->
<%= form_with(model: @stage, url: {controller: 'home', action: 'stage_n'} ,local: true) do |form|%>
  <div class="form">
    <div class="form-body">
      <p>game_name</p>
      <%= form.collection_select(:game_id,@games,:id,:game_name)%>
      <p>stage_name</p>
      <%= form.text_field :stage_name%>
      <%= form.submit "保存"%>
    </div>
  </div>
<% end %>

新規登録用のController

  def stage_a
    @games = Game.all
    @stage = Stage.new
  end


  def stage_n
    @stage=Stage.new(game_id: params[:stage][:game_id],stage_name: params[:stage][:stage_name] )
    @stage.save
    redirect_to("/stage_view")
  end

新規登録用ページ(stage_a)のブラウザでの見え方

これも他と変わらずに期待値通り。正直form_forとそんなに変わらないじゃんと思った。参考にしたページの記述に似せたためControllerに多少違いはあるが、同じ書き方をしても問題ないと思っている。(試してはいない)

form_tagとform_forとform_withについて

それぞれの使い分けについてもざっくり調べてみた。まず、form_tagform_forについては以下のサイトが参考になった。

Railsのform_for/form_tagの分け方の意図としては、

form_for: 任意のmodelに基づいたformを作るときに使う
form_tag: modelに基づかないformを作るときに使う

ということです。
つまり、あるuserモデルに基づいたuserを作成するときはform_forを使い、
そうではなく、検索窓のような何のモデルにも基づかないformを作りたいときはform_tagを使うのが原則です。

【Rails】form_for/form_tagの違い・使い分けをまとめた

モデルに紐づいたフォームか否かが使い分けの基準と理解した。

そして、form_withform_tagform_forの機能をまとめたヘルパーメソッドである。form_withの使用が推奨されていることから、Rails5.1以降を使用する場合はform_withを使用すべきと考えてよいと思う。

プルダウン同士の連携

プルダウンメニューは作れたが、次は別の問題が発生した。
1つ目のプルダウンを選択したら2つ目のプルダウンで選択できる内容が変更できるような入力フォームを作ろうと考えたが、Viewを更新しないとアクションにデータを渡せない。

これを解決するためにはAjaxを理解する必要があるようだが今は???状態なので調べて理解・実装したら、別の記事でまとめようと思う。