ネストされたルーティングにおけるform_with
経緯
- ある掲示板に対するコメントを入力フォームを作りたい
- その際、form_withに渡す引数の仕組みが分からない
分解して考えてみる。
外部キーの存在
外部キーを設定したことにより、board_id(外部キー)が存在しない状態ではコメントは作成できない。
アソシエーションの設定
掲示板とコメントのアソシエーションを設定したことにより、Boardクラスのオブジェクトはcomments
というメソッドを使える様になった。
irb(main):012:0> board = Board.last Board Load (0.2ms) SELECT "boards".* FROM "boards" ORDER BY "boards"."id" DESC LIMIT ? [["LIMIT", 1]] => #<Board id: 3, title: "test_title1", body: "test_body1", created_at: "2022-01... irb(main):013:0> board.comments Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? LIMIT ? [["board_id", 3], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 2, body: "test_comment1", board_id: 3, created_at: "2022-01-29 10:19:27", updated_at: "2022-01-29 10:19:27">]>
得られた値は?
=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 2, body: "test_comment1", board_id: 3, created_at: "2022-01-29 10:19:27", updated_at: "2022-01-29 10:19:27">]>
関連づけされたコメントが表示されている?
form_with復習
DBに保存しない時
<%= form_with url: root_path do |f| %>
DBに保存する時
<%= form_with model: モデルクラスのインスタンス do |f| %>
記載されたモデルクラスのインスタンス情報を持った|f|
(FormBuilderオブジェクト)によって、FormBuilderオブジェクトの持つヘルパーメソッド(f.label
やf.text_field
など)を使うことができる。
これにより、入力フォームを簡単に生成することができている。
掲示板コントローラnewアクションに空のインスタンスを作っている理由
掲示板作成に置いてform_withに引数@boardを渡しているが、
この中身が空だった場合、createメソッドへ
中身があればupdateメソッドへ振り分けるため、newメソッド(新規作成時)には空のインスタンスを作っている。
じゃあもし各コメントにも編集機能をつけるとしたら、それを振り分けるための空インスタンスが必要ということ?
form_with Railsガイドより
<%= form_with url: posts_path do |form| %> <%= form.text_field :title %> <% end %> ↓ <form action="/posts" method="post" data-remote="true"> <input type="text" name="title"> </form>
inputフィールド名にスコープをプレフィックスとして追加する場合
<%= form_with scope: :post, url: posts_path do |form| %> <%= form.text_field :title %> <% end %> ↓ <form action="/posts" method="post" data-remote="true"> <input type="text" name="post[title]"> </form>
モデルを指定して、URLとスコープを自動推論させるには
<%= form_with model: Post.new do |form| %> <%= form.text_field :title %> <% end %> ↓ <form action="/posts" method="post" data-remote="true"> <input type="text" name="post[title]"> </form>
既存のモデルの場合は更新用フォームが生成され、フィールドに値が表示される
<%= form_with model: Post.first do |form| %> <%= form.text_field :title %> <% end %> ↓ <form action="/posts/1" method="post" data-remote="true"> <input type="hidden" name="_method" value="patch"> <input type="text" name="post[title]" value="<postのtitle>"> </form>
引数として渡すのは必ずしもコントローラで作られた@postの形でなくても良い。ただし、@post内に値が保存されないので、初期値がなくなってしまう。
form_withに渡す引数 modelオプションの場合
modelオプションの場合、form_withの引数には保存したいテーブルのクラスのインスタンスを指定
保存したいテーブルのクラスのインスタンス
ということで、このフォームでは投稿へのコメント
を保存したい。
この時、ルーティングを以下のようにネストしているため引数の書き方が少し異なる。 ネストとは入れ子状の書き方のことで、この例ではある掲示板の中のコメントという親子関係を表している。
resources boards do
resources comments
en
def create @board = Board.find(params[:board_id]) @comment = Comment.new(comment_params) @comment.board_id = @board.id @comment.save redirect_to board_path(@board) end private def comment_params params.require(:comment).permit(:body) end end
createメソッド内各行の役割
- どの投稿のコメントか情報が必要になるため、コメントする投稿を取得
- フォームに入力された情報をもとにコメントを作成
- 外部キーを設定したことで、board_idがない状態では保存(save)ができないため、コメントのboard_idに取得した@boardのID(@board.id)を入れる
- ③の操作により、④でコメントのsaveが成功する
- そしてboard_path(@board)のパスで詳細画面にリダイレクトする
<%= form_with(model: [@board, @board.comments.build], local: true) do |f| %> <p> <%= f.label :body %><br/> <%= f.text_area :body %><br/> </p> <%= f.submit %> <% end %>
form_withの引数には、上述した通り保存したいテーブルクラスのインスタンスを渡す。
ルーティングがネストされているパターンなので、渡す引数はコメントコントローラで取得した投稿(@board)と@board.comments.build
の配列を引数として渡している。
@board.comments.buildの記載、その中身が空であることから、Railsが自動推論してcreateメソッドに飛ばされるということかな?
もしこのコメントにも編集機能が欲しいのであれば、下記のようにnew,editに渡す分岐としてそれぞれnew,createを定義するということ??
def new @board = Board.find(params[:board_id]) @comment = Comment.new end def edit @board = Board.find(params[:board_id]) @comment = Comment.find(params[:id]) end
<%= form_with modle: [@board, @comment], local: true do |f| %>
これは試してないので分からないけど、多分そういうことだと思う。
所感
入門編時代によく分からなくなり、なあなあに進めていたツケというか、理解の甘さが如実に現れていた箇所だった。 その当時は全く理解できなかった問題の知識が今必要になったので、立ち返ってみたけれど以前よりもすんなり理解できるようになったのでこれはこれで良いのかなと思う。
どこかで全部復習しなきゃ!!みたいな焦りに駆られていたけども、必要な箇所を必要な分復習するスタイルにしていこうを思った。