mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-09 08:10:08 +09:00
Retain content-validated message fragments across unrelated commits
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
module Authentication::SessionLookup
|
||||
def find_session_by_cookie
|
||||
if token = cookies.signed[:session_token]
|
||||
Session.eager_load(:user).find_by(token: token)
|
||||
Session.find_by(token: token)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,11 +26,12 @@ module CachedResponses
|
||||
end
|
||||
|
||||
def combined_fragment_cache_key(key)
|
||||
@fragment_cache_namespace ||= [
|
||||
@response_cache_version, request.base_url, request.script_name, request.format.to_s, I18n.locale,
|
||||
@fragment_cache_context ||= [
|
||||
request.base_url, request.script_name, request.format.to_s, I18n.locale,
|
||||
Current.user&.id, (Digest::SHA256.hexdigest(Current.session.token) if Current.session)
|
||||
].freeze
|
||||
super([ @fragment_cache_namespace, key ])
|
||||
version = @response_cache_version unless Array(key).flatten.any? { |part| part.is_a?(FragmentCache::ContentKey) }
|
||||
super([ version, @fragment_cache_context, key ])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -83,6 +83,22 @@ module MessagesHelper
|
||||
FragmentCache.store.fetch(key) { capture(&block) }
|
||||
end
|
||||
|
||||
def message_fragment_cache_key(message)
|
||||
return message unless controller.perform_caching &&
|
||||
%i[room creator rich_text_body boosts attachment_attachment].all? { |name| message.association(name).loaded? }
|
||||
|
||||
room = message.room
|
||||
body = message.body.body
|
||||
return message if room.direct? || message.attachment? || !body || body.to_html.include?("<action-text-attachment") ||
|
||||
message.boosts.any? { |boost| !boost.association(:booster).loaded? }
|
||||
|
||||
# Collection caching preloads these rows before deriving keys. Read their
|
||||
# actual content: foreign SQL writers need not touch Rails timestamps.
|
||||
dependencies = [ message.attributes, room.attributes.slice("id", "type", "name"), message.creator.attributes,
|
||||
body.to_html, message.boosts.sort_by(&:created_at).map { |boost| [ boost.attributes, boost.booster.attributes ] } ]
|
||||
FragmentCache::ContentKey.new(Digest::SHA256.hexdigest(ActiveSupport::JSON.encode(dependencies)))
|
||||
end
|
||||
|
||||
private
|
||||
def text_message_presentation(body)
|
||||
render = -> do
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Native view caches share one byte budget across every database generation.
|
||||
class FragmentCache
|
||||
# Only callers that fingerprint every rendered dependency may omit the epoch.
|
||||
ContentKey = Data.define(:digest) do
|
||||
def cache_key
|
||||
digest
|
||||
end
|
||||
end
|
||||
|
||||
STORE = ActiveSupport::Cache::MemoryStore.new(size: 64.megabytes)
|
||||
private_constant :STORE
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%# Be sure to check/update messages/_template.html.erb when changing this file %>
|
||||
|
||||
<%# Bump this version when the message presentation filters change what they emit. Editing this line changes the template digest, which busts BOTH this fragment cache and the collection cache that keys on this partial's digest (helper Ruby changes alone don't). %>
|
||||
<% cache [ message, "presentation-v6" ] do %>
|
||||
<% cache [ message_fragment_cache_key(message), "presentation-v7" ] do %>
|
||||
<%= message_tag message do %>
|
||||
<h2 class="message__day-separator"><%= local_datetime_tag message.created_at, style: :date %></h2>
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<%= render partial: "messages/message", collection: @messages, cached: true %>
|
||||
<%= render partial: "messages/message", collection: @messages, cached: ->(message) { message_fragment_cache_key(message) } %>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= turbo_stream.append dom_id(@room, :messages) do %>
|
||||
<%= render partial: "messages/message", collection: @new_messages, cached: true %>
|
||||
<%= render partial: "messages/message", collection: @new_messages, cached: ->(message) { message_fragment_cache_key(message) } %>
|
||||
<% end if @new_messages.any? %>
|
||||
|
||||
<% @updated_messages.each do |message| %>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<%= messages_tag(@room) do %>
|
||||
<%= render "rooms/show/invitation", room: @room %>
|
||||
<%= render partial: "messages/message", collection: @messages, cached: true %>
|
||||
<%= render partial: "messages/message", collection: @messages, cached: ->(message) { message_fragment_cache_key(message) } %>
|
||||
<% end %>
|
||||
|
||||
<%= turbo_stream_from @room, :messages, channel: "RoomMessagesChannel" %>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</div>
|
||||
|
||||
<%= search_results_tag do %>
|
||||
<%= render partial: "messages/message", collection: @messages, cached: true %>
|
||||
<%= render partial: "messages/message", collection: @messages, cached: ->(message) { message_fragment_cache_key(message) } %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -74,6 +74,21 @@ class FragmentRenderingTest < ActionDispatch::IntegrationTest
|
||||
assert_select "form[action=?]", message_boosts_path(@message)
|
||||
end
|
||||
|
||||
test "a commit in another room retains content-validated message collection hits" do
|
||||
ResponseCache.instance.stubs(:budget).returns(0)
|
||||
get room_messages_url(@room)
|
||||
assert_response :success
|
||||
foreign_write("UPDATE rooms SET name = ? WHERE id = ?", "Unrelated room rename", rooms(:pets).id)
|
||||
collections = []
|
||||
ActiveSupport::Notifications.subscribed(->(event) { collections << event.payload }, "render_collection.action_view") do
|
||||
get room_messages_url(@room)
|
||||
end
|
||||
assert_response :success
|
||||
messages = collections.find { |payload| payload[:identifier].end_with?("messages/_message.html.erb") }
|
||||
assert messages
|
||||
assert_equal @room.messages.count, messages[:cache_hits]
|
||||
end
|
||||
|
||||
test "a foreign commit after capture bypasses old native fragment lookups" do
|
||||
ResponseCache.instance.stubs(:budget).returns(0)
|
||||
get room_messages_url(@room)
|
||||
|
||||
Reference in New Issue
Block a user