mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-03-25 01:14:43 +00:00
Ensuring good caching for the most popular pages. Added tests.
This commit is contained in:
13
app/views/schools/_wrestler_row_cells.html.erb
Normal file
13
app/views/schools/_wrestler_row_cells.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<% if local_assigns[:school_permission_key].present? %>
|
||||||
|
<% wrestler_path_with_key = wrestler_path(wrestler) %>
|
||||||
|
<% wrestler_path_with_key += "?school_permission_key=#{school_permission_key}" %>
|
||||||
|
<td><%= link_to wrestler.name, wrestler_path_with_key %></td>
|
||||||
|
<% else %>
|
||||||
|
<td><%= link_to wrestler.name, wrestler_path(wrestler) %></td>
|
||||||
|
<% end %>
|
||||||
|
<td><%= wrestler.weight.max %></td>
|
||||||
|
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
||||||
|
<td><%= wrestler.original_seed %></td>
|
||||||
|
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
||||||
|
<td><%= "Yes" if wrestler.extra? %></td>
|
||||||
|
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
||||||
@@ -54,19 +54,8 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<% @wrestlers.sort_by { |w| w.weight.max }.each do |wrestler| %>
|
<% @wrestlers.sort_by { |w| w.weight.max }.each do |wrestler| %>
|
||||||
<% if params[:school_permission_key].present? %>
|
<% if params[:school_permission_key].present? %>
|
||||||
<!-- No caching when school_permission_key is present -->
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<%= render "schools/wrestler_row_cells", wrestler: wrestler, school_permission_key: params[:school_permission_key] %>
|
||||||
<% wrestler_path_with_key = wrestler_path(wrestler) %>
|
|
||||||
<% wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
|
||||||
<%= link_to wrestler.name, wrestler_path_with_key %>
|
|
||||||
</td>
|
|
||||||
<td><%= wrestler.weight.max %></td>
|
|
||||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
|
||||||
<td><%= wrestler.original_seed %></td>
|
|
||||||
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
|
||||||
<td><%= "Yes" if wrestler.extra? %></td>
|
|
||||||
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
|
||||||
|
|
||||||
<% if can? :manage, wrestler.school %>
|
<% if can? :manage, wrestler.school %>
|
||||||
<td>
|
<td>
|
||||||
@@ -86,29 +75,16 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</tr>
|
</tr>
|
||||||
<% else %>
|
<% else %>
|
||||||
<!-- Use caching only when school_permission_key is NOT present -->
|
|
||||||
<% cache ["#{wrestler.id}_school_show", @school] do %>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= link_to wrestler.name, wrestler_path(wrestler) %></td>
|
<% cache ["school_show_wrestler_cells", wrestler] do %>
|
||||||
<td><%= wrestler.weight.max %></td>
|
<%= render "schools/wrestler_row_cells", wrestler: wrestler %>
|
||||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
|
||||||
<td><%= wrestler.original_seed %></td>
|
|
||||||
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
|
||||||
<td><%= "Yes" if wrestler.extra? %></td>
|
|
||||||
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can? :manage, wrestler.school %>
|
<% if can? :manage, wrestler.school %>
|
||||||
<td>
|
<td>
|
||||||
<% edit_wrestler_path_with_key = edit_wrestler_path(wrestler) %>
|
<%= link_to edit_wrestler_path(wrestler), class: "text-decoration-none" do %>
|
||||||
<% edit_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
|
||||||
|
|
||||||
<% delete_wrestler_path_with_key = wrestler_path(wrestler) %>
|
|
||||||
<% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
|
||||||
|
|
||||||
<%= link_to edit_wrestler_path_with_key, class: "text-decoration-none" do %>
|
|
||||||
<span class="fas fa-edit" aria-hidden="true"></span>
|
<span class="fas fa-edit" aria-hidden="true"></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to delete_wrestler_path_with_key, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
|
<%= link_to wrestler_path(wrestler), data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
|
||||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<% @final_match.each do |match| %>
|
<% @final_match.each do |match| %>
|
||||||
|
<% cache ["bracket_final_match", match, match.wrestler1, match.wrestler2, @winner_place, params[:print].to_s] do %>
|
||||||
<div class="round">
|
<div class="round">
|
||||||
<div class="game">
|
<div class="game">
|
||||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||||
@@ -10,4 +11,5 @@
|
|||||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<div class="round">
|
<div class="round">
|
||||||
<% @round_matches.sort_by{|m| m.bracket_position_number}.each do |match| %>
|
<% @round_matches.sort_by{|m| m.bracket_position_number}.each do |match| %>
|
||||||
|
<% cache ["bracket_round_match", match, match.wrestler1, match.wrestler2, params[:print].to_s] do %>
|
||||||
<div class="game">
|
<div class="game">
|
||||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||||
<% if params[:print] %>
|
<% if params[:print] %>
|
||||||
@@ -11,5 +12,6 @@
|
|||||||
<div class="bout-number"><%= match.bracket_position %></div>
|
<div class="bout-number"><%= match.bracket_position %></div>
|
||||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
6
app/views/tournaments/_team_score_row.html.erb
Normal file
6
app/views/tournaments/_team_score_row.html.erb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<% cache ["team_score_row", school, rank] do %>
|
||||||
|
<tr>
|
||||||
|
<td><%= rank %>. <%= school.name %> (<%= school.abbreviation %>)</td>
|
||||||
|
<td><%= school.page_score_string %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
@@ -31,14 +31,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<% (local_assigns[:matches] || tournament.up_matches_unassigned_matches).each do |m| %>
|
<%= render partial: "tournaments/up_matches_unassigned_row", collection: (local_assigns[:matches] || tournament.up_matches_unassigned_matches), as: :match %>
|
||||||
<tr>
|
|
||||||
<td>Round <%= m.round %></td>
|
|
||||||
<td><%= m.bout_number %></td>
|
|
||||||
<td><%= m.weight_max %></td>
|
|
||||||
<td><%= m.w1_bracket_name %> vs. <%= m.w2_bracket_name %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<% queue1_match, queue2_match, queue3_match, queue4_match = mat.queue_matches %>
|
<% queue1_match, queue2_match, queue3_match, queue4_match = mat.queue_matches %>
|
||||||
<% cache ["up_matches_mat_row", mat, mat.queue1, mat.queue2, mat.queue3, mat.queue4] do %>
|
<% queue_match_dependencies = [queue1_match, queue2_match, queue3_match, queue4_match].compact.flat_map { |match| [match, match.wrestler1, match.wrestler2] } %>
|
||||||
|
<% cache ["up_matches_mat_row", mat, *queue_match_dependencies] do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= mat.name %></td>
|
<td><%= mat.name %></td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<% cache ["up_matches_unassigned_row", match, match.wrestler1, match.wrestler2] do %>
|
||||||
|
<tr>
|
||||||
|
<td>Round <%= match.round %></td>
|
||||||
|
<td><%= match.bout_number %></td>
|
||||||
|
<td><%= match.weight_max %></td>
|
||||||
|
<td><%= match.w1_bracket_name %> vs. <%= match.w2_bracket_name %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<% cache ["#{@weight.id}_bracket", @weight] do %>
|
<% cache ["#{@weight.id}_bracket", @weight, params[:print].to_s] do %>
|
||||||
<%= render 'bracket_partial' %>
|
<%= render 'bracket_partial' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @tournament.tournament_type == "Pool to bracket" %>
|
<% if @tournament.tournament_type == "Pool to bracket" %>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
<% team_scores_last_updated = @schools.map(&:updated_at).compact.max&.utc&.to_fs(:nsec) %>
|
||||||
<% cache ["#{@tournament.id}_team_scores", @tournament] do %>
|
<% cache ["team_scores", @tournament.id, @schools.size, team_scores_last_updated] do %>
|
||||||
|
|
||||||
<table class="pagebreak table table-striped table-bordered">
|
<table class="pagebreak table table-striped table-bordered">
|
||||||
<h3>Team Scores</h3>
|
<h3>Team Scores</h3>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -11,11 +10,8 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @schools.each do |school| %>
|
<% @schools.each_with_index do |school, index| %>
|
||||||
<tr>
|
<%= render "tournaments/team_score_row", school: school, rank: index + 1 %>
|
||||||
<td><%= @schools.index(school) + 1 %>. <%= school.name %> (<%= school.abbreviation %>)</td>
|
|
||||||
<td><%= school.page_score_string %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
10
app/views/weights/_readonly_wrestler_row.html.erb
Normal file
10
app/views/weights/_readonly_wrestler_row.html.erb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<% cache ["weight_show_wrestler_row", wrestler] do %>
|
||||||
|
<tr>
|
||||||
|
<td><%= link_to wrestler.name, wrestler %></td>
|
||||||
|
<td><%= wrestler.school.name %></td>
|
||||||
|
<td><%= wrestler.original_seed %></td>
|
||||||
|
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
||||||
|
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
||||||
|
<td><%= "Yes" if wrestler.extra? %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
@@ -14,35 +14,36 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]}.each do |wrestler| %>
|
<% sorted_wrestlers = @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]} %>
|
||||||
|
<% if can? :manage, @tournament %>
|
||||||
|
<% sorted_wrestlers.each do |wrestler| %>
|
||||||
<% if wrestler.weight_id == @weight.id %>
|
<% if wrestler.weight_id == @weight.id %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= link_to "#{wrestler.name}", wrestler %></td>
|
<td><%= link_to wrestler.name, wrestler %></td>
|
||||||
<td><%= wrestler.school.name %></td>
|
<td><%= wrestler.school.name %></td>
|
||||||
<td>
|
<td>
|
||||||
<% if can? :manage, @tournament %>
|
|
||||||
<%= fields_for "wrestler[]", wrestler do |w| %>
|
<%= fields_for "wrestler[]", wrestler do |w| %>
|
||||||
<%= w.text_field :original_seed %>
|
<%= w.text_field :original_seed %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
|
||||||
<%= wrestler.original_seed %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
</td>
|
||||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
||||||
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
||||||
<td><% if wrestler.extra? == true %>
|
<td><%= "Yes" if wrestler.extra? %></td>
|
||||||
Yes
|
|
||||||
<% end %></td>
|
|
||||||
<% if can? :manage, @tournament %>
|
|
||||||
<td>
|
<td>
|
||||||
<%= link_to wrestler, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." }, class: "text-decoration-none" do %>
|
<%= link_to wrestler, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." }, class: "text-decoration-none" do %>
|
||||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<% end %>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<% sorted_wrestlers.each do |wrestler| %>
|
||||||
|
<% if wrestler.weight_id == @weight.id %>
|
||||||
|
<%= render "weights/readonly_wrestler_row", wrestler: wrestler %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br><p>*All wrestlers without a seed (determined by tournament director) will be assigned a random bracket line.</p>
|
<br><p>*All wrestlers without a seed (determined by tournament director) will be assigned a random bracket line.</p>
|
||||||
|
|||||||
143
test/controllers/school_show_cache_test.rb
Normal file
143
test/controllers/school_show_cache_test.rb
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class SchoolShowCacheTest < ActionController::TestCase
|
||||||
|
tests SchoolsController
|
||||||
|
|
||||||
|
setup do
|
||||||
|
create_double_elim_tournament_single_weight_1_6(8)
|
||||||
|
@tournament.update!(user_id: users(:one).id)
|
||||||
|
@school = @tournament.schools.first
|
||||||
|
|
||||||
|
sign_in users(:one)
|
||||||
|
|
||||||
|
@original_perform_caching = ActionController::Base.perform_caching
|
||||||
|
ActionController::Base.perform_caching = true
|
||||||
|
Rails.cache.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
teardown do
|
||||||
|
Rails.cache.clear
|
||||||
|
ActionController::Base.perform_caching = @original_perform_caching
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school show wrestler cell fragments hit cache and invalidate after wrestler update" do
|
||||||
|
first_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_events), :>, 0, "Expected initial school show render to write wrestler cell fragments"
|
||||||
|
|
||||||
|
second_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_events), "Expected repeat school show render to reuse wrestler cell fragments"
|
||||||
|
assert_operator cache_hits(second_events), :>, 0, "Expected repeat school show render to hit wrestler cell cache"
|
||||||
|
|
||||||
|
wrestler = @school.wrestlers.first
|
||||||
|
third_events = cache_events_for_school_show do
|
||||||
|
wrestler.touch
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate school show wrestler cell cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school show does not leak manage-only controls from cache across users" do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
assert_includes response.body, "New Wrestler"
|
||||||
|
assert_match(/fa-trash-alt/, response.body)
|
||||||
|
assert_match(/fa-edit/, response.body)
|
||||||
|
|
||||||
|
sign_out
|
||||||
|
|
||||||
|
spectator_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_hits(spectator_events), :>, 0, "Expected spectator request to hit wrestler cell cache warmed by owner"
|
||||||
|
assert_not_includes response.body, "New Wrestler"
|
||||||
|
assert_no_match(/fa-trash-alt/, response.body)
|
||||||
|
assert_no_match(/fa-edit/, response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school show with school_permission_key bypasses cached wrestler cell fragments" do
|
||||||
|
@school.update!(permission_key: SecureRandom.uuid)
|
||||||
|
sign_out
|
||||||
|
|
||||||
|
key_request_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id, school_permission_key: @school.permission_key }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 0, cache_writes(key_request_events), "Expected school_permission_key request to bypass cached wrestler cells"
|
||||||
|
assert_equal 0, cache_hits(key_request_events), "Expected school_permission_key request to avoid reading cached wrestler cells"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "completing a match expires school show wrestler cell caches" do
|
||||||
|
warm_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(warm_events), :>, 0, "Expected initial school show render to warm wrestler cell cache"
|
||||||
|
|
||||||
|
wrestler = @school.wrestlers.first
|
||||||
|
assert wrestler, "Expected a wrestler for match-completion cache test"
|
||||||
|
match = wrestler.unfinished_matches.first || wrestler.all_matches.first
|
||||||
|
assert match, "Expected a match involving school wrestler"
|
||||||
|
|
||||||
|
winner_id = match.w1 || match.w2
|
||||||
|
assert winner_id, "Expected match to have at least one wrestler slot"
|
||||||
|
match.update!(
|
||||||
|
finished: 1,
|
||||||
|
winner_id: winner_id,
|
||||||
|
win_type: "Decision",
|
||||||
|
score: "1-0"
|
||||||
|
)
|
||||||
|
|
||||||
|
post_action_events = cache_events_for_school_show do
|
||||||
|
get :show, params: { id: @school.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire school show wrestler cell cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def sign_out
|
||||||
|
@request.session[:user_id] = nil
|
||||||
|
@controller.instance_variable_set(:@current_user, nil)
|
||||||
|
@controller.instance_variable_set(:@current_ability, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_events_for_school_show
|
||||||
|
events = []
|
||||||
|
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||||
|
key = payload[:key].to_s
|
||||||
|
next unless key.include?("school_show_wrestler_cells")
|
||||||
|
|
||||||
|
events << { name: name, hit: payload[:hit] }
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveSupport::Notifications.subscribed(
|
||||||
|
subscriber,
|
||||||
|
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||||
|
) do
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
|
||||||
|
events
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_writes(events)
|
||||||
|
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_hits(events)
|
||||||
|
events.count do |event|
|
||||||
|
event[:name] == "cache_fetch_hit.active_support" ||
|
||||||
|
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
164
test/controllers/tournament_pages_cache_test.rb
Normal file
164
test/controllers/tournament_pages_cache_test.rb
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class TournamentPagesCacheTest < ActionController::TestCase
|
||||||
|
tests TournamentsController
|
||||||
|
|
||||||
|
setup do
|
||||||
|
create_double_elim_tournament_single_weight_1_6(8)
|
||||||
|
@tournament.update!(user_id: users(:one).id)
|
||||||
|
@weight = @tournament.weights.first
|
||||||
|
|
||||||
|
sign_in users(:one)
|
||||||
|
|
||||||
|
@original_perform_caching = ActionController::Base.perform_caching
|
||||||
|
ActionController::Base.perform_caching = true
|
||||||
|
Rails.cache.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
teardown do
|
||||||
|
Rails.cache.clear
|
||||||
|
ActionController::Base.perform_caching = @original_perform_caching
|
||||||
|
end
|
||||||
|
|
||||||
|
test "team_scores cache hits on repeat render and rewrites after school update" do
|
||||||
|
first_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||||
|
get :team_scores, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_events), :>, 0, "Expected initial team_scores render to write fragments"
|
||||||
|
|
||||||
|
second_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||||
|
get :team_scores, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_events), "Expected repeat team_scores render to reuse fragments"
|
||||||
|
assert_operator cache_hits(second_events), :>, 0, "Expected repeat team_scores render to hit cache"
|
||||||
|
|
||||||
|
school = @tournament.schools.first
|
||||||
|
third_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||||
|
school.update!(score: (school.score || 0) + 1)
|
||||||
|
get :team_scores, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(third_events), :>, 0, "Expected school score update to invalidate team_scores cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "bracket cache hits on repeat render and rewrites after match update" do
|
||||||
|
key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
|
||||||
|
|
||||||
|
first_events = cache_events_for(key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_events), :>, 0, "Expected initial bracket render to write fragments"
|
||||||
|
|
||||||
|
second_events = cache_events_for(key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_events), "Expected repeat bracket render to reuse fragments"
|
||||||
|
assert_operator cache_hits(second_events), :>, 0, "Expected repeat bracket render to hit cache"
|
||||||
|
|
||||||
|
match = @weight.matches.first
|
||||||
|
third_events = cache_events_for(key_markers) do
|
||||||
|
match.touch
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(third_events), :>, 0, "Expected match update to invalidate bracket cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "bracket cache separates print and non-print variants" do
|
||||||
|
key_markers = [@weight.id.to_s + "_bracket"]
|
||||||
|
|
||||||
|
non_print_events = cache_events_for(key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(non_print_events), :>, 0, "Expected non-print bracket render to write a page fragment"
|
||||||
|
assert_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected non-print bracket view to include spectate links")
|
||||||
|
|
||||||
|
first_print_events = cache_events_for(key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_print_events), :>, 0, "Expected first print bracket render to write a separate page fragment"
|
||||||
|
assert_no_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected print bracket view to omit spectate links")
|
||||||
|
|
||||||
|
second_print_events = cache_events_for(key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_print_events), "Expected repeat print bracket render to reuse print cache fragment"
|
||||||
|
assert_operator cache_hits(second_print_events), :>, 0, "Expected repeat print bracket render to hit cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "completing a match expires team_scores and bracket caches" do
|
||||||
|
team_warm_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||||
|
get :team_scores, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(team_warm_events), :>, 0, "Expected initial team_scores render to warm cache"
|
||||||
|
|
||||||
|
bracket_key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
|
||||||
|
bracket_warm_events = cache_events_for(bracket_key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(bracket_warm_events), :>, 0, "Expected initial bracket render to warm cache"
|
||||||
|
|
||||||
|
match = @weight.matches.where(finished: [nil, 0]).first || @weight.matches.first
|
||||||
|
assert match, "Expected a match to complete for expiration test"
|
||||||
|
|
||||||
|
match.update!(
|
||||||
|
finished: 1,
|
||||||
|
winner_id: match.w1 || match.w2,
|
||||||
|
win_type: "Decision",
|
||||||
|
score: "1-0"
|
||||||
|
)
|
||||||
|
|
||||||
|
team_post_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||||
|
get :team_scores, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(team_post_events), :>, 0, "Expected completed match to expire team_scores cache"
|
||||||
|
|
||||||
|
bracket_post_events = cache_events_for(bracket_key_markers) do
|
||||||
|
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(bracket_post_events), :>, 0, "Expected completed match to expire bracket cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cache_events_for(key_markers)
|
||||||
|
events = []
|
||||||
|
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||||
|
key = payload[:key].to_s
|
||||||
|
next unless key_markers.any? { |marker| key.include?(marker) }
|
||||||
|
|
||||||
|
events << { name: name, hit: payload[:hit] }
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveSupport::Notifications.subscribed(
|
||||||
|
subscriber,
|
||||||
|
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||||
|
) do
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
|
||||||
|
events
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_writes(events)
|
||||||
|
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_hits(events)
|
||||||
|
events.count do |event|
|
||||||
|
event[:name] == "cache_fetch_hit.active_support" ||
|
||||||
|
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -73,13 +73,86 @@ class UpMatchesCacheTest < ActionController::TestCase
|
|||||||
assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite"
|
assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "up_matches unassigned row fragments hit cache and invalidate after unassigned match update" do
|
||||||
|
key_markers = %w[up_matches_unassigned_row]
|
||||||
|
|
||||||
|
first_events = cache_events_for_up_matches(key_markers) do
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_events), :>, 0, "Expected initial unassigned row render to write fragments"
|
||||||
|
|
||||||
|
second_events = cache_events_for_up_matches(key_markers) do
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_events), "Expected repeat unassigned row render to reuse cached fragments"
|
||||||
|
assert_operator cache_hits(second_events), :>, 0, "Expected repeat unassigned row render to hit cache"
|
||||||
|
|
||||||
|
unassigned_match = @tournament.up_matches_unassigned_matches.first
|
||||||
|
assert unassigned_match, "Expected at least one unassigned match for cache invalidation test"
|
||||||
|
|
||||||
|
third_events = cache_events_for_up_matches(key_markers) do
|
||||||
|
unassigned_match.touch
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(third_events), :>, 0, "Expected unassigned match update to invalidate unassigned row fragment"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "completing an on-mat match expires up_matches cached fragments" do
|
||||||
|
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
|
||||||
|
|
||||||
|
mat = @tournament.mats.detect { |m| m.queue1_match.present? }
|
||||||
|
assert mat, "Expected a mat with a queued match"
|
||||||
|
match = mat.queue1_match
|
||||||
|
assert match, "Expected queue1 match to complete"
|
||||||
|
|
||||||
|
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||||
|
match.update!(
|
||||||
|
finished: 1,
|
||||||
|
winner_id: match.w1 || match.w2,
|
||||||
|
win_type: "Decision",
|
||||||
|
score: "1-0"
|
||||||
|
)
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire and rewrite up_matches caches"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "manually assigning an unassigned match to a mat queue expires up_matches caches" do
|
||||||
|
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
|
||||||
|
|
||||||
|
unassigned_match = @tournament.up_matches_unassigned_matches.first
|
||||||
|
assert unassigned_match, "Expected at least one unassigned match to manually place on a mat"
|
||||||
|
target_mat = @tournament.mats.first
|
||||||
|
|
||||||
|
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||||
|
target_mat.assign_match_to_queue!(unassigned_match, 4)
|
||||||
|
get :up_matches, params: { id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_operator cache_writes(post_action_events), :>, 0, "Expected manual mat assignment to expire and rewrite up_matches caches"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def cache_events_for_up_matches
|
def cache_events_for_up_matches(key_markers = %w[up_matches_mat_row up_matches_unassigned_row])
|
||||||
events = []
|
events = []
|
||||||
subscriber = lambda do |name, _start, _finish, _id, payload|
|
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||||
key = payload[:key].to_s
|
key = payload[:key].to_s
|
||||||
next unless key.include?("up_matches_mat_row")
|
next unless key_markers.any? { |marker| key.include?(marker) }
|
||||||
|
|
||||||
events << { name: name, hit: payload[:hit] }
|
events << { name: name, hit: payload[:hit] }
|
||||||
end
|
end
|
||||||
|
|||||||
107
test/controllers/weight_show_cache_test.rb
Normal file
107
test/controllers/weight_show_cache_test.rb
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class WeightShowCacheTest < ActionController::TestCase
|
||||||
|
tests WeightsController
|
||||||
|
|
||||||
|
setup do
|
||||||
|
create_a_tournament_with_single_weight("Regular Double Elimination 1-6", 8)
|
||||||
|
@tournament.update!(user_id: users(:one).id)
|
||||||
|
@weight = @tournament.weights.first
|
||||||
|
|
||||||
|
@original_perform_caching = ActionController::Base.perform_caching
|
||||||
|
ActionController::Base.perform_caching = true
|
||||||
|
Rails.cache.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
teardown do
|
||||||
|
Rails.cache.clear
|
||||||
|
ActionController::Base.perform_caching = @original_perform_caching
|
||||||
|
end
|
||||||
|
|
||||||
|
test "weight show readonly row fragments hit cache and invalidate after wrestler update" do
|
||||||
|
first_events = cache_events_for_weight_show do
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(first_events), :>, 0, "Expected initial weight show render to write readonly row fragments"
|
||||||
|
|
||||||
|
second_events = cache_events_for_weight_show do
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_equal 0, cache_writes(second_events), "Expected repeat weight show render to reuse readonly row fragments"
|
||||||
|
assert_operator cache_hits(second_events), :>, 0, "Expected repeat weight show render to hit readonly row cache"
|
||||||
|
|
||||||
|
wrestler = @weight.wrestlers.first
|
||||||
|
third_events = cache_events_for_weight_show do
|
||||||
|
wrestler.touch
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate weight show readonly row cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "weight show does not leak manage-only controls from cache across users" do
|
||||||
|
sign_in users(:one)
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
assert_includes response.body, "Save Seeds"
|
||||||
|
assert_match(/fa-trash-alt/, response.body)
|
||||||
|
assert_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||||
|
|
||||||
|
sign_out
|
||||||
|
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
assert_not_includes response.body, "Save Seeds"
|
||||||
|
assert_no_match(/fa-trash-alt/, response.body)
|
||||||
|
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||||
|
|
||||||
|
spectator_cache_events = cache_events_for_weight_show do
|
||||||
|
get :show, params: { id: @weight.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
assert_operator cache_hits(spectator_cache_events), :>, 0, "Expected repeat spectator request to hit readonly wrestler row cache"
|
||||||
|
assert_not_includes response.body, "Save Seeds"
|
||||||
|
assert_no_match(/fa-trash-alt/, response.body)
|
||||||
|
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def sign_out
|
||||||
|
@request.session[:user_id] = nil
|
||||||
|
@controller.instance_variable_set(:@current_user, nil)
|
||||||
|
@controller.instance_variable_set(:@current_ability, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_events_for_weight_show
|
||||||
|
events = []
|
||||||
|
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||||
|
key = payload[:key].to_s
|
||||||
|
next unless key.include?("weight_show_wrestler_row")
|
||||||
|
|
||||||
|
events << { name: name, hit: payload[:hit] }
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveSupport::Notifications.subscribed(
|
||||||
|
subscriber,
|
||||||
|
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||||
|
) do
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
|
||||||
|
events
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_writes(events)
|
||||||
|
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_hits(events)
|
||||||
|
events.count do |event|
|
||||||
|
event[:name] == "cache_fetch_hit.active_support" ||
|
||||||
|
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user