Techioz Blog

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 を使用する場合、ブラウザのコンソールを見る必要があります。