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
%>
# using a form field also works
# like `hidden_field_tag :format, :html` inside your form:
<%= button_to "HTML turbo destroy with format input", model_path(model),
method: :delete,
params: {format: :html}
%>
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
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}
%>