ja ja ja ru ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja en ja ja ja ja ja en pt ja ja ja ja ja ja ja en ja ja en ja ja ja ja ja ja ja ja ja ja ru nl ja en ja ja de ja ja de ja ja nb ja ja nb ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja cv es es ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
なのでlink_toをbutton_toに変更して対応しました。
= button_to('/auth/twitter') { 'Twitterでログイン' }
バリデーションエラー時の対応
TurboはTurbolinksでは未対応だったformのsubmitに対応しています。data-remote=trueなformでなくとも自動でfetchを使い非同期でPOSTします。
バリデーションエラー時に422などのステータスコードを返すと、URLはそのままの状態で画面の差し替えが行われます。つまり普通に(scaffoldで生成したようなアクションの形で)バリデーションエラーのメッセージを含む画面を描画できるようになりました。
Turbo方式とTurbolinks方式を混在させる
savanna.io では ajax_error_rendererというgemを使いSJRでエラーメッセージを差し込む、ということをやっているのでそれを普通のエラーメッセージ用のHTMLを返すように変更する必要があります。
すべてのアクションを一気に変更するのは大変なので、徐々に切り替えられるように、Turbo方式とTurbolinks方式を混在させられるような仕組みを作りました。
Turbolinksはrails-ujsによるajaxでPOSTをする想定です。そのときredirectをしたらTurbolinks.visit()を実行するようにサーバからレスポンスを返します。これをturbolinks-railsというgemが実現していました。次のコードは、turbolinks-railsと同等のことをTurboで実現するようにしています。このように書くと、data-remote=trueなformからPOSTしたときはturbolinksの挙動で、それ以外はTurboの挙動になります。
# ApplicationController def redirect_to(...) super.tap do visit_location_with_turbo(location) if request.xhr? && !request.get? && !request.format.turbo_stream? end end def visit_location_with_turbo(location) visit_options = { action: 'replace' } script = [] script << 'Turbo.clearCache()' script << "Turbo.visit(#{location.to_json}, #{visit_options.to_json})" self.status = 200 self.response_body = script.join("\n") response.content_type = 'text/javascript' response.headers['X-Xhr-Redirect'] = location end
request.format.turbo_stream?がtrueのときはTurboからのリクエスト経由のリダイレクトなのでなにもしない(通常のredirect_to)ようにしています。
// application.ts import { Turbo } from '@hotwired/turbo-rails' window.Turbo = Turbo
Turboはwebpack環境ではglobalではないので明示的にglobal変数として扱えるようにする必要があります。
Turboではバリデーションエラー時にturbo:loadが発火しない
これであとは頑張ってバリデーションエラー時の処理を置き換えていくだけ…と思いきやそんなことはありません><バリデーションエラー時にturbo:loadが発火しないという現象に遭遇しました。https://github.com/hotwired/turbo/issues/85 を見た限りこれは仕様とのことです…。
savanna.ioではturbo:loadがすべてのページ表示のタイミングで発火するのを期待しているコードがあったので、検討した結果、上記の「Turbo方式とTurbolinks方式を混在させる」で書いたやり方は不採用にしました*2。
結局、turbo-streamを利用するとajax_error_rendererと使用感がほぼ同じになるのでturbo-streamでエラーメッセージを差し込むようにしました。次のようにしてrender_errors_by_turbo_streamを呼び出すと  にエラーメッセージが差し込まれます。
def render_errors_by_turbo_stream(model:) render turbo_stream: turbo_stream.update(:error, partial: 'errors', locals: { model: model }), status: :unprocessable_entity end
- unless model.errors.empty? #error_explanation.alert.alert-danger.error-messages %ul - model.errors.full_messages.each do |msg| %li= msg
[コラム]バリデーションエラー時のリロードの処理
バリデーションエラー時にブラウザをリロードしたときの挙動はTurbo使用時とそうでないときとで違いがあります。Turbo使用時はformのページがリロードされます。Turbo未使用時は「フォームの内容を再送しますか?」といったダイアログが表示されます。
これはHistroy APIを利用している以上仕方がない*3ですが、Turbolinksのときと比べるとだいぶ改善された感じがあります。Turbolinks利用時かつdata-remote=trueなしなフォームのときは、newアクションのformにいるはずがリロードするとindexアクションにアクセスしてしまう、という事が起きていました。
参考: Turbolinks5でPOSTするときはajax経由のほうが良いのかも - おもしろwebサービス開発日記チラシの裏
data-disable-withに対応する
turbo-streamを使うと動的にエラーメッセージを差し込むことができてめでたしめでたし、かと思いきや、バリデーションエラー時にsubmitボタンがdisableのまま、という状況に遭遇しました。
form_withなどでajaxを利用してPOSTしたとき、ajax終了時に自動でdisable状態を解除するようにrails-ujsで定義されています。turboはrails-ujsを利用していないので、独自で対応する必要があります。次のように、フォームsubmit後のバリデーションエラーで、rails-ujsのdisable状態を解除するためのメソッドを呼び出すようにしました。
document.addEventListener('turbo:submit-end', (event: Event) => { if (!event.detail.success) { Rails.enableElement(event.detail.formSubmission.formElement) } })
assets precompileの対象から外す
turbo-railsは今のところassets precompileできるなら、turboをその対象として自動で挿入するという仕様になっています。savanna.io は基本webpackerなのですがassetsを使っているgem(administrate)があるのでassets pipelineはオフにしていません。そしてturbo-railsが提供しているjsはES6の書き方なので、本番デプロイ時にuglifierによる圧縮処理で失敗しました。そもそもassetsとして提供されているjsは使わないので、次のようにして対処しました。
# config/application.rb config.after_initialize do config.assets.precompile -= ['turbo'] end
その他turbolinksやrails-ujsに依存している処理を書き換える
あとは細かい変更なので概要だけ書いておきます。
turbolinks化でgoogle-analyticsを使うために google-analytics-turbolinkというgemを使っていたのをやめた
「保存されていない変更があります。移動してよろしいですか?」を実装するためにrails-ujsのイベントをhookしていたのをturboのイベントにフックするようにした
ここまで見てきたように、TurbolinksからTurboの移行に関してはそれなりにハマりどころがあります
ただ明らかにTurbolinks時代から改善が進んでいて体験が良くなっているので、気になっている人は一度試してみるのをおすすめします
*1:厳密には現時点で7.0.0-beta.4です
*2:便利に使う人もいるかと思うので書き残しています
*3:History APIはGETしか対応していない
willnet 2021-02-14 15:41
iCARE Dev MeetupでHotwireについて話した
僕がお手伝いしているiCAREさん主催のミートアップ、iCARE Dev …
ajax_error_renderer 0.2.0をリリースした
個人的に便利に使っているajax_error_rendererなのだけど、フォ…
turbolinksとform_withを便利に使うためのgemを作った
前提 rails標準のわりに使っている人の少ないturbolinksですが…
ginza.rb 第33回で Rails 5 の新機能について学んだ
3/15は ginza.rb の33回目のミートアップでした。 Ginza.rb 第3…
ginza.rb 第1回を開催して strong_parameters のソースコードを読んだ
7月16日(火)に ginza.rb 第1回を開催しました。 Ginza.rb 第1回…
« iCARE Dev MeetupでHotwireについて話した Railsアプリケーションのテストが失敗した… »
id:willnet はてなブログPro
べんりで楽しいwebサービスをたくさん作りたい
読者です 読者をやめる 読者になる 読者になる
@netwillnetをフォロー
失敗したテストのログだけを出力するぞ
iCARE Dev Meetup #34で登壇しました
`Thread.current[:hoge]`はスレッドローカル変数を参照していると思いきや実際はファイバーローカル変数だった