diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index 623846e..e9b417d 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -10,15 +10,10 @@ 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 @@ -26,6 +21,10 @@ def index 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) @@ -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 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index d3b20a8..fb2f0e7 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -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 @@ -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 diff --git a/spec/requests/alchemy/json_api/pages_spec.rb b/spec/requests/alchemy/json_api/pages_spec.rb index 4666370..0b23ce3 100644 --- a/spec/requests/alchemy/json_api/pages_spec.rb +++ b/spec/requests/alchemy/json_api/pages_spec.rb @@ -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 @@ -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 @@ -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