Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/controllers/alchemy/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ def render_page
end

def set_expiration_headers
if must_not_cache?
if page_cache_disabled_by_elements?
# no-cache still allows storage and conditional 304 responses, which would skip element-level cache variants.
no_store
elsif must_not_cache?
expires_now
else
expires_in @page.expiration_time, {public: !@page.restricted}.merge(caching_options)
Expand Down Expand Up @@ -247,7 +250,11 @@ def render_fresh_page?

# don't cache pages if we have flash message to display or the page has caching disabled
def must_not_cache?
!caching_enabled? || !@page.cache_page? || flash.present?
!caching_enabled? || !@page.cache_page? || flash.present? || page_cache_disabled_by_elements?
end

def page_cache_disabled_by_elements?
@page&.find_elements&.any? { |element| element.definition.page_cache == false } || false
end

def caching_enabled?
Expand Down
1 change: 1 addition & 0 deletions app/models/alchemy/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Element < BaseRecord
"ingredients",
"message",
"nestable_elements",
"page_cache",
"searchable",
"taggable",
"warning"
Expand Down
1 change: 1 addition & 0 deletions app/models/alchemy/element_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ElementDefinition
attribute :taggable, :boolean, default: false
attribute :compact, :boolean, default: false
attribute :fixed, :boolean, default: false
attribute :page_cache, :boolean, default: true
attribute :ingredients, default: []
attribute :nestable_elements, default: []
attribute :autogenerate, default: []
Expand Down
11 changes: 11 additions & 0 deletions spec/models/alchemy/element_definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module Alchemy
it { is_expected.to have_key(:taggable) }
it { is_expected.to have_key(:compact) }
it { is_expected.to have_key(:fixed) }
it { is_expected.to have_key(:page_cache) }
it { is_expected.to have_key(:ingredients) }
it { is_expected.to have_key(:nestable_elements) }
it { is_expected.to have_key(:autogenerate) }
Expand All @@ -35,6 +36,16 @@ module Alchemy
it { is_expected.to have_key(:searchable) }
end

describe "#page_cache" do
it "defaults to true" do
expect(described_class.new.page_cache).to be(true)
end

it "can be disabled" do
expect(described_class.new(page_cache: false).page_cache).to be(false)
end
end

describe "validations" do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to allow_value("article").for(:name) }
Expand Down
40 changes: 40 additions & 0 deletions spec/requests/alchemy/page_request_caching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,46 @@
end
end

context "with an element-level page cache opt-out" do
around do |example|
Alchemy::ElementDefinition.add({"name" => "uncached_element", "page_cache" => false})
Alchemy::PageDefinition.add({"name" => "uncached_layout", "elements" => ["uncached_element"]})

example.run
ensure
Alchemy::ElementDefinition.reset!
Alchemy::PageDefinition.reset!
end

it "sets no-store when a published element disables page caching" do
create(:alchemy_element, name: "uncached_element", page_version: page.public_version)

get "/#{page.urlname}"

expect(response.headers).to have_key("Cache-Control")
expect(response.headers["Cache-Control"]).to eq("no-store")
end

it "keeps page caching when the page layout allows but does not contain an element that disables page caching" do
uncached_page = create(:alchemy_page, :public, page_layout: "uncached_layout")

get "/#{uncached_page.urlname}"

expect(response.headers).to have_key("Cache-Control")
expect(response.headers["Cache-Control"]).to eq("max-age=60, public, must-revalidate")
end

it "renders without conditional cache revalidation when a published element disables page caching" do
create(:alchemy_element, name: "uncached_element", page_version: page.public_version)

expect_any_instance_of(Alchemy::PagesController).not_to receive(:stale?)

get "/#{page.urlname}", headers: {"If-None-Match" => "\"cached-page\""}

expect(response.status).to eq(200)
end
end

it "does not set last-modified header" do
get "/#{page.urlname}"
expect(response.headers).to_not have_key("Last-Modified")
Expand Down
Loading