英文:
Rails 7 respond_do Error 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] = "请输入有效的符号进行搜索"
redirect_to my_portfolio_path
end
else
flash[:alert] = "请输入要搜索的符号"
redirect_to my_portfolio_path
end
end
end
我的表单:
<div class="search-area">
<h3>搜索股票</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: "股票代码",
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 %>
</div>
感谢您的时间。
更新
控制器已更新:
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" # 渲染任何部分并删除js代码。
)
end
end
else
flash[:alert] = "请输入有效的符号进行搜索"
redirect_to my_portfolio_path
end
else
flash[:alert] = "请输入要搜索的符号"
redirect_to my_portfolio_path
end
end
end
表单标签:
<%= form_tag search_stock_path, method: :get, data: {turbo_stream: true}, remote: true do %>
英文:
I am trying to do a Ajax request in rails 7 when the form is submitted (when the button is pressed) it renders JavaScript
In my Stocks Controller I have:
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
My Form I have:
<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 %>
</div>
Thank you for your time.
Update
Controller Updated
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
end
Form Tag
<%= form_tag search_stock_path, method: :get, data: {turbo_stream: true}, remote: true do %>
I have researched the 406 Not acceptable error in console but it says to add respond_to which I have done and still the same issue
答案1
得分: 1
我将为您翻译以下部分:
Headers
Rails将此抽象出来,因此您无需处理它。有两个标头您必须知道:
Content-Type
这是您发送到服务器的请求和您从响应中发送的内容。例如,您可以发送多部分表单数据并获得JSON作为响应(当您需要上传图像到API服务器时)。
Accept
这是您要作为响应获取的内容。这是确定Rails将运行哪个格式块的标头。
Mime类型
这是放入Accept和Content-Type标头的内容。Rails有一个处理它的类:
https://api.rubyonrails.org/classes/Mime/Type.html
Mime::Type.register "text/vnd.hyper-stream.html", :hyper
# 如果您在Accept标头中发送这个,然后运行这个格式块
ActiveSupport.on_load(:action_controller) do
ActionController::Renderers.add :hyper do |html, options|
# 如果渲染^ `render hyper: ..`,设置响应类型
self.content_type = Mime[:hyper] if media_type.nil?
html
end
end
# 现在您有了自己的格式
format.hyper { render hyper: ... }
Controller and Form
您知道要使用哪些标头,下面是如何使用它们:
<!-- app/views/stocks/_form.html.erb -->
<!-- 暂时禁用Turbo以防止干扰 -->
<script type="module"> Turbo.session.drive = false </script>
<!-- 制作自己的远程表单 -->
<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}" # 您发送的内容
puts "# ACCEPT | #{request.accept}" # 您想要的内容
# 在`fetch`中更改Accept标头以选择要运行的格式块
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}" # 您收到的内容
end
{remote: true}
remote: true
(如果使用form_with
,也可以是local: false
)将在表单标签中添加data-remote="true"
,仅此而已。前端的某些部分必须知道如何处理它。这个部分就是由Turbo在rails 7中取代的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()
函数中所做的操作,并将Accept设置为text/javascript
,以便运行format.js
块。它还会处理响应并执行代码。
Turbo
流和帧以及广播需要一点时间来理解。首先从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" # 渲染任何部分并删除js代码。
)
end
end
更新
GET流
对于method: :get
和链接,将data-turbo-stream="true"
添加到表单中。
form_tag "/", method: :get, data: {turbo_stream: true} do
https://turbo.hotwired.dev/handbook/streams#streaming-from-http-responses
引导
您不能导入"bootstrap",除非您有一个pin:
import * as bootstrap from "bootstrap"
// ^^^^^^^^^
// 浏览器不知道如何获取它
添加一个pin(并从cdn获取样式):
bin/importmap pin bootstrap
如果importmaps给您带来了太多麻烦,可以使用cssbundling-rails
。Rails已经内置了这个功能:
rails new my_app -c bootstrap
但是有很多其他关于如何在Rails 7中安装bootstrap的答案。
调试
在处理JavaScript时,您必须查看浏览器控制台。
英文:
I'll try to demystify the magic here, I've seen this question come up a lot.
Setup:
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
Headers
Rails abstracts this out of the away so you don't have to deal with this. There are two headers that you just have to know:
Content-Type
This is what you send to the server with your request, and what you send back with the response. For example, you can send multipart form data and get json as a response (when you need to upload images to your api server).
Accept
This is what you want to get as a response. This is the one that determines what format block rails will run.
Mime types
It's what goes into Accept and Content-Type headers. Rails has a class to handle it:
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: ... }
Controller and Form
You know what headers to use, here is how to use them:
<!-- 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 %>
*Use event listeners and event delegation in real apps.
<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}
remote: true
(or local: false
if using form_with
) will add data-remote="true"
to the form tag and that's it. Something on the frontend has to know what to do with it. That something is RailsUJS which was replaced by Turbo in rails 7.
$ bin/importmap pin @rails/ujs
Just pick Turbo or 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| %>
...
Now RailsUJS will do what we did with remote()
function and set Accept
to text/javascript
so that format.js
block would run. It will also handle the response and execute the code.
Turbo
Streams and frames and broadcasts take a little bit to get your head around it. Just start with turbo_stream
I found it much easier to understand at first.
No set up needed, all forms submit "remotely" as TURBO_STREAM format aka "Accept": "text/vnd.turbo-stream.html, text/html, application/xhtml+xml"
. This means you can respond with format.html
or 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
Update
GET streams
Add data-turbo-stream="true"
to forms with method: :get
and links.
form_tag "/", method: :get, data: {turbo_stream: true} do
https://turbo.hotwired.dev/handbook/streams#streaming-from-http-responses
Bootstrap
You can't import "bootstrap" unless you have a pin for it:
import * as bootstrap from "bootstrap"
// ^^^^^^^^^
// browser doesn't know how to get that
Add a pin (and get styles from cdn):
bin/importmap pin bootstrap
If importmaps add too much hassle use cssbundling-rails
. Rails has this built in:
rails new my_app -c bootstrap
But there are lots of other answers on how to install bootstrap in rails 7.
Debug
Working with javascript, you have to look at your browser console.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论