英文:
In rails, Turbo just appends the response from the server at the end of current page
问题
问题: Turbo 只是将来自服务器的响应附加到当前页面的末尾,而不是像预期的那样替换页面。
最近从Rails 6升级到了7。
同时,我从importmap切换到了esbuild。
我有一个表单,正在向create方法发出POST请求。控制器的响应是:
if @stamp.save
redirect_to stamps_path, notice: "Input saved"
else
奇怪的事情发生了,响应被附加到原始源代码的底部,从顶部附加一个新的头部部分,如下所示:
<...> 整个初始HTML <...>
</body>
<title>页面标题</title> <-- 服务器响应被附加在</body>之后
<meta ....整个页面再次重复
不太确定要向您展示什么,一直在查找所有设置等。我以前从未见过Turbo表现出这种响应。
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.2"
gem "rails", "~> 7.0.2", ">= 7.0.2.4"
gem "sprockets-rails"
gem "puma", "~> 5.6"
gem "haml-rails", "~> 2.0"
gem "sqlite3", "~> 1.4"
gem 'devise', '~> 4.8'
gem 'devise-i18n', '~> 1.10'
gem "turbo-rails"
gem "jsbundling-rails", "~> 1.0"
gem 'sass-rails'
gem "stimulus-rails"
gem 'rubyXL', '~> 3.3'
gem "jbuilder"
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
gem 'kaminari', '~> 1' # 分页宝石
gem "bootsnap", require: false
group :development, :test do
gem "debug", platforms: %i[mri mingw x64_mingw]
end
group :development do
gem "web-console"
end
group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
end
{
"private": true,
"license": "ISC",
"name": "name",
"version": "0.1.0",
"scripts": {
"release": "npm run build && npm run publish",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.2.5",
"@popperjs/core": "^2.11.5",
"@rails/activestorage": "^6.0.0",
"bootstrap": "^5.2.0",
"esbuild": "^0.17.7"
}
}
我一直在尝试复制另一个使用Turbo的Rails应用程序。一直在查看所有设置等。其他页面可以采取redirect_to并替换内容,如预期的那样。
我正在使用Haml,因此HTML结构应该没问题。
<details>
<summary>英文:</summary>
The problem: Turbo just appends the response from server to the end of current page. Not replacing the page like expected.
Recently upgraded from Rails 6 to 7.
Also I switched from importmap to esbuild.
I have a form that is making a post to a create method. The controller responds with
if @stamp.save
redirect_to stamps_path, notice: "Input saved"
else
The strange thing that happens is that the response is being appended to the bottom of the source code from the top. So at the end of the original source code a new head-section from the response is appended like
<...> The whole initial html <...>
</body>
<title>Page title</title> <-- The server response gets appended after </body>
<meta .... the whole page gets repeated again
Not really sure what to show you here, been digging through all settings etc. I have never seen Turbo behave like this with a respons.
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.2"
gem "rails", "~> 7.0.2", ">= 7.0.2.4"
gem "sprockets-rails"
gem "puma", "~> 5.6"
gem "haml-rails", "~> 2.0"
gem "sqlite3", "~> 1.4"
gem 'devise', '~> 4.8'
gem 'devise-i18n', '~> 1.10'
gem "turbo-rails"
gem "jsbundling-rails", "~> 1.0"
gem 'sass-rails'
gem "stimulus-rails"
gem 'rubyXL', '~> 3.3'
gem "jbuilder"
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
gem 'kaminari', '~> 1' # pagination gem
gem "bootsnap", require: false
group :development, :test do
gem "debug", platforms: %i[ mri mingw x64_mingw ]
end
group :development do
gem "web-console"
end
group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
end
{
"private": true,
"license": "ISC",
"name": "name",
"version": "0.1.0",
"scripts": {
"release": "npm run build && npm run publish",
"test": "echo "Error: no test specified" && exit 1",
"build": "esbuild app/javascript/. --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.2.5",
"@popperjs/core": "^2.11.5",
"@rails/activestorage": "^6.0.0",
"bootstrap": "^5.2.0",
"esbuild": "^0.17.7"
}
}
I've been trying to replicate another rails app I have using Turbo. Been looking into all settings etc. Other pages can take a redirect_to and replacing the content as expected.
I'm using haml so the HTML-structure should be fine.
</details>
# 答案1
**得分**: 7
我只能想到一种可能发生这种情况的方式:您有一个名为`show.erb`的模板。只需将其重命名为`show.html.erb`(或者*.haml*,*.slim*,都可以)。
默认情况下,表单会被提交为*TURBO_STREAM*,这是*rails*在日志中显示的,然而,这并不是全部情况。表单提交请求[设置](https://github.com/hotwired/turbo/blob/v7.2.5/src/core/drive/form_submission.ts#L161) `Accept` 标头:
```rb
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
# ^
# 这里是`TURBO_STREAM`的来源
这些是turbo期望接收的响应Content-Type
。当您在控制器操作中使用respond_to
块时,Accept
标头决定调用哪个format
块:
respond_to do |format|
if @model.save(model_params)
# 如果您有turbo_stream,它将首先运行
# format.turbo_stream { render turbo_stream: turbo_stream.replace(model) }
# 如果您没有turbo_stream,`Accept`标头中的下一个格式是html
format.html { redirect_to model_url(@model) }
format.json { render :show, status: :ok, location: @model }
# 如果没有匹配项,您将获得`ActionController::UnknownFormat`错误。
# 就像尝试呈现`format.js`时获得的错误,但Turbo不处理js格式,所以它不发送`Accept: text/javascript`
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @model.errors, status: :unprocessable_entity }
end
end
当您使用respond_to
块时,rails会自动设置一些呈现选项,其中之一是:content_type
,它又设置了响应标头的Content-Type
。因此,如果format.html
块运行,它会设置Content-Type: text/html
,而turbo知道如何通过重定向或替换页面内容来处理它。
重要的部分是我们从turbo_stream格式转到html格式。毫无疑问,html需要呈现类似html的响应,而turbo_stream必须是一个<turbo-stream>
标签。
当您不使用respond_to
块时会发生什么情况。Rails将尝试呈现与格式匹配的模板。例如,turbo_stream请求将查找show.turbo_stream.erb
,然后是show.html.erb
,然后是show.erb
,如下所示:
render :show, formats: [:turbo_stream, :html]
假设它找到了show.html.erb
,因为存在html扩展名,rails会将响应Content-Type
设置为text/html
,一切都按预期工作。
但是,如果它找到了show.erb
,没有任何东西告诉rails它是html格式,我们没有使用respond_to
块,因此响应Content-Type
没有明确设置,唯一的选择是回退到第一个Accept类型,即turbo_stream。现在您正在呈现一个html模板,但响应类型是turbo_stream,而没有<turbo-stream>
标签。
当turbo看到turbo_stream响应时,它会将其追加到文档中,这就是您看到的情况。因为没有<turbo-stream>
指令,所以它保持不变。
简而言之,沿途的某个地方需要设置正确的内容类型。
要么使用respond_to
块,它将设置必要的呈现选项。要么自己设置呈现选项:content_type: "text/html"
。要么通过使用html.erb
和turbo_stream.erb
扩展名来告诉rails内容类型。
请注意,当您重定向时,turbo_stream内容类型保持不变,因为turbo在前端处理重定向。如果在show操作中没有html.erb
扩展名和没有respond_to
块,您将得到内容类型不匹配的错误。
英文:
I can only think of a one way this could happen: you have show.erb
template. Just rename it to show.html.erb
(or .haml, .slim, it doesn't matter).
By default, the forms are submitted as TURBO_STREAM, it's what rails shows in the logs, however, this is not the whole picture. The form submit request sets Accept
header:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
# ^
# here is where `TURBO_STREAM` comes from
These are the response Content-Type
s that turbo expects to receive. When you use respond_to
block in controller actions, Accept
header is what determines which format
block to call:
respond_to do |format|
if @model.save(model_params)
# if you have turbo_stream, it will run first
# format.turbo_stream { render turbo_stream: turbo_stream.replace(model) }
# if you don't have turbo_stream, next format in `Accept` header is html
format.html { redirect_to model_url(@model) }
format.json { render :show, status: :ok, location: @model }
# if nothing matches, you get `ActionController::UnknownFormat` error.
# the one you get when trying to render `format.js`, but Turbo
# doesn't handle js format, so it doesn't send `Accept: text/javascript`
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @model.errors, status: :unprocessable_entity }
end
end
When you use respond_to
block, rails will automatically set some render options, one of which is :content_type
which in turn sets Content-Type
response header. So if format.html
block runs it sets Content-Type: text/html
which turbo knows how to handle by redirecting or replacing the page content.
The important part is that we went from turbo_stream format to html. Needless to say that html needs to render html looking response and turbo_stream has to be a <turbo-stream>
tag.
What happens when you don't use respond_to
block. Rails will try to render a template that matches the format. For example, turbo_stream request will look for show.turbo_stream.erb
then show.html.erb
then show.erb
, like this:
render :show, formats: [:turbo_stream, :html]
Let's say it finds show.html.erb
, because there is an html
extension present, rails will set response Content-Type to text/html
and everything works as expected.
However, if it finds show.erb
, there is nothing to tell rails that it is in html format, we didn't use respond_to
block so response Content-Type is not set explicitly, the only option left is to fallback to the first Accept type, which is turbo_stream. Now you're rendering an html template but response type is turbo_stream, and there is no <turbo-stream>
tag.
When turbo sees a turbo_stream response it appends it to the document, which is what you're seeing. Since there are no <turbo-stream> instructions, it just stays untouched.
Long story short, something along the way needs to set the correct content type.
Either use respond_to
block which will set necessary render options. Or set render options yourself: content_type: "text/html"
. Or let rails know the content type by using html.erb
and turbo_stream.erb
extensions.
Note, when you redirect, turbo_stream content type stays, because turbo handles the redirect on the front end. If there is no html.erb
extension and no respond_to
block in the show action, you get a content type mismatch.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论