英文:
How to have a delete link respond to turbo_stream and html in Rails 7?
问题
我有一个在Rails 7中的删除链接,可以使用turbo_stream或html正确运行,但不能同时使用这两者。
link_to 'delete', @object, data: { turbo_method: 'delete', turbo_confirm: '真的吗?' }
我从索引页面调用此链接,应该使用turbo_stream响应来删除记录并移除表格行。索引页面包含在一个turbo-frame标记中。我还从展示页面调用此方法,其中应该使用html响应来删除记录并重定向回到索引页面。展示页面未包含在turbo-frame标记中。
展示页面链接正确地命中了销毁操作并销毁了记录,但没有重定向。实际上,它响应了turbo_stream。如果我从销毁操作中移除format.turbo_stream块,那么相同的链接会正确命中format.html响应并重定向。相同的链接知道如何响应format.html,但它却尝试响应format.turbo_stream,即使链接没有包含在turbo-frame标记中。
在Rails 7中,数据属性"turbo_method: 'delete'"导致了turbo_stream调用。有没有一种方法可以让该链接响应format.html?
当来自链接的传入响应是turbo_stream时,我如何让展示页面上的链接响应format.html并重定向?
英文:
I have a delete link in Rails 7 that functions correctly using either turbo_stream or html, but not each of those.
link_to 'delete', @object, data: { turbo_method: 'delete', turbo_confirm: 'Really?' }
I call this link from the index page, which should use turbo_stream response to delete the record and remove the table row. The index page is wrapped in a turbo-frame tag. I also call this method from the show page, where an html response should delete the record and redirect back to the index page. The show page is not wrapped in a turbo-frame tag.
The show page link correctly hits the destroy action and destroys the record---however, it does not redirect. It actually responds to turbo_stream. If I remove the format.turbo_stream block from the destroy action, then that same link correctly hits the format.html response and redirects. That same link knows how to respond to format.html, but it instead attempts to respond to format.turbo_stream even though the link was not wrapped in a turbo-frame tag.
In Rails 7, the data attribute "turbo_method: 'delete'" results in a turbo_stream call. Is there a way to tell that link to respond to format.html?
How can I get the link on the show page to respond to format.html and redirect--when the incoming response from the link is turbo_stream?
答案1
得分: 13
I have mentioned headers a few times recently, so I'll keep this part short:
当您发送TURBO_STREAM请求时,首选的格式是turbo_stream
。如果您没有turbo_stream格式块或turbo_stream.erb模板,那么将使用html
格式。因为turbo可以处理这两种响应,它在Accept
标头中设置了这两种类型,以确定要运行的格式块。您可以从destroy
操作中查看它:
puts request.headers["Accept"]
#=> text/vnd.turbo-stream.html, text/html, application/xhtml+xml
# ^ ^
# turbo is first in line html is second
def destroy
@model.destroy
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.remove(@model) }
format.html { redirect_to models_url, notice: "Destroyed." }
end
end
要获取turbo_stream响应
<%= link_to "Turbo destroy", model_path(model),
data: {turbo_method: :delete}
%>
<%= button_to "Turbo destroy", model_path(model),
method: :delete
%>
要获取html响应
Rails也可以忽略Accept
标头,从URL扩展名确定格式。对/models/1.html
的Turbo请求将响应为html。
<%= link_to "HTML turbo destroy", model_path(model, format: :html),
data: {turbo_method: :delete}
%>
<%= button_to "HTML turbo destroy", model_path(model, format: :html),
method: :delete
%>
我最不喜欢的选项是turbo: false
,让人讨厌:
<%= button_to "HTML rails destroy", model_path(model),
method: :delete,
data: {turbo: false}
%>
使用URL或表单参数执行您想要的操作
<%= button_to "Turbo destroy with params", model_path(model),
method: :delete,
params: {redirect_to: "/anywhere/you/like"} # 或者只是true/false
%>
def destroy
@model.destroy
respond_to do |format|
# 只需传递一个参数并跳过turbo_stream块
unless params[:redirect_to]
format.turbo_stream { render turbo_stream: turbo_stream.remove(@model) }
end
format.html { redirect_to (params[:redirect_to] || models_url), notice: "Destroyed." }
end
end
您还可以明确设置format:
// 它不必是回调,只需在`respond_to`块之前发生。
before_action :guess_destroy_format, only: :destroy
def guess_destroy_format
// 这样,您不需要在turbo_stream周围使用`unless params[:redirect_to]`
request.format = :html if params[:redirect_to]
// 如果从显示页面删除,无需进行任何额外操作
request.format = :html if request.referrer.start_with?(request.url)
end
https://api.rubyonrails.org/classes/ActionDispatch/Http/MimeNegotiation.html
一些值得一提的地方:
// `format`也可以作为表单输入而不是URL扩展名
<%= button_to "Turbo destroy with format input", model_path(model),
method: :delete,
params: {format: :html}
%>
// 格式作为URL查询参数,它有效,但参数会从URL中消失
// 并不会显示在日志中,这是正常的。Turbo的魔法将其转化为带有输入字段的表单,就像上面的button_to一样
<%= link_to "Turbo destroy with `?format=html`",
model_path(model, params: {format: :html}),
data: {turbo_method: :delete}
%>
要获取带有Accept
标头的任何响应
也许您需要隐藏那个难看的.html
,或者您不想过多地处理控制器。设置Accept
标头并获取您需要的内容。请注意,Turbo将处理html和turbo_stream,但您将不得不自己处理其他响应:
// app/javascript/application.js
const Mime = {
turbo_stream: "text/vnd.turbo-stream.html",
html: "text/html",
json: "application/json",
}
document.addEventListener('turbo:submit-start', function (event) {
const {
detail: {
formSubmission: {
fetchRequest: { headers },
submitter: { dataset: { accept } },
},
},
} = event
if (Mime[accept]) {
headers["Accept"] = Mime[accept]
}
})
使用data-accept
来设置类型:
<%= button_to "only html", model, method: :delete,
data: {accept: :html}
%>
<%= button_to "only turbo", model, method: :delete,
data: {accept: :turbo_stream}
%>
英文:
I have mentioned headers a few times recently, so I'll keep this part short:
When you send a TURBO_STREAM request, the first format that takes priority is turbo_stream
. If you don't have a turbo_stream format block or a turbo_stream.erb template, then html
format is used. Because turbo can handle both of these responses, it sets both types in Accept
header, which determines what format block to run. You can take a look at it from destroy
action:
puts request.headers["Accept"]
#=> text/vnd.turbo-stream.html, text/html, application/xhtml+xml
# ^ ^
# turbo is first in line html is second
def destroy
@model.destroy
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.remove(@model) }
format.html { redirect_to models_url, notice: "Destroyed." }
end
end
To get a turbo_stream response
<%= link_to "Turbo destroy", model_path(model),
data: {turbo_method: :delete}
%>
<%= button_to "Turbo destroy", model_path(model),
method: :delete
%>
To get an html response
Rails can also ignore Accept
header and determine the format from a url extension. Turbo request to /models/1.html
will respond with html.
<%= link_to "HTML turbo destroy", model_path(model, format: :html),
data: {turbo_method: :delete}
%>
<%= button_to "HTML turbo destroy", model_path(model, format: :html),
method: :delete
%>
My least favorite option turbo: false
, yuck:
<%= button_to "HTML rails destroy", model_path(model),
method: :delete,
data: {turbo: false}
%>
Use url or form params to do whatever you want
<%= button_to "Turbo destroy with params", model_path(model),
method: :delete,
params: {redirect_to: "/anywhere/you/like"} # or maybe just true/false
%>
def destroy
@model.destroy
respond_to do |format|
# just pass a param and skip turbo_stream block
unless params[:redirect_to]
format.turbo_stream { render turbo_stream: turbo_stream.remove(@model) }
end
format.html { redirect_to (params[:redirect_to] || models_url), notice: "Destroyed." }
end
end
You can also set the format explicitly:
# it doesn't have to be a callback, just has to happen before `respond_to` block.
before_action :guess_destroy_format, only: :destroy
def guess_destroy_format
# this way you don't need `unless params[:redirect_to]` around turbo_stream
request.format = :html if params[:redirect_to]
# don't need to do anything extra if deleting from a show page
request.format = :html if request.referrer.start_with?(request.url)
end
https://api.rubyonrails.org/classes/ActionDispatch/Http/MimeNegotiation.html
A couple of honorable mentions:
# `format` also works as a form input instead of a url extension
<%= button_to "Turbo destroy with format input", model_path(model),
method: :delete,
params: {format: :html}
%>
# format as a url query param, it works but params disappear from the url
# and don't show in the logs, that's normal. Turbo magic turns it into
# a form with inputs, like the button_to above
<%= link_to "Turbo destroy with `?format=html`",
model_path(model, params: {format: :html}),
data: {turbo_method: :delete}
%>
To get any response with Accept
header
Maybe you need to hide that ugly .html
or you don't want to mess with controllers to much. Set Accept
header and get just what you need. Note that Turbo will handle html and turbo_stream, but you'll have to handle any other responses yourself:
// app/javascript/application.js
const Mime = {
turbo_stream: "text/vnd.turbo-stream.html",
html: "text/html",
json: "application/json",
}
document.addEventListener('turbo:submit-start', function (event) {
const {
detail: {
formSubmission: {
fetchRequest: { headers },
submitter: { dataset: { accept } },
},
},
} = event
if (Mime[accept]) {
headers["Accept"] = Mime[accept]
}
})
Use data-accept
to set the type:
<%= button_to "only html", model, method: :delete,
data: {accept: :html}
%>
<%= button_to "only turbo", model, method: :delete,
data: {accept: :turbo_stream}
%>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论