From 8fd60c77f3bfd952f6d9d7bea4642b0193b3f509 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 1 Feb 2024 09:35:27 +0100 Subject: [PATCH 01/17] Allow jsonapi.rb 2.x In 2022, they released a new version of jsonapi.rb. The functionality almost did not change at all, so no changes on our side are necessary. --- alchemy-json_api.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alchemy-json_api.gemspec b/alchemy-json_api.gemspec index a82e903..ce1950a 100644 --- a/alchemy-json_api.gemspec +++ b/alchemy-json_api.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] spec.add_dependency "alchemy_cms", [">= 7.0.0.a", "< 8"] - spec.add_dependency "jsonapi.rb", "~> 1.6" + spec.add_dependency "jsonapi.rb", [">= 1.6.0", "< 2.1"] spec.add_development_dependency "factory_bot" spec.add_development_dependency "github_changelog_generator" From 57bb91c9d2ac579b17054c0df0d6cadfc3b3f63d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 1 Feb 2024 09:44:40 +0100 Subject: [PATCH 02/17] Require activesupport in Rakefile The guide says this is needed for cherry-picking just the string extensions, and without it our specs fail. Cf: https://guides.rubyonrails.org/active_support_core_extensions.html#loading-grouped-core-extensions --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 4c99106..df1689c 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,7 @@ RSpec::Core::RakeTask.new(:spec) task default: [:test_setup, :spec] +require "active_support" require "active_support/core_ext/string" desc "Setup test app" From ece1da380acaf0461f9e5102a13c13bbeaacfa65 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:26:07 +0200 Subject: [PATCH 03/17] test: Fixate sqlite3 to 1.4 2.0 is not compatible with Rails yet --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6344e2b..3cb2be3 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,7 @@ gemspec # To use a debugger # gem 'byebug', group: [:development, :test] -gem "sqlite3" +gem "sqlite3", "~> 1.4" alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "main") gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: alchemy_branch From 16ba35d18d728d574a6438b5661e79acf94f7a38 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:52:05 +0200 Subject: [PATCH 04/17] Use Alchemy::Current if supported Since Alchemy 7.2 `Alchemy::Language.current` is deprecated. We should use `Alchemy::Current` if present in the Alchemy version. --- .../alchemy/json_api/admin/pages_controller.rb | 6 +++++- app/controllers/alchemy/json_api/pages_controller.rb | 12 ++++++++++-- .../alchemy/json_api/admin/pages_controller_spec.rb | 6 +++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/controllers/alchemy/json_api/admin/pages_controller.rb b/app/controllers/alchemy/json_api/admin/pages_controller.rb index 37e8049..92dc383 100644 --- a/app/controllers/alchemy/json_api/admin/pages_controller.rb +++ b/app/controllers/alchemy/json_api/admin/pages_controller.rb @@ -17,7 +17,11 @@ def caching_options end def set_current_preview - Alchemy::Page.current_preview = @page + if Alchemy.const_defined?(:Current) + Alchemy::Current.preview_page = @page + else + Alchemy::Page.current_preview = @page + end end def last_modified_for(page) diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index bc1aee1..2f219ff 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -120,9 +120,17 @@ def api_page(page) def base_page_scope # cancancan is not able to merge our complex AR scopes for logged in users if can?(:edit_content, ::Alchemy::Page) - Alchemy::Language.current.pages.joins(page_version_type) + current_language.pages.joins(page_version_type) else - Alchemy::Language.current.pages.published.joins(page_version_type) + current_language.pages.published.joins(page_version_type) + end + end + + def current_language + if Alchemy.const_defined?(:Current) + Alchemy::Current.language + else + Alchemy::Language.current end end diff --git a/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb b/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb index 9f8bdeb..dde698e 100644 --- a/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb +++ b/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb @@ -17,7 +17,11 @@ it "stores page as preview" do get :show, params: { path: page.urlname } - expect(Alchemy::Page.current_preview).to eq(page.id) + if Alchemy.const_defined?(:Current) + expect(Alchemy::Current.preview_page).to eq(page) + else + expect(Alchemy::Page.current_preview).to eq(page.id) + end end end end From 66a7ed832d179f253630edbb6faff2db0a71b221 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:34:23 +0200 Subject: [PATCH 05/17] Ignore sqlite files --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 88ae009..67a7354 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,10 @@ pkg/ spec/dummy/log/*.log spec/dummy/tmp/ Gemfile.lock -spec/dummy/db/*.sqlite3 spec/dummy/uploads -spec/dummy/db/development.sqlite3 spec/dummy/db/schema.rb spec/dummy/db/migrate -spec/dummy/db/*.sqlite3 +spec/dummy/db/*.sqlite3* spec/dummy/public Gemfile.lock .ruby-version From 26802b54903be7d3a19984538cd1bd303891add9 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:42:48 +0200 Subject: [PATCH 06/17] CI: Update test matrix Test stable Alchemy versions and latest Ruby 3.3 --- .github/workflows/ci.yml | 3 +++ Gemfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c325d4..4d0fe1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,14 @@ jobs: fail-fast: false matrix: alchemy_branch: + - 7.0-stable + - 7.1-stable - main ruby: - "3.0" - "3.1" - "3.2" + - "3.3" env: ALCHEMY_BRANCH: ${{ matrix.alchemy_branch }} steps: diff --git a/Gemfile b/Gemfile index 3cb2be3..e4c9b27 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,7 @@ gem "sqlite3", "~> 1.4" alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "main") gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: alchemy_branch -gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: alchemy_branch +gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: "main" gem "rufo" gem "rubocop" From 9b320ceb6c2d4de76b07cb9f037f5ac28e0f657d Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:44:40 +0200 Subject: [PATCH 07/17] CI: Only run on push of main branch --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d0fe1a..e01f13d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,10 @@ name: Test on: - - push - - pull_request + push: + branches: + - main + pull_request: jobs: RSpec: From 1bef19f223088b91d97a06ae99f9db68fe759950 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 22:49:31 +0200 Subject: [PATCH 08/17] CI: Update actions For NodeJS 16 warning --- .github/workflows/brakeman-analysis.yml | 2 +- .github/workflows/ci.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/brakeman-analysis.yml b/.github/workflows/brakeman-analysis.yml index 40bd93c..b9dccc2 100644 --- a/.github/workflows/brakeman-analysis.yml +++ b/.github/workflows/brakeman-analysis.yml @@ -17,7 +17,7 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Customize the ruby version depending on your needs - name: Set up Ruby diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e01f13d..b35b268 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: env: ALCHEMY_BRANCH: ${{ matrix.alchemy_branch }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -32,7 +32,7 @@ jobs: bundler-cache: true - name: Restore apt cache id: apt-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /home/runner/apt/cache key: apt-sqlite- @@ -54,9 +54,9 @@ jobs: env: NODE_ENV: test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore node modules cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: node_modules key: ${{ runner.os }}-yarn-${{ hashFiles('./package.json') }} From 960192c921b3db18c8e9dd20b0ba57d42d83c17b Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 23:01:47 +0200 Subject: [PATCH 09/17] Use StandardRb as Ruby linter --- .github/workflows/lint.yml | 21 ++ .rubocop.yml | 308 +----------------- .standard.yml | 5 + Gemfile | 5 +- alchemy-json_api.gemspec | 1 + .../json_api/admin/layout_pages_controller.rb | 1 + .../json_api/admin/pages_controller.rb | 3 +- .../alchemy/json_api/base_controller.rb | 5 +- .../json_api/layout_pages_controller.rb | 1 + .../alchemy/json_api/nodes_controller.rb | 6 +- .../alchemy/json_api/pages_controller.rb | 30 +- .../alchemy/json_api/element_serializer.rb | 3 +- .../json_api/ingredient_audio_serializer.rb | 2 +- .../json_api/ingredient_picture_serializer.rb | 8 +- .../ingredient_richtext_serializer.rb | 2 +- .../json_api/ingredient_text_serializer.rb | 2 +- .../json_api/ingredient_video_serializer.rb | 2 +- .../alchemy/json_api/language_serializer.rb | 3 +- .../alchemy/json_api/node_serializer.rb | 3 +- .../alchemy/json_api/page_serializer.rb | 3 +- config/routes.rb | 9 +- lib/alchemy/json_api/engine.rb | 1 + lib/alchemy/json_api/essence_serializer.rb | 1 + .../essence_serializer_behaviour.rb | 5 +- .../ingredient_serializer_behaviour.rb | 2 +- lib/alchemy/json_api/version.rb | 1 + lib/tasks/alchemy/json_api_tasks.rake | 1 + .../json_api/admin/pages_controller_spec.rb | 2 +- spec/rails_helper.rb | 1 + .../json_api/admin/layout_pages_spec.rb | 19 +- .../alchemy/json_api/admin/pages_spec.rb | 13 +- .../alchemy/json_api/layout_pages_spec.rb | 9 +- spec/requests/alchemy/json_api/nodes_spec.rb | 15 +- spec/requests/alchemy/json_api/pages_spec.rb | 45 +-- spec/routing/layout_page_routing_spec.rb | 8 +- spec/routing/page_routing_spec.rb | 8 +- .../json_api/element_serializer_spec.rb | 15 +- .../ingredient_audio_serializer_spec.rb | 2 +- .../ingredient_file_serializer_spec.rb | 1 + .../ingredient_headline_serializer_spec.rb | 4 +- .../ingredient_html_serializer_spec.rb | 1 + .../ingredient_link_serializer_spec.rb | 2 +- .../ingredient_node_serializer_spec.rb | 2 +- .../ingredient_page_serializer_spec.rb | 2 +- .../ingredient_picture_serializer_spec.rb | 28 +- .../ingredient_richtext_serializer_spec.rb | 2 +- .../ingredient_text_serializer_spec.rb | 1 + .../ingredient_video_serializer_spec.rb | 2 +- .../json_api/language_serializer_spec.rb | 9 +- .../alchemy/json_api/node_serializer_spec.rb | 11 +- .../alchemy/json_api/page_serializer_spec.rb | 19 +- spec/spec_helper.rb | 1 + 52 files changed, 206 insertions(+), 450 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .standard.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..1a7dd9d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: Lint + +on: [pull_request] + +concurrency: + group: lint-${{ github.ref_name }} + cancel-in-progress: ${{ github.ref_name != 'main' }} + +jobs: + Standard: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.0" + bundler-cache: true + - name: Lint Ruby files + run: bundle exec standardrb diff --git a/.rubocop.yml b/.rubocop.yml index 68a2c8a..6faf5db 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,307 +1,7 @@ -# Relaxed.Ruby.Style +require: standard AllCops: - TargetRubyVersion: 2.4 - Exclude: - - 'bin/rspec' - - 'vendor/**/*' - - 'spec/dummy/db/**/*' - - 'spec/dummy/config/**/*' - - 'alchemy_cms.gemspec' - - 'Rakefile' - - 'node_modules/**/*' + TargetRubyVersion: 3.0 -# Really, rubocop? -Bundler/OrderedGems: - Enabled: false - -Style/EmptyLiteral: - Enabled: false - -# We use class vars and will have to continue doing so for compatability -Style/ClassVars: - Enabled: false - -Style/FloatDivision: - EnforcedStyle: left_coerce - -# This has been used for customization -Style/MutableConstant: - Enabled: false - -Style/ClassAndModuleChildren: - Enabled: false - -Style/GuardClause: - Enabled: false - -# We support older versions of Ruby (2.1) that do not have the %i syntax -Style/SymbolArray: - Enabled: false - -Style/SymbolProc: - Exclude: - - 'lib/alchemy/permissions.rb' - -Style/WordArray: - Enabled: false - -Style/ConditionalAssignment: - Enabled: false - -Style/MixinUsage: - Exclude: - - spec/**/* - -Layout/ArgumentAlignment: - Enabled: false - -Layout/HashAlignment: - Enabled: false - -Layout/ParameterAlignment: - Enabled: false - -Layout/ClosingParenthesisIndentation: - Enabled: false - -Layout/DotPosition: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styledotposition - -Layout/ElseAlignment: - Enabled: false - -Layout/EmptyLineAfterMagicComment: - Enabled: false - -Layout/FirstArrayElementIndentation: - Enabled: false - -Layout/FirstHashElementIndentation: - Enabled: false - -Layout/IndentationWidth: - Enabled: false - -Layout/LineLength: - Enabled: false - -Layout/MultilineHashBraceLayout: - Enabled: false - -Layout/MultilineMethodCallBraceLayout: - Enabled: false - -Layout/MultilineMethodCallIndentation: - Enabled: false - -Layout/MultilineOperationIndentation: - EnforcedStyle: indented - -Layout/SpaceBeforeBlockBraces: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces - -Layout/SpaceInsideHashLiteralBraces: - Enabled: false - -Layout/SpaceInsideParens: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens - -Layout/EndAlignment: - Enabled: false - -Layout/RescueEnsureAlignment: - Enabled: false - -Lint/SuppressedException: - Exclude: - - 'config/initializers/mini_profiler.rb' - -Style/CollectionMethods: - Enabled: false - -Style/Alias: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylealias - -Style/BeginBlock: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylebeginblock - -Style/BlockDelimiters: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleblockdelimiters - -Style/Documentation: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styledocumentation - -Style/DoubleNegation: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styledoublenegation - -Style/EmptyMethod: - Enabled: false - -Style/EndBlock: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleendblock - -Style/FormatString: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleformatstring - -Style/IfUnlessModifier: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier - -Style/Lambda: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylelambda - -Style/ModuleFunction: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylemodulefunction - -Style/MultilineBlockChain: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylemultilineblockchain - -Style/NegatedIf: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylenegatedif - -Style/NegatedWhile: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile - -Style/NumericLiteralPrefix: - Enabled: false - -Style/ParallelAssignment: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleparallelassignment - -Style/PercentLiteralDelimiters: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylepercentliteraldelimiters - -Style/PerlBackrefs: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styleperlbackrefs - -Style/RegexpLiteral: - Enabled: false - -Style/Semicolon: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylesemicolon - -Style/SignalException: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylesignalexception - -Style/SingleLineBlockParams: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylesinglelineblockparams - -Style/SingleLineMethods: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods - -Style/SpecialGlobalVars: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes - -Style/TrailingCommaInArguments: - EnforcedStyleForMultiline: comma - -Style/TrailingCommaInArrayLiteral: - EnforcedStyleForMultiline: comma - -Style/TrailingCommaInHashLiteral: - EnforcedStyleForMultiline: consistent_comma - -Style/WhileUntilModifier: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylewhileuntilmodifier - -# We use a lot of -# -# expect { -# something -# }.to { happen } -# -# syntax in the specs files. -Lint/AmbiguousBlockAssociation: - Exclude: - - 'spec/**/*' - -Lint/AmbiguousRegexpLiteral: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral - -Lint/AssignmentInCondition: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition - -# We use eval to get the correct url proxy object of engines in: -# - app/helpers/alchemy/admin/navigation_helper.rb:139 -# - lib/alchemy/resources_helper.rb:24 -Security/Eval: - Exclude: - - app/helpers/alchemy/admin/navigation_helper.rb - - lib/alchemy/resources_helper.rb - -Metrics/AbcSize: - Enabled: false - -Metrics/BlockLength: - Enabled: false - -Metrics/BlockNesting: - Enabled: false - -Metrics/ClassLength: - Enabled: false - -Metrics/ModuleLength: - Enabled: false - -Metrics/CyclomaticComplexity: - Enabled: false - -Metrics/MethodLength: - Enabled: false - -Metrics/ParameterLists: - Enabled: false - -Metrics/PerceivedComplexity: - Enabled: false - -Naming/AccessorMethodName: - Enabled: false - -Naming/HeredocDelimiterNaming: - Enabled: false - -# This cop is great but has no config option to support the style `@_method_name` we are using. -Naming/MemoizedInstanceVariableName: - Enabled: false - -# We need these names for backwards compatability -Naming/PredicateName: - Enabled: false - -Naming/VariableNumber: - Enabled: false +inherit_gem: + standard: config/base.yml diff --git a/.standard.yml b/.standard.yml new file mode 100644 index 0000000..7e46ec6 --- /dev/null +++ b/.standard.yml @@ -0,0 +1,5 @@ +parallel: true +format: progress +ruby_version: 3.0 +ignore: + - "spec/dummy/**/*" diff --git a/Gemfile b/Gemfile index e4c9b27..a3dbb18 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ # frozen_string_literal: true + source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } @@ -20,6 +21,6 @@ alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "main") gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: alchemy_branch gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: "main" -gem "rufo" -gem "rubocop" +gem "rubocop", require: false +gem "standard", "~> 1.25", require: false gem "pry-byebug" diff --git a/alchemy-json_api.gemspec b/alchemy-json_api.gemspec index ce1950a..d66b89e 100644 --- a/alchemy-json_api.gemspec +++ b/alchemy-json_api.gemspec @@ -1,4 +1,5 @@ # frozen_string_literal: true + $:.push File.expand_path("lib", __dir__) # Maintain your gem's version: diff --git a/app/controllers/alchemy/json_api/admin/layout_pages_controller.rb b/app/controllers/alchemy/json_api/admin/layout_pages_controller.rb index 18cc438..4dcf488 100644 --- a/app/controllers/alchemy/json_api/admin/layout_pages_controller.rb +++ b/app/controllers/alchemy/json_api/admin/layout_pages_controller.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi module Admin diff --git a/app/controllers/alchemy/json_api/admin/pages_controller.rb b/app/controllers/alchemy/json_api/admin/pages_controller.rb index 92dc383..0bf5660 100644 --- a/app/controllers/alchemy/json_api/admin/pages_controller.rb +++ b/app/controllers/alchemy/json_api/admin/pages_controller.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi module Admin @@ -13,7 +14,7 @@ def cache_duration end def caching_options - { public: false, must_revalidate: true } + {public: false, must_revalidate: true} end def set_current_preview diff --git a/app/controllers/alchemy/json_api/base_controller.rb b/app/controllers/alchemy/json_api/base_controller.rb index 6aa404a..10eb88d 100644 --- a/app/controllers/alchemy/json_api/base_controller.rb +++ b/app/controllers/alchemy/json_api/base_controller.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class BaseController < ::ApplicationController @@ -11,7 +12,7 @@ class BaseController < ::ApplicationController rescue_from( CanCan::AccessDenied, - with: :render_jsonapi_unauthorized, + with: :render_jsonapi_unauthorized ) private @@ -32,7 +33,7 @@ def log_error(exception) end def render_jsonapi_unauthorized(exception) - error = { status: "401", title: Rack::Utils::HTTP_STATUS_CODES[401] } + error = {status: "401", title: Rack::Utils::HTTP_STATUS_CODES[401]} render jsonapi_errors: [error], status: :unauthorized end end diff --git a/app/controllers/alchemy/json_api/layout_pages_controller.rb b/app/controllers/alchemy/json_api/layout_pages_controller.rb index 59e7b93..a4e361d 100644 --- a/app/controllers/alchemy/json_api/layout_pages_controller.rb +++ b/app/controllers/alchemy/json_api/layout_pages_controller.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class LayoutPagesController < JsonApi::PagesController diff --git a/app/controllers/alchemy/json_api/nodes_controller.rb b/app/controllers/alchemy/json_api/nodes_controller.rb index 5a417c3..0ce2e59 100644 --- a/app/controllers/alchemy/json_api/nodes_controller.rb +++ b/app/controllers/alchemy/json_api/nodes_controller.rb @@ -11,7 +11,7 @@ def index end end - expires_in cache_duration, { public: true, must_revalidate: true } + expires_in cache_duration, {public: true, must_revalidate: true} end private @@ -25,7 +25,7 @@ def jsonapi_meta(nodes) { pagination: pagination.presence, - total: node_scope.count, + total: node_scope.count }.compact end @@ -37,7 +37,7 @@ def node_scope_with_includes if params[:include].present? includes = params[:include].split(",").map do |association| association.split(".").reverse.inject({}) do |value, key| - { key.to_sym => value } + {key.to_sym => value} end end node_scope.includes(includes) diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index 2f219ff..850ee4c 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -17,7 +17,7 @@ def index end end - expires_in cache_duration, { public: @pages.none?(&:restricted?) }.merge(caching_options) + expires_in cache_duration, {public: @pages.none?(&:restricted?)}.merge(caching_options) end def show @@ -30,7 +30,7 @@ def show render jsonapi: api_page(load_page) end - expires_in cache_duration, { public: !@page.restricted? }.merge(caching_options) + expires_in cache_duration, {public: !@page.restricted?}.merge(caching_options) end private @@ -51,13 +51,13 @@ def cache_duration end def caching_options - { must_revalidate: true } + {must_revalidate: true} end # Get page w/o includes to get cache key def load_page_for_cache_key - @page = page_scope.where(id: params[:path]). - or(page_scope.where(urlname: params[:path])).first! + @page = page_scope.where(id: params[:path]) + .or(page_scope.where(urlname: params[:path])).first! end def last_modified_for(page) @@ -69,7 +69,7 @@ def jsonapi_meta(pages) { pagination: pagination.presence, - total: page_scope.count, + total: page_scope.count }.compact end @@ -78,7 +78,7 @@ def load_page end def load_page_by_id - return unless params[:path] =~ /\A\d+\z/ + return unless /\A\d+\z/.match?(params[:path]) page_scope_with_includes.find_by(id: params[:path]) end @@ -92,20 +92,20 @@ def page_scope end def page_scope_with_includes - page_scope. - includes( + page_scope + .includes( [ :legacy_urls, - { language: { nodes: [:parent, :children, { page: { language: { site: :languages } } }] } }, + {language: {nodes: [:parent, :children, {page: {language: {site: :languages}}}]}}, { page_version_type => { elements: [ :nested_elements, - { ingredients: :related_object }, - ], - }, - }, - ], + {ingredients: :related_object} + ] + } + } + ] ) end diff --git a/app/serializers/alchemy/json_api/element_serializer.rb b/app/serializers/alchemy/json_api/element_serializer.rb index 97a1a6d..9624f31 100644 --- a/app/serializers/alchemy/json_api/element_serializer.rb +++ b/app/serializers/alchemy/json_api/element_serializer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class ElementSerializer < BaseSerializer @@ -7,7 +8,7 @@ class ElementSerializer < BaseSerializer :fixed, :position, :created_at, - :updated_at, + :updated_at ) cache_options store: Rails.cache, namespace: "alchemy-jsonapi" diff --git a/app/serializers/alchemy/json_api/ingredient_audio_serializer.rb b/app/serializers/alchemy/json_api/ingredient_audio_serializer.rb index 526863a..d5e3fb8 100644 --- a/app/serializers/alchemy/json_api/ingredient_audio_serializer.rb +++ b/app/serializers/alchemy/json_api/ingredient_audio_serializer.rb @@ -11,7 +11,7 @@ class IngredientAudioSerializer < BaseSerializer :autoplay, :controls, :muted, - :loop, + :loop ) attribute :value do |ingredient| diff --git a/app/serializers/alchemy/json_api/ingredient_picture_serializer.rb b/app/serializers/alchemy/json_api/ingredient_picture_serializer.rb index 88fea00..62ae44f 100644 --- a/app/serializers/alchemy/json_api/ingredient_picture_serializer.rb +++ b/app/serializers/alchemy/json_api/ingredient_picture_serializer.rb @@ -12,7 +12,7 @@ class IngredientPictureSerializer < BaseSerializer :caption, :link_class_name, :link_title, - :link_target, + :link_target ) attribute :value do |ingredient| @@ -26,7 +26,7 @@ class IngredientPictureSerializer < BaseSerializer attribute :image_dimensions do |ingredient| sizes = ingredient.settings[:size]&.split("x", 2)&.map(&:to_i) || [ ingredient.image_file_width, - ingredient.image_file_height, + ingredient.image_file_height ] ratio = ingredient.image_file_width.to_f / ingredient.image_file_height @@ -35,7 +35,7 @@ class IngredientPictureSerializer < BaseSerializer { width: width, - height: height, + height: height } end @@ -58,7 +58,7 @@ class IngredientPictureSerializer < BaseSerializer desc: "#{width}w", width: width, height: height, - type: type.to_s, + type: type.to_s } end end diff --git a/app/serializers/alchemy/json_api/ingredient_richtext_serializer.rb b/app/serializers/alchemy/json_api/ingredient_richtext_serializer.rb index 09de6ce..46f7923 100644 --- a/app/serializers/alchemy/json_api/ingredient_richtext_serializer.rb +++ b/app/serializers/alchemy/json_api/ingredient_richtext_serializer.rb @@ -9,7 +9,7 @@ class IngredientRichtextSerializer < BaseSerializer attributes( :sanitized_body, - :stripped_body, + :stripped_body ) attribute :body, &:value diff --git a/app/serializers/alchemy/json_api/ingredient_text_serializer.rb b/app/serializers/alchemy/json_api/ingredient_text_serializer.rb index 054c04d..44143af 100644 --- a/app/serializers/alchemy/json_api/ingredient_text_serializer.rb +++ b/app/serializers/alchemy/json_api/ingredient_text_serializer.rb @@ -11,7 +11,7 @@ class IngredientTextSerializer < BaseSerializer :link, :link_class_name, :link_target, - :link_title, + :link_title ) # maintain compatibility with EssenceText diff --git a/app/serializers/alchemy/json_api/ingredient_video_serializer.rb b/app/serializers/alchemy/json_api/ingredient_video_serializer.rb index a0ec68f..d056c8f 100644 --- a/app/serializers/alchemy/json_api/ingredient_video_serializer.rb +++ b/app/serializers/alchemy/json_api/ingredient_video_serializer.rb @@ -13,7 +13,7 @@ class IngredientVideoSerializer < BaseSerializer :allow_fullscreen, :autoplay, :controls, - :preload, + :preload ) attribute :value do |ingredient| diff --git a/app/serializers/alchemy/json_api/language_serializer.rb b/app/serializers/alchemy/json_api/language_serializer.rb index b6456cd..f071519 100644 --- a/app/serializers/alchemy/json_api/language_serializer.rb +++ b/app/serializers/alchemy/json_api/language_serializer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class LanguageSerializer < BaseSerializer @@ -6,7 +7,7 @@ class LanguageSerializer < BaseSerializer :name, :language_code, :country_code, - :locale, + :locale ) has_many :menu_items, record_type: :node, serializer: ::Alchemy::JsonApi::NodeSerializer, object_method_name: :nodes, id_method_name: :node_ids diff --git a/app/serializers/alchemy/json_api/node_serializer.rb b/app/serializers/alchemy/json_api/node_serializer.rb index c6ed2b0..afbc1da 100644 --- a/app/serializers/alchemy/json_api/node_serializer.rb +++ b/app/serializers/alchemy/json_api/node_serializer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class NodeSerializer < BaseSerializer @@ -13,7 +14,7 @@ class NodeSerializer < BaseSerializer :page, record_type: :page, if: ->(node) { node.page }, - serializer: ::Alchemy::JsonApi::PageSerializer, + serializer: ::Alchemy::JsonApi::PageSerializer ) do |node| ::Alchemy::JsonApi::Page.new(node.page) end diff --git a/app/serializers/alchemy/json_api/page_serializer.rb b/app/serializers/alchemy/json_api/page_serializer.rb index 824517e..8d6f962 100644 --- a/app/serializers/alchemy/json_api/page_serializer.rb +++ b/app/serializers/alchemy/json_api/page_serializer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi class PageSerializer < BaseSerializer @@ -14,7 +15,7 @@ class PageSerializer < BaseSerializer :meta_keywords, :meta_description, :created_at, - :updated_at, + :updated_at ) cache_options store: Rails.cache, namespace: "alchemy-jsonapi" diff --git a/config/routes.rb b/config/routes.rb index c7ed8e0..611cd58 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true + Alchemy::JsonApi::Engine.routes.draw do resources :pages, only: [:index] - get "pages/*path" => "pages#show", as: :page + get "pages/*path" => "pages#show", :as => :page resources :layout_pages, only: [:index] - get "layout_pages/*path" => "layout_pages#show", as: :layout_page + get "layout_pages/*path" => "layout_pages#show", :as => :layout_page resources :nodes, only: [:index] namespace :admin do - get "pages/*path" => "pages#show", as: :page + get "pages/*path" => "pages#show", :as => :page resources :layout_pages, only: [:index] - get "layout_pages/*path" => "layout_pages#show", as: :layout_page + get "layout_pages/*path" => "layout_pages#show", :as => :layout_page end end diff --git a/lib/alchemy/json_api/engine.rb b/lib/alchemy/json_api/engine.rb index 7218a39..7b0bc1f 100644 --- a/lib/alchemy/json_api/engine.rb +++ b/lib/alchemy/json_api/engine.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "jsonapi" module Alchemy diff --git a/lib/alchemy/json_api/essence_serializer.rb b/lib/alchemy/json_api/essence_serializer.rb index ea3b72e..e3416b2 100644 --- a/lib/alchemy/json_api/essence_serializer.rb +++ b/lib/alchemy/json_api/essence_serializer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi module EssenceSerializer diff --git a/lib/alchemy/json_api/test_support/essence_serializer_behaviour.rb b/lib/alchemy/json_api/test_support/essence_serializer_behaviour.rb index c5d17d4..c446d8d 100644 --- a/lib/alchemy/json_api/test_support/essence_serializer_behaviour.rb +++ b/lib/alchemy/json_api/test_support/essence_serializer_behaviour.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + RSpec.shared_examples "an essence serializer" do describe "attributes" do subject { serializer.serializable_hash[:data][:attributes] } @@ -15,7 +16,7 @@ expect(content).to receive(:definition).at_least(:once) do { name: "intro", - deprecated: true, + deprecated: true } end end @@ -30,7 +31,7 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has the right keys and values" do - expect(subject[:element]).to eq(data: { id: essence.element.id.to_s, type: :element }) + expect(subject[:element]).to eq(data: {id: essence.element.id.to_s, type: :element}) end end end diff --git a/lib/alchemy/json_api/test_support/ingredient_serializer_behaviour.rb b/lib/alchemy/json_api/test_support/ingredient_serializer_behaviour.rb index 547477c..5091016 100644 --- a/lib/alchemy/json_api/test_support/ingredient_serializer_behaviour.rb +++ b/lib/alchemy/json_api/test_support/ingredient_serializer_behaviour.rb @@ -31,7 +31,7 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has one element" do - expect(subject[:element]).to eq(data: { id: ingredient.element_id.to_s, type: :element }) + expect(subject[:element]).to eq(data: {id: ingredient.element_id.to_s, type: :element}) end end end diff --git a/lib/alchemy/json_api/version.rb b/lib/alchemy/json_api/version.rb index d92f259..4e63f33 100644 --- a/lib/alchemy/json_api/version.rb +++ b/lib/alchemy/json_api/version.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Alchemy module JsonApi VERSION = "2.0.1" diff --git a/lib/tasks/alchemy/json_api_tasks.rake b/lib/tasks/alchemy/json_api_tasks.rake index 39d42b2..d4af9e0 100644 --- a/lib/tasks/alchemy/json_api_tasks.rake +++ b/lib/tasks/alchemy/json_api_tasks.rake @@ -1,4 +1,5 @@ # frozen_string_literal: true + # desc "Explaining what the task does" # task :alchemy_json_api do # # Task goes here diff --git a/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb b/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb index dde698e..6c2b393 100644 --- a/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb +++ b/spec/controllers/alchemy/json_api/admin/pages_controller_spec.rb @@ -16,7 +16,7 @@ let(:page) { FactoryBot.create(:alchemy_page) } it "stores page as preview" do - get :show, params: { path: page.urlname } + get :show, params: {path: page.urlname} if Alchemy.const_defined?(:Current) expect(Alchemy::Current.preview_page).to eq(page) else diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index fb2f0e7..c934809 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # This file is copied to spec/ when you run 'rails generate rspec:install' require "spec_helper" ENV["RAILS_ENV"] ||= "test" diff --git a/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb b/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb index 7f7adc2..fa209e2 100644 --- a/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb +++ b/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" require "alchemy/devise/test_support/factories" @@ -44,7 +45,7 @@ end it "returns paginated result" do - get alchemy_json_api.admin_layout_pages_path(page: { number: 2, size: 1 }) + get alchemy_json_api.admin_layout_pages_path(page: {number: 2, size: 1}) document = JSON.parse(response.body) expect(document["data"].length).to eq(1) expect(document["meta"]).to eq({ @@ -54,9 +55,9 @@ "last" => 3, "next" => 3, "prev" => 1, - "records" => 3, + "records" => 3 }, - "total" => 3, + "total" => 3 }) end end @@ -97,8 +98,8 @@ { "data" => [{ "id" => element.id.to_s, - "type" => "element", - }], + "type" => "element" + }] } ) end @@ -121,10 +122,10 @@ get alchemy_json_api.admin_layout_page_path(page) etag = response.headers["ETag"] get alchemy_json_api.admin_layout_page_path(page), - headers: { - "If-Modified-Since" => page.updated_at.utc.httpdate, - "If-None-Match" => etag, - } + headers: { + "If-Modified-Since" => page.updated_at.utc.httpdate, + "If-None-Match" => etag + } expect(response.status).to eq(304) end end diff --git a/spec/requests/alchemy/json_api/admin/pages_spec.rb b/spec/requests/alchemy/json_api/admin/pages_spec.rb index 97cabba..939b761 100644 --- a/spec/requests/alchemy/json_api/admin/pages_spec.rb +++ b/spec/requests/alchemy/json_api/admin/pages_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" require "alchemy/devise/test_support/factories" @@ -44,10 +45,10 @@ get alchemy_json_api.admin_page_path(page) etag = response.headers["ETag"] get alchemy_json_api.admin_page_path(page), - headers: { - "If-Modified-Since" => page.updated_at.utc.httpdate, - "If-None-Match" => etag, - } + headers: { + "If-Modified-Since" => page.updated_at.utc.httpdate, + "If-None-Match" => etag + } expect(response.status).to eq(304) end end @@ -68,8 +69,8 @@ { "data" => [{ "id" => element.id.to_s, - "type" => "element", - }], + "type" => "element" + }] } ) end diff --git a/spec/requests/alchemy/json_api/layout_pages_spec.rb b/spec/requests/alchemy/json_api/layout_pages_spec.rb index 341b835..4507014 100644 --- a/spec/requests/alchemy/json_api/layout_pages_spec.rb +++ b/spec/requests/alchemy/json_api/layout_pages_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" require "alchemy/devise/test_support/factories" @@ -9,7 +10,7 @@ :public, :layoutpage, urlname: nil, - title: "Footer", + title: "Footer" ) end @@ -117,7 +118,7 @@ end it "returns paginated result" do - get alchemy_json_api.layout_pages_path(page: { number: 2, size: 1 }) + get alchemy_json_api.layout_pages_path(page: {number: 2, size: 1}) document = JSON.parse(response.body) expect(document["data"].length).to eq(1) expect(document["meta"]).to eq({ @@ -127,9 +128,9 @@ "last" => 3, "next" => 3, "prev" => 1, - "records" => 3, + "records" => 3 }, - "total" => 3, + "total" => 3 }) end end diff --git a/spec/requests/alchemy/json_api/nodes_spec.rb b/spec/requests/alchemy/json_api/nodes_spec.rb index da747ac..27895cb 100644 --- a/spec/requests/alchemy/json_api/nodes_spec.rb +++ b/spec/requests/alchemy/json_api/nodes_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" require "alchemy/devise/test_support/factories" require "alchemy/version" @@ -33,10 +34,10 @@ get alchemy_json_api.nodes_path etag = response.headers["ETag"] get alchemy_json_api.nodes_path, - headers: { - "If-Modified-Since" => nodes.max_by(&:updated_at).updated_at.utc.httpdate, - "If-None-Match" => etag, - } + headers: { + "If-Modified-Since" => nodes.max_by(&:updated_at).updated_at.utc.httpdate, + "If-None-Match" => etag + } expect(response.status).to eq(304) end end @@ -86,7 +87,7 @@ end it "returns paginated result" do - get alchemy_json_api.nodes_path(page: { number: 2, size: 1 }) + get alchemy_json_api.nodes_path(page: {number: 2, size: 1}) document = JSON.parse(response.body) expect(document["data"].length).to eq(1) expect(document["meta"]).to eq({ @@ -96,9 +97,9 @@ "last" => 4, "next" => 3, "prev" => 1, - "records" => 4, + "records" => 4 }, - "total" => 4, + "total" => 4 }) end end diff --git a/spec/requests/alchemy/json_api/pages_spec.rb b/spec/requests/alchemy/json_api/pages_spec.rb index d81a63b..114112d 100644 --- a/spec/requests/alchemy/json_api/pages_spec.rb +++ b/spec/requests/alchemy/json_api/pages_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" require "alchemy/devise/test_support/factories" require "alchemy/version" @@ -12,7 +13,7 @@ title: "Page Title", meta_keywords: "Meta Keywords", meta_description: "Meta Description", - tag_list: "Tag1,Tag2", + tag_list: "Tag1,Tag2" ) end @@ -22,7 +23,7 @@ FactoryBot.create( :alchemy_page, :public, - published_at: DateTime.yesterday, + published_at: DateTime.yesterday ) end @@ -45,7 +46,7 @@ :alchemy_page, :public, :restricted, - published_at: DateTime.yesterday, + published_at: DateTime.yesterday ) end @@ -61,7 +62,7 @@ :alchemy_page, :public, page_layout: "contact", - published_at: DateTime.yesterday, + published_at: DateTime.yesterday ) end @@ -76,10 +77,10 @@ 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, - } + headers: { + "If-Modified-Since" => page.published_at.utc.httpdate, + "If-None-Match" => etag + } expect(response.status).to eq(304) end end @@ -121,7 +122,7 @@ FactoryBot.create( :alchemy_page, :public, - urlname: "a-nested/page", + urlname: "a-nested/page" ) end @@ -195,7 +196,7 @@ :alchemy_page, :public, :restricted, - published_at: DateTime.yesterday, + published_at: DateTime.yesterday ) end @@ -211,7 +212,7 @@ :alchemy_page, :public, page_layout: "contact", - published_at: DateTime.yesterday, + published_at: DateTime.yesterday ) end @@ -226,10 +227,10 @@ 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, - } + 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 @@ -267,7 +268,7 @@ let!(:news_page2) { FactoryBot.create(:alchemy_page, :public, name: "News", page_layout: "news", published_at: Date.yesterday) } it "returns only matching pages by page_layout" do - get alchemy_json_api.pages_path(filter: { page_layout_eq: "news" }) + get alchemy_json_api.pages_path(filter: {page_layout_eq: "news"}) document = JSON.parse(response.body) expect(document["data"]).not_to include(have_id(standard_page.id.to_s)) expect(document["data"]).to include(have_id(news_page.id.to_s)) @@ -275,7 +276,7 @@ end it "returns only matching pages by name" do - get alchemy_json_api.pages_path(filter: { name_eq: "News" }) + get alchemy_json_api.pages_path(filter: {name_eq: "News"}) document = JSON.parse(response.body) expect(document["data"]).not_to include(have_id(standard_page.id.to_s)) expect(document["data"]).not_to include(have_id(news_page.id.to_s)) @@ -284,7 +285,7 @@ context "if no pages returned for filter params" do it "does not throw error" do - get alchemy_json_api.pages_path(filter: { page_layout_eq: "not-found" }) + get alchemy_json_api.pages_path(filter: {page_layout_eq: "not-found"}) expect(response).to be_successful end end @@ -296,7 +297,7 @@ end it "sets cache headers of latest matching page" do - get alchemy_json_api.pages_path(filter: { page_layout_eq: "news" }) + 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") @@ -310,7 +311,7 @@ end it "returns paginated result" do - get alchemy_json_api.pages_path(page: { number: 2, size: 1 }) + get alchemy_json_api.pages_path(page: {number: 2, size: 1}) document = JSON.parse(response.body) expect(document["data"].length).to eq(1) expect(document["meta"]).to eq({ @@ -320,9 +321,9 @@ "last" => 4, "next" => 3, "prev" => 1, - "records" => 4, + "records" => 4 }, - "total" => 4, + "total" => 4 }) end end diff --git a/spec/routing/layout_page_routing_spec.rb b/spec/routing/layout_page_routing_spec.rb index 08705e0..fbd0952 100644 --- a/spec/routing/layout_page_routing_spec.rb +++ b/spec/routing/layout_page_routing_spec.rb @@ -8,7 +8,7 @@ it "routes layout_pages/" do expect(get: "/layout_pages").to route_to( controller: "alchemy/json_api/layout_pages", - action: "index", + action: "index" ) end @@ -16,7 +16,7 @@ expect(get: "/layout_pages/1").to route_to( controller: "alchemy/json_api/layout_pages", action: "show", - path: "1", + path: "1" ) end @@ -24,7 +24,7 @@ expect(get: "/layout_pages/a-page").to route_to( controller: "alchemy/json_api/layout_pages", action: "show", - path: "a-page", + path: "a-page" ) end @@ -32,7 +32,7 @@ expect(get: "/layout_pages/a-nested/page").to route_to( controller: "alchemy/json_api/layout_pages", action: "show", - path: "a-nested/page", + path: "a-nested/page" ) end end diff --git a/spec/routing/page_routing_spec.rb b/spec/routing/page_routing_spec.rb index 2132129..a91865d 100644 --- a/spec/routing/page_routing_spec.rb +++ b/spec/routing/page_routing_spec.rb @@ -8,7 +8,7 @@ it "routes pages/" do expect(get: "/pages").to route_to( controller: "alchemy/json_api/pages", - action: "index", + action: "index" ) end @@ -16,7 +16,7 @@ expect(get: "/pages/1").to route_to( controller: "alchemy/json_api/pages", action: "show", - path: "1", + path: "1" ) end @@ -24,7 +24,7 @@ expect(get: "/pages/a-page").to route_to( controller: "alchemy/json_api/pages", action: "show", - path: "a-page", + path: "a-page" ) end @@ -32,7 +32,7 @@ expect(get: "/pages/a-nested/page").to route_to( controller: "alchemy/json_api/pages", action: "show", - path: "a-nested/page", + path: "a-nested/page" ) end end diff --git a/spec/serializers/alchemy/json_api/element_serializer_spec.rb b/spec/serializers/alchemy/json_api/element_serializer_spec.rb index f9c0e26..f72268b 100644 --- a/spec/serializers/alchemy/json_api/element_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/element_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::ElementSerializer do @@ -8,7 +9,7 @@ autogenerate_ingredients: true, tag_list: "Tag1,Tag2", nested_elements: [nested_element], - parent_element: parent_element, + parent_element: parent_element ) end let(:nested_element) { FactoryBot.create(:alchemy_element) } @@ -45,7 +46,7 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has nested_elements" do - expect(subject[:nested_elements]).to eq(data: [{ id: nested_element.id.to_s, type: :element }]) + expect(subject[:nested_elements]).to eq(data: [{id: nested_element.id.to_s, type: :element}]) end context "with ingredients" do @@ -54,7 +55,7 @@ [ FactoryBot.build_stubbed(:alchemy_ingredient_text, element: element), FactoryBot.build_stubbed(:alchemy_ingredient_richtext, element: element), - FactoryBot.build_stubbed(:alchemy_ingredient_picture, element: element), + FactoryBot.build_stubbed(:alchemy_ingredient_picture, element: element) ] end end @@ -62,10 +63,10 @@ it "has ingredients" do expect(subject[:ingredients]).to eq( data: [ - { id: "1001", type: :ingredient_text }, - { id: "1002", type: :ingredient_richtext }, - { id: "1003", type: :ingredient_picture }, - ], + {id: "1001", type: :ingredient_text}, + {id: "1002", type: :ingredient_richtext}, + {id: "1003", type: :ingredient_picture} + ] ) end end diff --git a/spec/serializers/alchemy/json_api/ingredient_audio_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_audio_serializer_spec.rb index 7fedc46..22f56cf 100644 --- a/spec/serializers/alchemy/json_api/ingredient_audio_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_audio_serializer_spec.rb @@ -12,7 +12,7 @@ attachment: attachment, autoplay: false, controls: true, - muted: true, + muted: true ) end diff --git a/spec/serializers/alchemy/json_api/ingredient_file_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_file_serializer_spec.rb index 7eea312..fbbc29a 100644 --- a/spec/serializers/alchemy/json_api/ingredient_file_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_file_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::IngredientFileSerializer do diff --git a/spec/serializers/alchemy/json_api/ingredient_headline_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_headline_serializer_spec.rb index 189dc4f..360ece9 100644 --- a/spec/serializers/alchemy/json_api/ingredient_headline_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_headline_serializer_spec.rb @@ -11,7 +11,7 @@ role: "headline", value: "Hello you world", size: 2, - level: 3, + level: 3 ) end @@ -26,7 +26,7 @@ hash_including( value: "Hello you world", level: 3, - size: 2, + size: 2 ) ) end diff --git a/spec/serializers/alchemy/json_api/ingredient_html_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_html_serializer_spec.rb index bd0cc78..cc1206d 100644 --- a/spec/serializers/alchemy/json_api/ingredient_html_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_html_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::IngredientHtmlSerializer do diff --git a/spec/serializers/alchemy/json_api/ingredient_link_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_link_serializer_spec.rb index 23a5eee..ec80a8c 100644 --- a/spec/serializers/alchemy/json_api/ingredient_link_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_link_serializer_spec.rb @@ -9,7 +9,7 @@ value: "/hello", link_class_name: "external", link_target: "_blank", - link_title: "Greetings!", + link_title: "Greetings!" ) end diff --git a/spec/serializers/alchemy/json_api/ingredient_node_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_node_serializer_spec.rb index 70057d1..b891ae4 100644 --- a/spec/serializers/alchemy/json_api/ingredient_node_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_node_serializer_spec.rb @@ -28,7 +28,7 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has the right keys and values" do - expect(subject[:node]).to eq(data: { id: node.id.to_s, type: :node }) + expect(subject[:node]).to eq(data: {id: node.id.to_s, type: :node}) end end diff --git a/spec/serializers/alchemy/json_api/ingredient_page_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_page_serializer_spec.rb index 7883c20..505ce0b 100644 --- a/spec/serializers/alchemy/json_api/ingredient_page_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_page_serializer_spec.rb @@ -30,7 +30,7 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has page object" do - expect(subject[:page]).to eq(data: { id: page.id.to_s, type: :page }) + expect(subject[:page]).to eq(data: {id: page.id.to_s, type: :page}) end end diff --git a/spec/serializers/alchemy/json_api/ingredient_picture_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_picture_serializer_spec.rb index 70c8c23..56d610a 100644 --- a/spec/serializers/alchemy/json_api/ingredient_picture_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_picture_serializer_spec.rb @@ -8,7 +8,7 @@ :alchemy_ingredient_picture, title: "Picture", link: "/hello", - picture: picture, + picture: picture ) end @@ -51,7 +51,7 @@ end let(:size) do - { size: "100x100" } + {size: "100x100"} end it do @@ -60,7 +60,7 @@ context "without y dimension" do let(:size) do - { size: "100x" } + {size: "100x"} end it "infers height from ratio" do @@ -70,7 +70,7 @@ context "without x dimension" do let(:size) do - { size: "x50" } + {size: "x50"} end it "infers width from ratio" do @@ -94,7 +94,7 @@ before do expect(ingredient).to receive(:settings).at_least(:once) do { - srcset: srcset_definition, + srcset: srcset_definition } end end @@ -112,15 +112,15 @@ desc: "100w", width: "100", height: "100", - type: "image/png", + type: "image/png" }, { url: instance_of(String), desc: "200w", width: "200", height: "100", - type: "image/png", - }, + type: "image/png" + } ] ) end @@ -131,12 +131,12 @@ [ { size: "100x100", - crop: true, + crop: true }, { size: "200x100", - format: "jpg", - }, + format: "jpg" + } ] end @@ -148,15 +148,15 @@ desc: "100w", width: "100", height: "100", - type: "image/png", + type: "image/png" }, { url: a_string_matching(%r{.jpg}), desc: "200w", width: "200", height: "100", - type: "image/jpeg", - }, + type: "image/jpeg" + } ] ) end diff --git a/spec/serializers/alchemy/json_api/ingredient_richtext_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_richtext_serializer_spec.rb index 450e4f7..3c42576 100644 --- a/spec/serializers/alchemy/json_api/ingredient_richtext_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_richtext_serializer_spec.rb @@ -6,7 +6,7 @@ let(:ingredient) do FactoryBot.create( :alchemy_ingredient_richtext, - value: "

