在Rails中,Turbo只是将来自服务器的响应附加到当前页面的末尾。

huangapple go评论61阅读模式
英文:

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 &quot;Error: no test specified&quot; && 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&#39;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&#39;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.erbturbo_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-Types 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&#39;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&#39;t handle js format, so it doesn&#39;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 &lt;turbo-stream&gt; 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 &lt;turbo-stream&gt; tag.

When turbo sees a turbo_stream response it appends it to the document, which is what you're seeing. Since there are no &lt;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: &quot;text/html&quot;. 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.

huangapple
  • 本文由 发表于 2023年2月16日 11:57:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定