Skip to content

Commit

Permalink
Add support for disabled cache per page
Browse files Browse the repository at this point in the history
A page can have caching disabled via the `cache: false` flag
on the page_layout definition.
  • Loading branch information
tvdeyen committed Nov 12, 2021
1 parent e474f1f commit 140252b
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 70 deletions.
28 changes: 19 additions & 9 deletions app/controllers/alchemy/json_api/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@ def index

jsonapi_filter(page_scope, allowed) do |filtered_pages|
@pages = filtered_pages.result
if stale?(last_modified: @pages.maximum(:published_at), etag: @pages.max_by(&:cache_key)&.cache_key)
# Only load pages with all includes when browser cache is stale
jsonapi_filter(page_scope_with_includes, allowed) do |filtered|
# decorate with our page model that has a eager loaded elements collection
filtered_pages = filtered.result.map { |page| api_page(page) }
jsonapi_paginate(filtered_pages) do |paginated|
render jsonapi: paginated
end
end
if !@pages.all?(&:cache_page?)
render_pages_json(allowed) && return
elsif stale?(last_modified: @pages.maximum(:published_at), etag: @pages.max_by(&:cache_key)&.cache_key)
render_pages_json(allowed)
end
end

expires_in cache_duration, { public: @pages.none?(&:restricted?) }.merge(caching_options)
end

def show
if !@page.cache_page?
render(jsonapi: api_page(load_page)) && return
end

if stale?(last_modified: last_modified_for(@page), etag: @page.cache_key)
# Only load page with all includes when browser cache is stale
render jsonapi: api_page(load_page)
Expand All @@ -36,6 +35,17 @@ def show

private

def render_pages_json(allowed)
# Only load pages with all includes when browser cache is stale
jsonapi_filter(page_scope_with_includes, allowed) do |filtered|
# decorate with our page model that has a eager loaded elements collection
filtered_pages = filtered.result.map { |page| api_page(page) }
jsonapi_paginate(filtered_pages) do |paginated|
render jsonapi: paginated
end
end
end

def cache_duration
ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", 3).to_i.hours
end
Expand Down
4 changes: 4 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
require "alchemy/test_support/factories"
end

require "alchemy/test_support/config_stubbing"

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
Expand Down Expand Up @@ -81,4 +83,6 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")

config.include Alchemy::TestSupport::ConfigStubbing
end
175 changes: 114 additions & 61 deletions spec/requests/alchemy/json_api/pages_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,62 @@
)
end

it "sets public cache headers" do
get alchemy_json_api.page_path(page)
expect(response.headers["Last-Modified"]).to eq(page.published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
end

context "if page is restricted" do
let(:page) do
FactoryBot.create(
:alchemy_page,
:public,
:restricted,
published_at: DateTime.yesterday,
)
context "with caching enabled" do
before do
allow(Rails.application.config.action_controller).to receive(:perform_caching) { true }
stub_alchemy_config(:cache_pages, true)
end

it "sets private cache headers" do
it "sets public cache headers" do
get alchemy_json_api.page_path(page)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, private, must-revalidate")
expect(response.headers["Last-Modified"]).to eq(page.published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
end
end

context "if browser sends fresh cache headers" do
it "returns not modified" do
get alchemy_json_api.page_path(page)
etag = response.headers["ETag"]
get alchemy_json_api.page_path(page),
headers: {
"If-Modified-Since" => page.published_at.utc.httpdate,
"If-None-Match" => etag,
}
expect(response.status).to eq(304)
context "if page is restricted" do
let(:page) do
FactoryBot.create(
:alchemy_page,
:public,
:restricted,
published_at: DateTime.yesterday,
)
end

it "sets private cache headers" do
get alchemy_json_api.page_path(page)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, private, must-revalidate")
end
end

context "if cache for page is disabled" do
let(:page) do
FactoryBot.create(
:alchemy_page,
:public,
page_layout: "contact",
published_at: DateTime.yesterday,
)
end

it "sets no cache headers" do
get alchemy_json_api.page_path(page)
expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate")
end
end

context "if browser sends fresh cache headers" do
it "returns not modified" do
get alchemy_json_api.page_path(page)
etag = response.headers["ETag"]
get alchemy_json_api.page_path(page),
headers: {
"If-Modified-Since" => page.published_at.utc.httpdate,
"If-None-Match" => etag,
}
expect(response.status).to eq(304)
end
end
end
end
Expand Down Expand Up @@ -153,39 +176,62 @@
context "as anonymous user" do
let!(:pages) { [public_page] }

it "sets public cache headers of latest published page" do
get alchemy_json_api.pages_path
expect(response.headers["Last-Modified"]).to eq(pages.max_by(&:published_at).published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
end

context "if one page is restricted" do
let!(:restricted_page) do
FactoryBot.create(
:alchemy_page,
:public,
:restricted,
published_at: DateTime.yesterday,
)
context "with caching enabled" do
before do
allow(Rails.application.config.action_controller).to receive(:perform_caching) { true }
stub_alchemy_config(:cache_pages, true)
end

it "sets private cache headers" do
it "sets public cache headers of latest published page" do
get alchemy_json_api.pages_path
expect(response.headers["Cache-Control"]).to eq("max-age=10800, private, must-revalidate")
expect(response.headers["Last-Modified"]).to eq(pages.max_by(&:published_at).published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
end
end

context "if browser sends fresh cache headers" do
it "returns not modified" do
get alchemy_json_api.pages_path
etag = response.headers["ETag"]
get alchemy_json_api.pages_path,
headers: {
"If-Modified-Since" => pages.max_by(&:published_at).published_at.utc.httpdate,
"If-None-Match" => etag,
}
expect(response.status).to eq(304)
context "if one page is restricted" do
let!(:restricted_page) do
FactoryBot.create(
:alchemy_page,
:public,
:restricted,
published_at: DateTime.yesterday,
)
end

it "sets private cache headers" do
get alchemy_json_api.pages_path
expect(response.headers["Cache-Control"]).to eq("max-age=10800, private, must-revalidate")
end
end

context "if for one page caching is disabled" do
let!(:no_cache_page) do
FactoryBot.create(
:alchemy_page,
:public,
page_layout: "contact",
published_at: DateTime.yesterday,
)
end

it "sends no cache headers" do
get alchemy_json_api.pages_path
expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate")
end
end

context "if browser sends fresh cache headers" do
it "returns not modified" do
get alchemy_json_api.pages_path
etag = response.headers["ETag"]
get alchemy_json_api.pages_path,
headers: {
"If-Modified-Since" => pages.max_by(&:published_at).published_at.utc.httpdate,
"If-None-Match" => etag,
}
expect(response.status).to eq(304)
end
end
end

Expand Down Expand Up @@ -235,11 +281,18 @@
end
end

it "sets cache headers of latest matching page" do
get alchemy_json_api.pages_path(filter: { page_layout_eq: "news" })
expect(response.headers["Last-Modified"]).to eq(news_page2.published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
context "with caching enabled" do
before do
allow(Rails.application.config.action_controller).to receive(:perform_caching) { true }
stub_alchemy_config(:cache_pages, true)
end

it "sets cache headers of latest matching page" do
get alchemy_json_api.pages_path(filter: { page_layout_eq: "news" })
expect(response.headers["Last-Modified"]).to eq(news_page2.published_at.utc.httpdate)
expect(response.headers["ETag"]).to match(/W\/".+"/)
expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate")
end
end
end

Expand Down

0 comments on commit 140252b

Please sign in to comment.