Проблема: Турбо просто захлопывает ответ от сервера до конца текущей страницы. Не заменяет страницу, как ожидалось.
Недавно обновился с Rails 6 до 7. Также я переключился с importmap на esbuild.
У меня есть форма, которая отправляет сообщение в метод создания. Контроллер отвечает
if @stamp.save
redirect_to stamps_path, notice: "Input saved"
else
Странная вещь, которая происходит, заключается в том, что ответ добавляется к нижней части исходного кода сверху. Таким образом, в конце исходного кода добавляется новый заголовок из ответа, например
<...> The whole initial html <...>
</body>
<title>Page title</title> <-- The server response gets appended after </body>
<meta .... the whole page gets repeated again
Не совсем уверен, что вам здесь показать, копался во всех настройках и т. д. Я никогда не видел, чтобы 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' # 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"
}
}
Я пытался воспроизвести другое приложение для рельсов, которое у меня есть, используя Turbo. Изучил все настройки и т. д. Другие страницы могут использовать redirect_to и заменять содержимое, как и ожидалось. Я использую haml, поэтому HTML-структура должна быть в порядке.
Я могу представить только один способ, которым это могло бы произойти: у вас есть шаблон show.erb. Просто переименуйте его в show.html.erb (или .haml, .slim, не важно).
По умолчанию формы отправляются как TURBO_STREAM, это то, что показывает rails в логах, однако это не вся картина. Форма отправки запроса устанавливает Accept заголовок:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
# ^
# here is where `TURBO_STREAM` comes from
Это ответы Content-Type, которые турбо ожидает получить. Когда вы используете блок respond_to в действиях контроллера, заголовок Accept определяет, какой блок format вызывать:
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
Когда вы используете блок respond_to, рельсы автоматически устанавливают некоторые параметры рендеринга, одним из которых является :content_type, который, в свою очередь, устанавливает заголовок ответа Content-Type. Поэтому, если блок format.html запускается, он устанавливает Content-Type: text/html, какой турбо знает, как обрабатывать, перенаправляя или заменяя содержимое страницы.
Важно то, что мы перешли от формата 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, нет ничего, что указывало бы на то, что он в формате 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.
Обратите внимание, что при перенаправлении тип содержимого turbo_stream остается, потому что turbo обрабатывает перенаправление на внешнем интерфейсе. Если в действии show нет расширения html.erb и блока respond_to, вы получаете несоответствие типа контента.
О боже, я люблю тебя .. Это было НАИЛУЧШИМ решением и лучшим образованием, которое я мог получить.