hotwired/turbo-rails

Adding X-Turbo-Request-Id to custom fetch requests

Closed this issue · 2 comments

There's a breakdown in the logic around using belongs_to touch: true on associated models to trigger refreshes of a master model as described in the documentation:

# This works great in hierarchical structures, where the child record touches parent records automatically
# to invalidate the cache:
#
# class Column < ApplicationRecord
# belongs_to :board, touch: true # +Board+ will trigger a page refresh on column changes
# end

In Rails, touch is really touch_later:

https://github.com/rails/rails/blob/ec667e5f114df58087493096253541f1034815af/activerecord/lib/active_record/associations/builder/belongs_to.rb#L44

I couldn't see that this was enqueuing a background job but it does seem to defer the touching in some way that loses the Turbo.current_request_id:

https://rubydoc.info/github/hotwired/turbo-rails/Turbo%2FStreams%2FActionHelper:turbo_stream_refresh_tag

Not sure how to proceed with this. If it really is enqueuing a seperate job, then the current_request_id is well lost before Turbo can get its hands on it. If it's simply deferred within the same request but we're somehow losing the current_request_id then perhaps there's a way forward to fix it.

Any thoughts before I dig in?

It's useful that the browser making the database change doesn't refresh its page.

Actually, it's more likely to be a problem with rails/request.js not passing through X-Turbo-Request-Id in requests... Will dig deeper.

My hunch was correct. Turbo provides a modified fetch that can be imported:

import { fetch } from '@hotwired/turbo'

This generates and adds X-Turbo-Request-Id as a header to the request. I manually queried and added the X-CSRF-Token header but perhaps there is an even more refined version of fetch in Turbo that does this too. All I could find was FetchRequest which seemed a bit too complicated for my needs. My final code (a kind of beacon alternative to unlock a model instance even when the browser is closing) is:

unlock() {
  if (this.hasUnlockUrlValue) {
    fetch(this.unlockUrlValue, { method: 'POST', keepalive: true, headers: {
      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    } })
  }
}