🐷

Railsで質問種別ごとの動的フォームを作成する(with turbo)

2025/03/01に公開

Nested Forms With Turbo (without dependencies)で紹介されている動的フォームに質問種別の切り替えを追加する。

課題

質問種別を選択し、選択した質問種別でtextやcheckboxといった動的フォームを切り替えたい

Javascriptなしで動的フォームを作成する - formaction,turbo_stream

質問種別を選択するselect_tagを追加することで動的フォームを切り替えることができる。

#app/views/surveys/_form.html.erb
...
  <div id="questions"></div>

  <%= select_tag "question_type", options_for_select([["Text", "text"], ["Multiple Choice", "multiple_choice"]]) %>
  <%= button_tag "Add Question", formaction: new_question_path, formmethod: :get, data: {turbo_stream: true} %>
...

QuestionController#newのturbo_stream内で、レンダリングするviewを切り替える。

# app/views/questions/new.turbo_stream.erb
<%= turbo_stream.append "questions" do %>
  <%= fields_for 'survey[questions_attributes][]', Question.new, index: Time.current.to_i do |form| %>
    <%= render "surveys/question_fields", form: form %>
  <% end %>
<% end %>

# app/views/surveys/_question_fields.html.erb
<div>
  <%= params[:question_type] %>
  
  <%= form.label :content, "Question" %>
  <% if params[:question_type] == "multiple_choice" %>
    <%= form.check_box :content %>
    <%= form.check_box :content %>
    <%= form.check_box :content %>
  <% else %>
    <%= form.text_area :content %>
  <% end %>
</div>

現状では、button_tagにformactionとformmethodを利用してformタグが指定するリソースを上書きしている。
そのため、form_with内のパラメータがすべてGETリクエストで送られる。

Started GET "/questions/new?_method=patch&authenticity_token=[FILTERED]&survey%5Bname%5D=1&survey%5Bquestions_attributes%5D%5B1740783204%5D%5Bcontent%5D=1111&question_type=multiple_choice&button=" for 127.0.0.1 at 2025-02-28 22:53:26 +0000
Processing by QuestionsController#new as TURBO_STREAM

Javascriptありで動的formを作成する - javascript,turbo_stream

formactionによるリソースの上書きでは、不要なパラメータがサーバー側に送信される。
2000文字を超えるURLは危険と考えられているため、代替案を検討する。

buttonタグによるformactionの書き換えから、aタグのURL生成へ実装方針を切り替える。
https://github.com/thoughtbot/hotwire-example-template/tree/hotwire-example-turbo-dynamic-forms?tab=readme-ov-file#refining-the-request

aタグに変更し、select_tag変更時にaタグのクエリパラメータを差し替える。

-  <%= select_tag "question_type", options_for_select([["Text", "text"], ["Multiple Choice", "multiple_choice"]]) %>
-  <%= button_tag "Add Question", formaction: new_question_path, formmethod: :get, data: {turbo_stream: true} %>
+  <fieldset class="contents" data-controller="search-params">
+    <%= select_tag "question_type", options_for_select([["Text", "text"], ["Multiple Choice", "multiple_choice"]]), data: {action: "search-params#encode"} %>
+    <%= link_to "Add Question(LINK)", new_question_path, data: {turbo_stream: true, search_params_target: "anchor"} %>
+  </fieldset> 

URLSearchParamsを利用して、select_tagで選択している値をクエリパラメータに設定する。

// app/javascript/controllers/search_params_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "anchor" ]

  encode({ target: { name, value } }) {
    for (const anchor of this.anchorTargets) {
      anchor.search = new URLSearchParams({ [name]: value })
    }
  }
}

question_typeのみを送信してformを切り替えることができるようになった。

Started GET "/questions/new?question_type=multiple_choice" for 127.0.0.1 at 2025-03-01 01:03:01 +0000
Processing by QuestionsController#new as TURBO_STREAM
Parameters: {"question_type"=>"multiple_choice"}

ラップアップ

javascriptとturboを使うことで、不要なパラメータ送信することなく質問種別毎に動的フォームを切り替えることができた。
turboと組み合わせることで、javascirptはURLのクエリパラメータを設定するのみでよい。これまでjavascriptがAPIエンドポイントへリクエストをし、HTMLレンダリングしていたコードが不要となる。

参考

https://thoughtbot.com/blog/dynamic-forms-with-turbo

Discussion