Rails 7のresponse_doエラーActionController::UnknownFormat
概要
Rails 7でAjaxリクエストを実行しようとしていますが、フォームが送信されると(ボタンが押されると)JavaScriptがレンダリングされます。
私の株式コントローラーには次のものがあります。
class StocksController < ApplicationController
def search
if params[:stock].present?
@stock = Stock.new_lookup(params[:stock])
if @stock
respond_to do |format|
format.js {render partial: 'users/result'}
end
else
flash[:alert] = "Please enter a valid symbol to search"
redirect_to my_portfolio_path
end
else
flash[:alert] = "Please enter a symbol to search"
redirect_to my_portfolio_path
end
end
end
私が持っている私のフォーム:
<div class="search-area">
<h3>Search Stocks</h3>
<%= form_tag search_stock_path, method: :get, remote: true do %>
<div class="form-group row">
<div class="col-sm-9 noRightPad">
<%= text_field_tag :stock, params[:stock], placeholder: "Stock ticker symbol",
autofocus: true, class: "form-control form-control-lg" %>
</div>
<div class="col-sm-3 noLeftPad">
<%= button_tag type: :submit, class: "btn btn-success" do %>
<%= fa_icon "search 2x" %>
<% end %>
</div>
</div>
<% end %>
エラーメッセージ:
お時間をいただきありがとうございます。
アップデート コントローラが更新されました
class StocksController < ApplicationController
respond_to :js
def search
if params[:stock].present?
@stock = Stock.new_lookup(params[:stock])
if @stock
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.update(
"results",
partial: "users/result" # render any partial and remove js code.
)
end
end
else
flash[:alert] = "Please enter a valid symbol to search"
redirect_to my_portfolio_path
end
else
flash[:alert] = "Please enter a symbol to search"
redirect_to my_portfolio_path
end
end
終わり
フォームタグ
<%= form_tag search_stock_path, method: :get, data: {turbo_stream: true}, remote: true do %>
コンソールで406 Not allowedエラーを調査しましたが、respond_toを追加するように指示されていますが、それでも同じ問題が発生します
解決策
この質問がよく寄せられるのを見てきましたので、ここでその魔法を解き明かしてみます。
設定:
rails new rails_formats -c tailwind
cd rails_formats
bin/rails g scaffold stock name
bin/rails db:migrate
open http://localhost:3000/stocks/new
bin/dev
Rails はこれを抽象化するので、これに対処する必要はありません。知っておく必要があるヘッダーが 2 つあります。
これは、リクエストでサーバーに送信し、応答で送り返すものです。たとえば、マルチパート フォーム データを送信し、応答として json を取得できます (画像を API サーバーにアップロードする必要がある場合)。
これが応答として取得したいものです。これは、どの形式のブロック レールを実行するかを決定するものです。
これは、Accept ヘッダーと Content-Type ヘッダーに含まれるものです。 Rails にはそれを処理するクラスがあります。 https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://api.rubyonrails.org/classes/Mime/Type.html
Mime::Type.register "text/vnd.hyper-stream.html", :hyper
# if you send this in Accept ^ header, then run this ^ format block
ActiveSupport.on_load(:action_controller) do
ActionController::Renderers.add :hyper do |html, options|
# set response type if rendering ^ `render hyper: ..`
self.content_type = Mime[:hyper] if media_type.nil?
html
end
end
# now you have your own format
format.hyper { render hyper: ... }
どのヘッダーを使用すればよいかはわかりました。その使用方法は次のとおりです。
<!-- app/views/stocks/_form.html.erb -->
<!-- disable Turbo for now so it doesn't interfere -->
<script type="module"> Turbo.session.drive = false </script>
<!-- make your own remote form -->
<div id="remote_response"></div>
<%= form_with model: stock, html: {onsubmit: "remote(event)"} do |form| %>
<%= form.submit %>
<% end %>
*実際のアプリではイベント リスナーとイベント委任を使用します。
<script charset="utf-8">
function remote(event) {
event.preventDefault();
const form = event.target;
fetch(form.action, {
// headers: { "Accept": "text/html" },
// headers: { "Accept": "text/vnd.turbo-stream.html" },
headers: { "Accept": "application/json" },
method: form.method,
body: new FormData(form),
})
.then(response => response.text())
.then(text => {
document.querySelector("#remote_response").innerHTML = text;
})
}
</script>
def create
puts "# CONTENT TYPE | #{request.content_type}" # what you sent
puts "# ACCEPT | #{request.accept}" # what you want
# Change Accept header in `fetch` to choose which format block to run
respond_to do |format|
format.html { render html: "Responded with html" }
format.json { render json: "Responded with json" }
format.js { render js: "console.log('railsujs')" }
format.turbo_stream { render turbo_stream: "Responded with turbo stream" }
end
puts "# RESPONSE TYPE | #{response.content_type}" # what you get
end
Remote: true (または、form_with を使用する場合は local: false) は、data-remote=“true” を form タグに追加するだけです。フロントエンドの何かがそれをどうするかを知る必要があります。それは Rails7 で Turbo に置き換えられた RailsUJS です。
$ bin/importmap pin @rails/ujs
Turbo または RailsUJS を選択してください。
// app/javascript/application.js
// import "@hotwired/turbo-rails"
// import "controllers"
import Rails from "@rails/ujs";
Rails.start();
<%= form_with model: stock, local: false do |form| %>
...
ここで、RailsUJS は、remote() 関数で行ったことを実行し、format.js ブロックが実行されるように Accept を text/javascript に設定します。また、応答を処理し、コードを実行します。
ストリーム、フレーム、ブロードキャストについて理解するには、少し時間がかかります。まずはturbo_streamから始めてください。最初ははるかに理解しやすいと思いました。
セットアップは必要ありません。すべてのフォームは TURBO_STREAM 形式、別名「Accept」:「text/vnd.turbo-stream.html、text/html、application/xhtml+xml」として「リモート」で送信されます。これは、format.html または format.turbo_stream で応答できることを意味します。
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.update(
"id_of_the_element_to_update",
partial: "users/result" # render any partial and remove js code.
)
end
end
ストリームを取得する
data-turbo-stream=“true” をメソッド:get とリンクを使用してフォームに追加します。
form_tag "/", method: :get, data: {turbo_stream: true} do
https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://turbo.hotwired.dev/handbook/streams#streaming-from-http-responses
ブートストラップ
「ブートストラップ」は、ピンがない限りインポートできません。
import * as bootstrap from "bootstrap"
// ^^^^^^^^^
// browser doesn't know how to get that
ピンを追加します (そして cdn からスタイルを取得します)。
bin/importmap pin bootstrap
importmap が面倒すぎる場合は、cssbundling-rails を使用してください。 Rails にはこれが組み込まれています。
rails new my_app -c bootstrap
ただし、Rails 7にブートストラップをインストールする方法については他にもたくさんの答えがあります。
デバッグ
JavaScript を使用する場合、ブラウザのコンソールを見る必要があります。