Turbo Streams + Rails Time Zone Helpers: Date Fragment Updates
0 reputation · 18 Mar 2021, 10:50 UTC
0 reputation · 18 Mar 2021, 10:50 UTC
Goal: Ensure that date/time fragments rendered by Rails view helpers display the correct offset after a Turbo Stream update when a user changes their preferred time zone.
Constraint: Turbo Streams inserts server‑generated HTML without performing any additional client‑side time‑zone conversion, and its cache‑busting flags do not automatically invalidate cached date fragments when the user’s zone changes.
Uncertainty: It is unclear whether developers should rely on re‑rendering the fragment via a new Turbo Stream, employ a Stimulus controller to rewrite the DOM after the update, or adjust caching keys to force a fresh server response.
Questions:
31775 reputation · 18 Mar 2021, 14:41 UTC
When a user changes their preferred time zone, the simplest and most reliable way to update date fragments that are rendered by Turbo Streams is to let the server re‑render the fragment in the new zone and send that markup in the Turbo Stream. Turbo Streams do not perform any client‑side conversion, so the HTML you send must already contain the correctly formatted timestamp.
Stimulus would have to parse the rendered string, apply a JavaScript‑side time‑zone conversion, and replace the innerHTML of the node. That adds complexity, introduces a second source of truth for the format, and can easily get out of sync with the server‑side formatting rules. The Rails l helper (or time_ago_in_words, etc.) already produces the desired string when Time.zone is set, so re‑rendering on the server is the idiomatic Rails pattern.
Time.zone per request. In ApplicationController add a before_action that pulls the user’s zone from session[:time_zone] or a user attribute and assigns it:
before_action :set_time_zone
def set_time_zone
Time.zone = current_user.time_zone.presence || "UTC"
end
def update
@record = Record.find(params[:id])
respond_to do |format|
format.turbo_stream
end
end
The app/views/records/_record.html.erb partial can use:
<%= l @record.updated_at, format: :short %>
Because Time.zone is already set, l outputs the timestamp in the user’s zone.
cache @record, @record.updated_at, current_user.time_zone), include the zone in the cache key so that a zone change forces a new cache entry.
When you use turbo_stream_from or turbo_stream.update, the server can set a Cache‑Control header that expires quickly (e.g. max-age=0, must-revalidate). More robustly, include the user’s time zone in the cache key or in the Turbo Stream’s data-turbo-cache-key attribute. Example:
<%= turbo_stream.update "record_#{@record.id}", partial: "records/record", locals: { record: @record }, cache_key: "record_#{@record.id}_#{current_user.time_zone}" %>
This ensures that a change to session[:time_zone] will cause the next Turbo Stream to fetch fresh markup.
To fine‑tune the cache key, I need to know how the user’s time zone is stored (session, user model, or cookie). Could you confirm that?
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 18 Mar 2021, 12:15 UTC
When you render a Turbo Stream that updates a date fragment, make sure the response contains only the fragment markup—no layout, no extra HTML. In Rails you can achieve this by rendering the partial with layout: false inside the format.turbo_stream block, for example:
def update
@record = Record.find(params[:id])
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace("record_#{@record.id}", partial: "records/record", locals: {record: @record}, layout: false) }
end
end
This guarantees that the time‑zone‑formatted timestamp generated by Rails helpers is the exact payload Turbo replaces, avoiding stray tags or whitespace that could interfere with the stream.