diff --git a/README.md b/README.md index 7816e5bee..16bee54e7 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,18 @@ This is a Rails 8.1.0 application built with: For detailed setup and development instructions, please see our [CONTRIBUTING.md](CONTRIBUTING.md) guide. +## Production Maintenance + +### Featured workshops not showing on the home page + +Featured workshop IDs are cached for up to 1 year. The cache auto-invalidates when a workshop's `featured`, `publicly_featured`, or `published` flags change, but stale data can persist if the invalidation races with the save transaction. To force a refresh, visit: + +``` +/?bust_cache=true +``` + +This clears and repopulates the cache for all users. + ## Orphaned Reports When users are deleted from the system, their reports are automatically assigned to a special diff --git a/app/controllers/home/workshops_controller.rb b/app/controllers/home/workshops_controller.rb index f60262130..7d61f9a26 100644 --- a/app/controllers/home/workshops_controller.rb +++ b/app/controllers/home/workshops_controller.rb @@ -2,12 +2,16 @@ module Home class WorkshopsController < ApplicationController def index authorize! :home + if params[:bust_cache] == "true" && current_user&.super_user? + Rails.cache.delete("featured_and_publicly_featured_workshop_ids") + end + ids = Rails.cache.fetch("featured_and_publicly_featured_workshop_ids", expires_in: 1.year) do Workshop.featured_or_publicly_featured.pluck(:id) end base_scope = Workshop.includes(:windows_type, primary_asset: { file_attachment: :blob }, gallery_assets: { file_attachment: :blob }) - .where(id: ids) + .where(id: ids) @workshops = authorized_scope(base_scope, with: HomePolicy).decorate @workshops = @workshops.sort { |x, y| Date.parse(y.date) <=> Date.parse(x.date) } diff --git a/app/views/home/_workshops.html.erb b/app/views/home/_workshops.html.erb index 106743d11..9376401e7 100644 --- a/app/views/home/_workshops.html.erb +++ b/app/views/home/_workshops.html.erb @@ -6,7 +6,7 @@ title: title, subtitle: "Spotlights from our curriculum" %> - <%= turbo_frame_tag "home_workshops", src: home_workshops_path do %> + <%= turbo_frame_tag "home_workshops", src: home_workshops_path(bust_cache: params[:bust_cache].presence) do %> <%= render "workshops_cards_skeleton" %> <% end %> diff --git a/spec/requests/home/workshops_spec.rb b/spec/requests/home/workshops_spec.rb new file mode 100644 index 000000000..5324604a0 --- /dev/null +++ b/spec/requests/home/workshops_spec.rb @@ -0,0 +1,53 @@ +require "rails_helper" + +RSpec.describe "/home/workshops", type: :request do + let(:user) { create(:user) } + let!(:windows_type) { create(:windows_type) } + + before { sign_in user } + + describe "GET /home/workshops" do + it "returns featured workshops" do + workshop = create(:workshop, :published, featured: true, windows_type: windows_type) + + get home_workshops_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include(workshop.title) + end + + it "does not return unfeatured workshops" do + create(:workshop, :published, featured: false, windows_type: windows_type) + + get home_workshops_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include("No workshops available right now.") + end + + context "with bust_cache=true" do + let(:admin) { create(:user, :admin) } + + before do + # Prime the cache with no featured workshops + get home_workshops_path + # Feature a workshop after the cache is set + create(:workshop, :published, featured: true, windows_type: windows_type) + end + + it "clears the cache for admins" do + sign_in admin + + get home_workshops_path, params: { bust_cache: "true" } + + expect(response.body).not_to include("No workshops available right now.") + end + + it "does not clear the cache for non-admins" do + get home_workshops_path, params: { bust_cache: "true" } + + expect(response.body).to include("No workshops available right now.") + end + end + end +end