Hello

", + value: "

Hello

" ) end diff --git a/spec/serializers/alchemy/json_api/ingredient_text_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_text_serializer_spec.rb index 9233f22..aafb360 100644 --- a/spec/serializers/alchemy/json_api/ingredient_text_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_text_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::IngredientTextSerializer do diff --git a/spec/serializers/alchemy/json_api/ingredient_video_serializer_spec.rb b/spec/serializers/alchemy/json_api/ingredient_video_serializer_spec.rb index 2393f89..6a2b3ff 100644 --- a/spec/serializers/alchemy/json_api/ingredient_video_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/ingredient_video_serializer_spec.rb @@ -12,7 +12,7 @@ attachment: attachment, allow_fullscreen: true, autoplay: false, - controls: true, + controls: true ) end diff --git a/spec/serializers/alchemy/json_api/language_serializer_spec.rb b/spec/serializers/alchemy/json_api/language_serializer_spec.rb index 43e1fa8..c51f072 100644 --- a/spec/serializers/alchemy/json_api/language_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/language_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::LanguageSerializer do @@ -29,10 +30,10 @@ subject { serializer.serializable_hash[:data][:relationships] } it "has the right keys and values" do - expect(subject[:root_page]).to eq(data: { id: root_page.id.to_s, type: :page }) - expect(subject[:pages]).to eq(data: [{ id: root_page.id.to_s, type: :page }]) - expect(subject[:menus]).to eq(data: [{ id: menu.id.to_s, type: :node }]) - expect(subject[:menu_items]).to eq(data: [{ id: menu.id.to_s, type: :node }, { id: menu_node.id.to_s, type: :node }]) + expect(subject[:root_page]).to eq(data: {id: root_page.id.to_s, type: :page}) + expect(subject[:pages]).to eq(data: [{id: root_page.id.to_s, type: :page}]) + expect(subject[:menus]).to eq(data: [{id: menu.id.to_s, type: :node}]) + expect(subject[:menu_items]).to eq(data: [{id: menu.id.to_s, type: :node}, {id: menu_node.id.to_s, type: :node}]) end end end diff --git a/spec/serializers/alchemy/json_api/node_serializer_spec.rb b/spec/serializers/alchemy/json_api/node_serializer_spec.rb index b7f1c79..6498ff1 100644 --- a/spec/serializers/alchemy/json_api/node_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/node_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::NodeSerializer do @@ -8,7 +9,7 @@ name: "A Node", url: "/acdc", title: "Pop-up explanation", - nofollow: true, + nofollow: true ) end let(:options) { {} } @@ -38,7 +39,7 @@ url: "/acdc", title: "Pop-up explanation", nofollow: true, - children: [child_node], + children: [child_node] ) end @@ -46,7 +47,7 @@ let(:child_of_child_node) { FactoryBot.create(:alchemy_node) } it "has children" do - expect(subject[:children]).to eq(data: [{ id: child_node.id.to_s, type: :node }]) + expect(subject[:children]).to eq(data: [{id: child_node.id.to_s, type: :node}]) end end @@ -58,14 +59,14 @@ url: "/acdc", title: "Pop-up explanation", nofollow: true, - page: page, + page: page ) end let(:page) { FactoryBot.create(:alchemy_page) } it "has page" do - expect(subject[:page]).to eq(data: { id: page.id.to_s, type: :page }) + expect(subject[:page]).to eq(data: {id: page.id.to_s, type: :page}) end end end diff --git a/spec/serializers/alchemy/json_api/page_serializer_spec.rb b/spec/serializers/alchemy/json_api/page_serializer_spec.rb index 678f9dc..cbc3d45 100644 --- a/spec/serializers/alchemy/json_api/page_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/page_serializer_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "rails_helper" RSpec.describe Alchemy::JsonApi::PageSerializer do @@ -10,7 +11,7 @@ title: "Page Title", meta_keywords: "Meta Keywords", meta_description: "Meta Description", - tag_list: "Tag1,Tag2", + tag_list: "Tag1,Tag2" ) end let!(:legacy_url) { Alchemy::LegacyPageUrl.create(urlname: "/other", page: alchemy_page) } @@ -50,10 +51,10 @@ it "does not include trashed, fixed or hidden elements" do expect(subject[:elements]).to eq( data: [ - { id: element.id.to_s, type: :element }, - ], + {id: element.id.to_s, type: :element} + ] ) - expect(subject[:language]).to eq(data: { id: page.language_id.to_s, type: :language }) + expect(subject[:language]).to eq(data: {id: page.language_id.to_s, type: :language}) end end @@ -61,18 +62,18 @@ it "does not include trashed, non-fixed or hidden elements" do expect(subject[:fixed_elements]).to eq( data: [ - { id: fixed_element.id.to_s, type: :element }, - ], + {id: fixed_element.id.to_s, type: :element} + ] ) - expect(subject[:language]).to eq(data: { id: page.language_id.to_s, type: :language }) + expect(subject[:language]).to eq(data: {id: page.language_id.to_s, type: :language}) end end it "has ancestors relationship" do expect(subject[:ancestors]).to eq( data: [ - { id: page.parent_id.to_s, type: :page }, - ], + {id: page.parent_id.to_s, type: :page} + ] ) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5ec9648..75c51cc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause From 0b28e436cbafeffabe0598038d24a7d42f54b60a Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 6 May 2024 17:25:26 +0200 Subject: [PATCH 10/17] fix(HTTP Cache): Use cache_key_with_version for etag The cache_key is just the page id. We need to include the cache_version (the page published_at date). --- .../alchemy/json_api/pages_controller.rb | 4 ++-- spec/requests/alchemy/json_api/pages_spec.rb | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index 850ee4c..ffaafdb 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -12,7 +12,7 @@ def index @pages = filtered_pages.result 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) + elsif stale?(last_modified: @pages.maximum(:published_at), etag: @pages.max_by(&:cache_key_with_version)&.cache_key_with_version) render_pages_json(allowed) end end @@ -25,7 +25,7 @@ def show render(jsonapi: api_page(load_page)) && return end - if stale?(last_modified: last_modified_for(@page), etag: @page.cache_key) + if stale?(last_modified: last_modified_for(@page), etag: @page.cache_key_with_version) # Only load page with all includes when browser cache is stale render jsonapi: api_page(load_page) end diff --git a/spec/requests/alchemy/json_api/pages_spec.rb b/spec/requests/alchemy/json_api/pages_spec.rb index 114112d..1d908cf 100644 --- a/spec/requests/alchemy/json_api/pages_spec.rb +++ b/spec/requests/alchemy/json_api/pages_spec.rb @@ -17,13 +17,15 @@ ) end + let(:published_at) { DateTime.parse("2024-05-04 00:00:00") } + describe "GET /alchemy/json_api/pages/:id" do context "a published page" do let(:page) do FactoryBot.create( :alchemy_page, :public, - published_at: DateTime.yesterday + published_at: published_at ) end @@ -36,7 +38,7 @@ 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["ETag"]).to eq('W/"0741fe32d81bfdabfeb47d9939c5f6b7"') expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate") end @@ -46,7 +48,7 @@ :alchemy_page, :public, :restricted, - published_at: DateTime.yesterday + published_at: published_at ) end @@ -62,7 +64,7 @@ :alchemy_page, :public, page_layout: "contact", - published_at: DateTime.yesterday + published_at: published_at ) end @@ -172,7 +174,7 @@ context "with layoutpages and unpublished pages" do let!(:layoutpage) { FactoryBot.create(:alchemy_page, :layoutpage, :public) } let!(:non_public_page) { FactoryBot.create(:alchemy_page) } - let!(:public_page) { FactoryBot.create(:alchemy_page, :public, published_at: Date.yesterday) } + let!(:public_page) { FactoryBot.create(:alchemy_page, :public, published_at: published_at) } context "as anonymous user" do let!(:pages) { [public_page] } @@ -196,7 +198,7 @@ :alchemy_page, :public, :restricted, - published_at: DateTime.yesterday + published_at: published_at ) end @@ -212,7 +214,7 @@ :alchemy_page, :public, page_layout: "contact", - published_at: DateTime.yesterday + published_at: published_at ) end @@ -265,7 +267,7 @@ context "with filters" do let!(:standard_page) { FactoryBot.create(:alchemy_page, :public, published_at: 2.weeks.ago) } let!(:news_page) { FactoryBot.create(:alchemy_page, :public, page_layout: "news", published_at: 1.week.ago) } - let!(:news_page2) { FactoryBot.create(:alchemy_page, :public, name: "News", page_layout: "news", published_at: Date.yesterday) } + let!(:news_page2) { FactoryBot.create(:alchemy_page, :public, name: "News", page_layout: "news", published_at: published_at) } it "returns only matching pages by page_layout" do get alchemy_json_api.pages_path(filter: {page_layout_eq: "news"}) @@ -299,7 +301,7 @@ 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["ETag"]).to eq('W/"e7a1c8beb22b58e94a605594d79766ad"') expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate") end end From 3a8a8957c126ebf4df8940c377ec1cb365b36886 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Tue, 7 May 2024 11:55:46 +0200 Subject: [PATCH 11/17] Bump alchemy-json_api to 2.1.0 --- CHANGELOG.md | 11 +++++++++++ lib/alchemy/json_api/version.rb | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e87885..bd06bfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [v2.1.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.1.0) (2024-05-07) + +[Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.0.1...v2.1.0) + +**Merged pull requests:** + +- Standard linting [\#78](https://github.com/AlchemyCMS/alchemy-json_api/pull/78) ([tvdeyen](https://github.com/tvdeyen)) +- CI fixes [\#77](https://github.com/AlchemyCMS/alchemy-json_api/pull/77) ([tvdeyen](https://github.com/tvdeyen)) +- fix\(HTTP Cache\): Use cache\_key\_with\_version for etag [\#76](https://github.com/AlchemyCMS/alchemy-json_api/pull/76) ([tvdeyen](https://github.com/tvdeyen)) +- Allow jsonapi.rb 2.x [\#75](https://github.com/AlchemyCMS/alchemy-json_api/pull/75) ([mamhoff](https://github.com/mamhoff)) + ## [v2.0.1](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.0.1) (2023-07-18) [Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.0.0...v2.0.1) diff --git a/lib/alchemy/json_api/version.rb b/lib/alchemy/json_api/version.rb index 4e63f33..0486d2f 100644 --- a/lib/alchemy/json_api/version.rb +++ b/lib/alchemy/json_api/version.rb @@ -2,6 +2,6 @@ module Alchemy module JsonApi - VERSION = "2.0.1" + VERSION = "2.1.0" end end From 5de2ef3e45929854310e15f7fd81e217e85fe195 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 13 May 2024 09:59:40 +0200 Subject: [PATCH 12/17] Fix specs by traveling to the past May 2024 was a long time in the future some time in the past. --- spec/rails_helper.rb | 2 +- spec/requests/alchemy/json_api/pages_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c934809..82d7d7f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -84,6 +84,6 @@ config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") - + config.include ActiveSupport::Testing::TimeHelpers 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 1d908cf..695d39d 100644 --- a/spec/requests/alchemy/json_api/pages_spec.rb +++ b/spec/requests/alchemy/json_api/pages_spec.rb @@ -17,6 +17,12 @@ ) end + around do |example| + travel_to(published_at - 1.week) do + example.run + end + end + let(:published_at) { DateTime.parse("2024-05-04 00:00:00") } describe "GET /alchemy/json_api/pages/:id" do From ae5602b56c9307067d06759ffe2ae8cbf38c6c97 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 15 May 2024 10:59:36 +0200 Subject: [PATCH 13/17] Do not convert cache duration to hours Hours is not fine enough as granularity for good cache control. For this reason, the `Cache-Control` header operates with a granularity of seconds as well. Let's also work with seconds here. This is literally a breaking change, but it will only break caches. --- app/controllers/alchemy/json_api/nodes_controller.rb | 4 +++- app/controllers/alchemy/json_api/pages_controller.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/alchemy/json_api/nodes_controller.rb b/app/controllers/alchemy/json_api/nodes_controller.rb index 0ce2e59..3829f21 100644 --- a/app/controllers/alchemy/json_api/nodes_controller.rb +++ b/app/controllers/alchemy/json_api/nodes_controller.rb @@ -3,6 +3,8 @@ module Alchemy module JsonApi class NodesController < JsonApi::BaseController + THREE_HOURS = 10800 + def index @nodes = node_scope.select(:id, :updated_at) if stale?(last_modified: @nodes.maximum(:updated_at), etag: @nodes) @@ -17,7 +19,7 @@ def index private def cache_duration - ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", 3).to_i.hours + ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", THREE_HOURS).to_i end def jsonapi_meta(nodes) diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index ffaafdb..fde1739 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -3,6 +3,8 @@ module Alchemy module JsonApi class PagesController < JsonApi::BaseController + THREE_HOURS = 10800 + before_action :load_page_for_cache_key, only: :show def index @@ -47,7 +49,7 @@ def render_pages_json(allowed) end def cache_duration - ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", 3).to_i.hours + ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", THREE_HOURS).to_i end def caching_options From 230fdea2f21f261dcd31df62753523e99aa1b9aa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 13 May 2024 10:53:00 +0200 Subject: [PATCH 14/17] Rely on improved etag for caching The Last-Modified header is a weaker cache key than the etag, and thus we rely solely on the etag from now on. Browsers and Rails will ignore it if it's not set while validating if a request is fresh. Also sets the etag based on JSONAPI's typical set of params, so that we get a different etag for different `include`, `sort`, `page`, `fields`, or `filter` params. --- .../json_api/admin/pages_controller.rb | 2 +- .../alchemy/json_api/pages_controller.rb | 21 +++-- .../json_api/admin/layout_pages_spec.rb | 2 +- .../alchemy/json_api/admin/pages_spec.rb | 2 +- spec/requests/alchemy/json_api/pages_spec.rb | 88 +++++++++++++++++-- 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/app/controllers/alchemy/json_api/admin/pages_controller.rb b/app/controllers/alchemy/json_api/admin/pages_controller.rb index 0bf5660..9f4071f 100644 --- a/app/controllers/alchemy/json_api/admin/pages_controller.rb +++ b/app/controllers/alchemy/json_api/admin/pages_controller.rb @@ -25,7 +25,7 @@ def set_current_preview end end - def last_modified_for(page) + def page_cache_key(page) page.updated_at end diff --git a/app/controllers/alchemy/json_api/pages_controller.rb b/app/controllers/alchemy/json_api/pages_controller.rb index fde1739..0e7a72b 100644 --- a/app/controllers/alchemy/json_api/pages_controller.rb +++ b/app/controllers/alchemy/json_api/pages_controller.rb @@ -4,6 +4,7 @@ module Alchemy module JsonApi class PagesController < JsonApi::BaseController THREE_HOURS = 10800 + JSONAPI_STALEMAKERS = %i[include fields sort filter page] before_action :load_page_for_cache_key, only: :show @@ -12,9 +13,10 @@ def index jsonapi_filter(page_scope, allowed) do |filtered_pages| @pages = filtered_pages.result + if !@pages.all?(&:cache_page?) render_pages_json(allowed) && return - elsif stale?(last_modified: @pages.maximum(:published_at), etag: @pages.max_by(&:cache_key_with_version)&.cache_key_with_version) + elsif stale?(etag: etag(@pages)) render_pages_json(allowed) end end @@ -27,7 +29,7 @@ def show render(jsonapi: api_page(load_page)) && return end - if stale?(last_modified: last_modified_for(@page), etag: @page.cache_key_with_version) + if stale?(etag: etag(@page)) # Only load page with all includes when browser cache is stale render jsonapi: api_page(load_page) end @@ -62,10 +64,6 @@ def load_page_for_cache_key .or(page_scope.where(urlname: params[:path])).first! end - def last_modified_for(page) - page.published_at - end - def jsonapi_meta(pages) pagination = jsonapi_pagination_meta(pages) @@ -119,6 +117,17 @@ def api_page(page) Alchemy::JsonApi::Page.new(page, page_version_type: page_version_type) end + def etag(pages) + pages = Array.wrap(pages) + return unless pages.any? + relevant_params = params.to_unsafe_hash.slice(*JSONAPI_STALEMAKERS).flatten.compact + pages.map { |page| page_cache_key(page) }.concat(relevant_params) + end + + def page_cache_key(page) + page.cache_key_with_version + end + def base_page_scope # cancancan is not able to merge our complex AR scopes for logged in users if can?(:edit_content, ::Alchemy::Page) diff --git a/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb b/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb index fa209e2..c93dc69 100644 --- a/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb +++ b/spec/requests/alchemy/json_api/admin/layout_pages_spec.rb @@ -112,7 +112,7 @@ it "sets cache headers" do get alchemy_json_api.admin_layout_page_path(page) - expect(response.headers["Last-Modified"]).to eq(page.updated_at.utc.httpdate) + expect(response.headers["Last-Modified"]).to be nil expect(response.headers["ETag"]).to match(/W\/".+"/) expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate") end diff --git a/spec/requests/alchemy/json_api/admin/pages_spec.rb b/spec/requests/alchemy/json_api/admin/pages_spec.rb index 939b761..e3aba0a 100644 --- a/spec/requests/alchemy/json_api/admin/pages_spec.rb +++ b/spec/requests/alchemy/json_api/admin/pages_spec.rb @@ -35,7 +35,7 @@ it "sets cache headers" do get alchemy_json_api.admin_page_path(page) - expect(response.headers["Last-Modified"]).to eq(page.updated_at.utc.httpdate) + expect(response.headers["Last-Modified"]).to be(nil) expect(response.headers["ETag"]).to match(/W\/".+"/) expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate") end diff --git a/spec/requests/alchemy/json_api/pages_spec.rb b/spec/requests/alchemy/json_api/pages_spec.rb index 695d39d..9eaad63 100644 --- a/spec/requests/alchemy/json_api/pages_spec.rb +++ b/spec/requests/alchemy/json_api/pages_spec.rb @@ -43,9 +43,32 @@ 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 eq('W/"0741fe32d81bfdabfeb47d9939c5f6b7"') + first_etag = response.headers["Last-Modified"] + expect(response.headers["ETag"]).to match(/W\/".+"/) expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate") + get alchemy_json_api.page_path(page) + expect(response.headers["Last-Modified"]).to eq(first_etag) + end + + it "returns a different etag if different filters are present" do + get alchemy_json_api.page_path(page) + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(filter: {page_layout_eq: "standard"}) + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different include params are present" do + get alchemy_json_api.page_path(page) + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(include: "all_elements.ingredients") + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different fields params are present" do + get alchemy_json_api.page_path(page) + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(fields: "urlname") + expect(response.headers["ETag"]).not_to eq(etag) end context "if page is restricted" do @@ -178,9 +201,9 @@ describe "GET /alchemy/json_api/pages" do context "with layoutpages and unpublished pages" do - let!(:layoutpage) { FactoryBot.create(:alchemy_page, :layoutpage, :public) } - let!(:non_public_page) { FactoryBot.create(:alchemy_page) } - let!(:public_page) { FactoryBot.create(:alchemy_page, :public, published_at: published_at) } + let!(:layoutpage) { FactoryBot.create(:alchemy_page, :layoutpage, :public, page_layout: "standard") } + let!(:non_public_page) { FactoryBot.create(:alchemy_page, page_layout: "standard") } + let!(:public_page) { FactoryBot.create(:alchemy_page, :public, published_at: published_at, page_layout: "standard") } context "as anonymous user" do let!(:pages) { [public_page] } @@ -193,11 +216,53 @@ 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["Last-Modified"]).to be_nil expect(response.headers["ETag"]).to match(/W\/".+"/) expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate") end + it "returns a different etag if different filters are present" do + get alchemy_json_api.pages_path + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(filter: {page_layout_eq: "standard"}) + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different sort params are present" do + get alchemy_json_api.pages_path + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(sort: "-id") + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different include params are present" do + get alchemy_json_api.pages_path + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(include: "all_elements.ingredients") + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different fields params are present" do + get alchemy_json_api.pages_path + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(fields: "urlname") + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different fields params are present" do + get alchemy_json_api.pages_path + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(page: {number: 2, size: 1}) + expect(response.headers["ETag"]).not_to eq(etag) + end + + it "returns a different etag if different JSONAPI params have the same value" do + get alchemy_json_api.pages_path(sort: "author") + etag = response.headers["ETag"] + get alchemy_json_api.pages_path(fields: "author") + expect(response.headers["ETag"]).not_to eq(etag) + end + context "if one page is restricted" do let!(:restricted_page) do FactoryBot.create( @@ -304,11 +369,16 @@ stub_alchemy_config(:cache_pages, true) end - it "sets cache headers of latest matching page" do + it "sets constant etag" 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 eq('W/"e7a1c8beb22b58e94a605594d79766ad"') + expect(response.headers["ETag"]).to match(/W\/".+"/) + + first_etag = response.headers["Last-Modified"] + expect(response.headers["Cache-Control"]).to eq("max-age=10800, public, must-revalidate") + + get alchemy_json_api.pages_path(filter: {page_layout_eq: "news"}) + expect(response.headers["Last-Modified"]).to eq(first_etag) end end end From f38b20253c8c5f3196d592c66903ad2775091e5d Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 16 May 2024 11:50:41 +0200 Subject: [PATCH 15/17] Bump alchemy-json_api to 2.2.0 --- CHANGELOG.md | 15 +++++++++++---- lib/alchemy/json_api/version.rb | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd06bfe..c55b3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [v2.2.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.2.0) (2024-05-16) + +[Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.1.0...v2.2.0) + +**Merged pull requests:** + +- Do not convert cache duration to hours [\#81](https://github.com/AlchemyCMS/alchemy-json_api/pull/81) ([mamhoff](https://github.com/mamhoff)) +- Improve etag generation [\#80](https://github.com/AlchemyCMS/alchemy-json_api/pull/80) ([mamhoff](https://github.com/mamhoff)) +- Fix specs by traveling to the past [\#79](https://github.com/AlchemyCMS/alchemy-json_api/pull/79) ([mamhoff](https://github.com/mamhoff)) + ## [v2.1.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.1.0) (2024-05-07) [Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.0.1...v2.1.0) @@ -10,15 +20,12 @@ - CI fixes [\#77](https://github.com/AlchemyCMS/alchemy-json_api/pull/77) ([tvdeyen](https://github.com/tvdeyen)) - fix\(HTTP Cache\): Use cache\_key\_with\_version for etag [\#76](https://github.com/AlchemyCMS/alchemy-json_api/pull/76) ([tvdeyen](https://github.com/tvdeyen)) - Allow jsonapi.rb 2.x [\#75](https://github.com/AlchemyCMS/alchemy-json_api/pull/75) ([mamhoff](https://github.com/mamhoff)) +- Fix page search [\#71](https://github.com/AlchemyCMS/alchemy-json_api/pull/71) ([tvdeyen](https://github.com/tvdeyen)) ## [v2.0.1](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.0.1) (2023-07-18) [Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.0.0...v2.0.1) -**Merged pull requests:** - -- Fix page search [\#71](https://github.com/AlchemyCMS/alchemy-json_api/pull/71) ([tvdeyen](https://github.com/tvdeyen)) - ## [v2.0.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.0.0) (2023-03-31) [Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v1.2.0...v2.0.0) diff --git a/lib/alchemy/json_api/version.rb b/lib/alchemy/json_api/version.rb index 0486d2f..8a89c8e 100644 --- a/lib/alchemy/json_api/version.rb +++ b/lib/alchemy/json_api/version.rb @@ -2,6 +2,6 @@ module Alchemy module JsonApi - VERSION = "2.1.0" + VERSION = "2.2.0" end end From fdc48df8a2d51680826dc1f16ce161ec4dcab663 Mon Sep 17 00:00:00 2001 From: Andrew Manley Date: Thu, 6 Jun 2024 18:36:49 -0600 Subject: [PATCH 16/17] Add restricted value to page serializer --- app/serializers/alchemy/json_api/page_serializer.rb | 3 ++- spec/serializers/alchemy/json_api/page_serializer_spec.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/serializers/alchemy/json_api/page_serializer.rb b/app/serializers/alchemy/json_api/page_serializer.rb index 8d6f962..7015f54 100644 --- a/app/serializers/alchemy/json_api/page_serializer.rb +++ b/app/serializers/alchemy/json_api/page_serializer.rb @@ -15,7 +15,8 @@ class PageSerializer < BaseSerializer :meta_keywords, :meta_description, :created_at, - :updated_at + :updated_at, + :restricted ) cache_options store: Rails.cache, namespace: "alchemy-jsonapi" diff --git a/spec/serializers/alchemy/json_api/page_serializer_spec.rb b/spec/serializers/alchemy/json_api/page_serializer_spec.rb index cbc3d45..bf6eef6 100644 --- a/spec/serializers/alchemy/json_api/page_serializer_spec.rb +++ b/spec/serializers/alchemy/json_api/page_serializer_spec.rb @@ -36,6 +36,7 @@ expect(attributes[:created_at]).to eq(page.created_at) expect(attributes[:updated_at]).to eq(page.updated_at) expect(attributes[:legacy_urls]).to eq(["/other"]) + expect(attributes[:restricted]).to be false expect(attributes.keys).not_to include(:tag_list, :status) end end From 85d6ec7a73cef370e2978cd9f7fb7309f1864d95 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 10 Jun 2024 21:26:46 +0200 Subject: [PATCH 17/17] Bump alchemy-json_api to 2.3.0 --- CHANGELOG.md | 8 ++++++++ lib/alchemy/json_api/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c55b3ad..fdf6fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v2.3.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.3.0) (2024-06-10) + +[Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.2.0...v2.3.0) + +**Merged pull requests:** + +- Add restricted value to page serializer [\#82](https://github.com/AlchemyCMS/alchemy-json_api/pull/82) ([manleyac](https://github.com/manleyac)) + ## [v2.2.0](https://github.com/AlchemyCMS/alchemy-json_api/tree/v2.2.0) (2024-05-16) [Full Changelog](https://github.com/AlchemyCMS/alchemy-json_api/compare/v2.1.0...v2.2.0) diff --git a/lib/alchemy/json_api/version.rb b/lib/alchemy/json_api/version.rb index 8a89c8e..36c1ce0 100644 --- a/lib/alchemy/json_api/version.rb +++ b/lib/alchemy/json_api/version.rb @@ -2,6 +2,6 @@ module Alchemy module JsonApi - VERSION = "2.2.0" + VERSION = "2.3.0" end end