From b95a82eeb8cb4dcebf5f5af260f9d44854ead91b Mon Sep 17 00:00:00 2001 From: Domizio Demichelis <dd.nexus@gmail.com> Date: Mon, 13 Jan 2025 18:37:38 +0700 Subject: [PATCH] WIP: Super refactoring 8 --- gem/lib/pagy/backend.rb | 20 ++++++- gem/lib/pagy/exceptions.rb | 8 +++ gem/lib/pagy/extras/jsonapi.rb | 70 ----------------------- gem/lib/pagy/helpers/url.rb | 28 ++++----- gem/lib/pagy/loaders/backend.rb | 3 +- gem/lib/pagy/mixins/headers.rb | 3 +- gem/lib/pagy/{helpers => mixins}/links.rb | 3 +- test/pagy/extras/jsonapi_test.rb | 30 ++++------ test/pagy/extras/jsonapi_test.rb.yaml | 26 ++++----- 9 files changed, 66 insertions(+), 125 deletions(-) delete mode 100644 gem/lib/pagy/extras/jsonapi.rb rename gem/lib/pagy/{helpers => mixins}/links.rb (91%) diff --git a/gem/lib/pagy/backend.rb b/gem/lib/pagy/backend.rb index 25256dd0e..6b807ab04 100644 --- a/gem/lib/pagy/backend.rb +++ b/gem/lib/pagy/backend.rb @@ -1,8 +1,8 @@ # See Pagy::Backend API documentation: https://ddnexus.github.io/pagy/docs/api/backend # frozen_string_literal: true +require_relative 'helpers/url' require_relative 'loaders/backend' - class Pagy # Define a few generic methods to paginate a collection out of the box, # or any collection by overriding any of the `pagy_*` methods in your controller. @@ -29,16 +29,30 @@ def pagy_get_limit(vars) # Get the limit from the request # Overridable by the jsonapi extra def pagy_requested_limit(vars) - params[vars[:limit_sym] || DEFAULT[:limit_sym]] + if pagy_skip_jsonapi?(vars) + params[vars[:limit_sym] || DEFAULT[:limit_sym]] + else + params[:page][vars[:limit_sym] || DEFAULT[:limit_sym]] + end end # Get the page integer from the params # Overridable by the jsonapi extra def pagy_get_page(vars, force_integer: true) - page = params[vars[:page_sym] || DEFAULT[:page_sym]] + page = if pagy_skip_jsonapi?(vars) + params[vars[:page_sym] || DEFAULT[:page_sym]] + else + params[:page][vars[:page_sym] || DEFAULT[:page_sym]] + end force_integer ? (page || 1).to_i : page end + def pagy_skip_jsonapi?(vars) + return true unless (vars.key?(:jsonapi) ? vars[:jsonapi] : DEFAULT[:jsonapi]) && params[:page] + + raise ReservedParamError, params[:page] unless params[:page].respond_to?(:fetch) + end + include Loaders::Backend end end diff --git a/gem/lib/pagy/exceptions.rb b/gem/lib/pagy/exceptions.rb index d35a14de4..a30a851a1 100644 --- a/gem/lib/pagy/exceptions.rb +++ b/gem/lib/pagy/exceptions.rb @@ -22,4 +22,12 @@ class I18nError < StandardError; end # Generic internal error class InternalError < StandardError; end + + # JsonApi :page param error + class ReservedParamError < StandardError + # Inform about the actual value + def initialize(value) + super("expected reserved :page param to be nil or Hash-like; got #{value.inspect}") + end + end end diff --git a/gem/lib/pagy/extras/jsonapi.rb b/gem/lib/pagy/extras/jsonapi.rb deleted file mode 100644 index 7067c5015..000000000 --- a/gem/lib/pagy/extras/jsonapi.rb +++ /dev/null @@ -1,70 +0,0 @@ -# See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/jsonapi -# frozen_string_literal: true - -require_relative '../helpers/url' -require_relative '../helpers/links' - -class Pagy - DEFAULT[:jsonapi] = true - - # Add a specialized backend method compliant with JSON:API - module JsonApiExtra - # JsonApi :page param error - class ReservedParamError < StandardError - # Inform about the actual value - def initialize(value) - super("expected reserved :page param to be nil or Hash-like; got #{value.inspect}") - end - end - - # Module overriding Backend - module BackendOverride - private - - include Url - include Links - - # Return the jsonapi links - alias pagy_jsonapi_links pagy_links - - # Should skip the jsonapi - def pagy_skip_jsonapi?(vars) - return true if vars[:jsonapi] == false || (vars[:jsonapi].nil? && DEFAULT[:jsonapi] == false) - # check the reserved :page param - raise ReservedParamError, params[:page] unless params[:page].respond_to?(:fetch) || params[:page].nil? - end - - # Override the Backend method - def pagy_get_page(vars, force_integer: true) - return super if pagy_skip_jsonapi?(vars) || params[:page].nil? - - page = params[:page][vars[:page_sym] || DEFAULT[:page_sym]] - force_integer ? (page || 1).to_i : page - end - - # Override the backend method - def pagy_requested_limit(vars) - return super if pagy_skip_jsonapi?(vars) - return unless params[:page] - - params[:page][vars[:limit_sym] || DEFAULT[:limit_sym]] - end - end - # :nocov: - Backend.prepend BackendOverride - # :nocov: - - # Module overriding UrlHelper - module UrlOverride - # Override Url method - def pagy_set_query_params(page, vars, query_params) - return super unless vars[:jsonapi] - - query_params['page'] ||= {} - query_params['page'][vars[:page_sym].to_s] = page - query_params['page'][vars[:limit_sym].to_s] = vars[:limit] if vars[:limit_requestable] - end - end - Url.prepend UrlOverride - end -end diff --git a/gem/lib/pagy/helpers/url.rb b/gem/lib/pagy/helpers/url.rb index 39183ccd9..258bf8cae 100644 --- a/gem/lib/pagy/helpers/url.rb +++ b/gem/lib/pagy/helpers/url.rb @@ -41,25 +41,19 @@ def escape(str) # It supports all Rack-based frameworks (Sinatra, Padrino, Rails, ...). # For non-rack environments you can just set the :url variable def pagy_page_url(pagy, page, absolute: false, fragment: nil, **_) - vars = pagy.vars + pagy_params = (vars = pagy.vars)[:params] + page_name, limit_name = vars.values_at(:page_sym, :limit_sym).map(&:to_s) + query_params = vars[:url] ? {} : request.GET.clone(freeze: false) - query_params.merge!(vars[:params].transform_keys(&:to_s)) if vars[:params].is_a?(Hash) - pagy_set_query_params(page, vars, query_params) - query_params = vars[:params].(query_params) if vars[:params].is_a?(Proc) - query_string = "?#{QueryUtils.build_nested_query(query_params, nil, - %i[page_sym limit_sym].map { |k| pagy.vars[k].to_s })}" - if vars[:url] - "#{vars[:url]}#{query_string}#{fragment}" - else - "#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{fragment}" - end - end + query_params.merge!(pagy_params.transform_keys(&:to_s)) if pagy_params.is_a?(Hash) + page_and_limit = { page_name => page }.tap { |h| h[limit_name] = vars[:limit] if vars[:limit_requestable] } + query_params.merge!(vars[:jsonapi] ? { 'page' => page_and_limit } : page_and_limit) + + query_params = pagy_params.(query_params) if pagy_params.is_a?(Proc) + query_string = "?#{QueryUtils.build_nested_query(query_params, nil, [page_name, limit_name])}" + return "#{vars[:url]}#{query_string}#{fragment}" if vars[:url] - # Add the page and limit params - # Overridable by the jsonapi extra - def pagy_set_query_params(page, vars, query_params) - query_params[vars[:page_sym].to_s] = page - query_params[vars[:limit_sym].to_s] = vars[:limit] if vars[:limit_requestable] + "#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{fragment}" end end end diff --git a/gem/lib/pagy/loaders/backend.rb b/gem/lib/pagy/loaders/backend.rb index 7cf085d89..422d673f1 100644 --- a/gem/lib/pagy/loaders/backend.rb +++ b/gem/lib/pagy/loaders/backend.rb @@ -23,7 +23,8 @@ def pagy_load_backend(...) pagy_meilisearch: PAGY_PATH.join('mixins/meilisearch').to_s, pagy_metadata: PAGY_PATH.join('mixins/metadata').to_s, pagy_offset: PAGY_PATH.join('mixins/offset').to_s, - pagy_searchkick: PAGY_PATH.join('mixins/searchkick').to_s }.freeze + pagy_searchkick: PAGY_PATH.join('mixins/searchkick').to_s, + pagy_links: PAGY_PATH.join('mixins/links') }.freeze BACKEND_METHOD_MIXINS.each_key do |method| class_eval "alias #{method} pagy_load_backend", __FILE__, __LINE__ # alias pagy_* pagy_load_backend diff --git a/gem/lib/pagy/mixins/headers.rb b/gem/lib/pagy/mixins/headers.rb index f104dc58d..19de95c2f 100644 --- a/gem/lib/pagy/mixins/headers.rb +++ b/gem/lib/pagy/mixins/headers.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true require_relative '../helpers/url' -require_relative '../helpers/links' +require_relative 'links' class Pagy DEFAULT[:headers] = { page: 'current-page', @@ -14,7 +14,6 @@ class Pagy private include Url - include Links # Merge the pagy headers into the response.headers def pagy_headers_merge(pagy) diff --git a/gem/lib/pagy/helpers/links.rb b/gem/lib/pagy/mixins/links.rb similarity index 91% rename from gem/lib/pagy/helpers/links.rb rename to gem/lib/pagy/mixins/links.rb index 411118e51..4250a6f1b 100644 --- a/gem/lib/pagy/helpers/links.rb +++ b/gem/lib/pagy/mixins/links.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true +# require_relative class Pagy - module Links + Backend.class_eval do def pagy_links(pagy, **) url_str = pagy_page_url(pagy, PAGE_TOKEN, **) { first: url_str.sub(PAGE_TOKEN, '1') }.tap do |links| diff --git a/test/pagy/extras/jsonapi_test.rb b/test/pagy/extras/jsonapi_test.rb index 4f8546ccb..6f26079d1 100644 --- a/test/pagy/extras/jsonapi_test.rb +++ b/test/pagy/extras/jsonapi_test.rb @@ -1,26 +1,22 @@ # frozen_string_literal: true require_relative '../../test_helper' -require 'pagy/extras/jsonapi' require_relative '../../mock_helpers/collection' require_relative '../../files/models' require_relative '../../mock_helpers/app' Pagy::DEFAULT[:limit_requestable] = true +Pagy::DEFAULT[:jsonapi] = true -describe 'pagy/mixins/jsonapi' do +describe 'jsonapi' do before do @collection = MockCollection.new end - it 'defaults to true' do - _(Pagy::DEFAULT[:jsonapi]).must_equal true - end - it 'raises PageParamError with page number' do app = MockApp.new(params: { page: 2 }) - _ { _pagy, _records = app.send(:pagy_offset, @collection) }.must_raise Pagy::JsonApiExtra::ReservedParamError + _ { _pagy, _records = app.send(:pagy_offset, @collection) }.must_raise Pagy::ReservedParamError end describe 'JsonApi' do @@ -50,13 +46,11 @@ Pagy::DEFAULT[:jsonapi] = true end it 'skips the :jsonapi with page:3' do - Pagy::DEFAULT[:jsonapi] = false app = MockApp.new(params: { page: 3 }) - pagy, _records = app.send(:pagy_offset, @collection, limit_requestable: false) + pagy, _records = app.send(:pagy_offset, @collection, jsonapi: false, limit_requestable: false) _(app.send(:pagy_page_url, pagy, 2)).must_equal '/foo?page=2' - pagy, _records = app.send(:pagy_offset, @collection) + pagy, _records = app.send(:pagy_offset, @collection, jsonapi: false) _(app.send(:pagy_page_url, pagy, 2)).must_equal '/foo?page=2&limit=20' - Pagy::DEFAULT[:jsonapi] = true end end describe 'JsonApi with custom named params' do @@ -72,35 +66,35 @@ _(app.send(:pagy_page_url, pagy, 4)).must_rematch :url end end - describe '#pagy_jsonapi_links' do + describe '#pagy_links' do it 'returns the ordered links' do app = MockApp.new(params: { page: { number: 3, size: 10 } }) pagy, _records = app.send(:pagy_offset, @collection, page_sym: :number, limit_sym: :size) - result = app.send(:pagy_jsonapi_links, pagy) + result = app.send(:pagy_links, pagy) _(result.keys).must_equal %i[first prev next last] _(result).must_rematch :result end it 'sets the prev value to null when the link is unavailable' do app = MockApp.new(params: { page: { page: 1 } }) pagy, _records = app.send(:pagy_offset, @collection) - result = app.send(:pagy_jsonapi_links, pagy) + result = app.send(:pagy_links, pagy) _(result[:prev]).must_be_nil end it 'sets the next value to null when the link is unavailable' do app = MockApp.new(params: { page: { page: 50 } }) pagy, _records = app.send(:pagy_offset, @collection) - result = app.send(:pagy_jsonapi_links, pagy) + result = app.send(:pagy_links, pagy) _(result[:next]).must_be_nil end end - describe '#pagy_jsonapi_links (keyset)' do + describe '#pagy_links (keyset)' do it 'returns the ordered links' do app = MockApp.new(params: { page: { latest: 'WzIwXQ', size: 10 } }) pagy, _records = app.send(:pagy_keyset, Pet.order(:id), page_sym: :latest, limit_sym: :size) - result = app.send(:pagy_jsonapi_links, pagy) + result = app.send(:pagy_links, pagy) _(result.keys).must_equal %i[first next] _(result).must_rematch :keyset_result end @@ -111,7 +105,7 @@ Pet.order(:id), page_sym: :latest, limit_sym: :size) - result = app.send(:pagy_jsonapi_links, pagy) + result = app.send(:pagy_links, pagy) _(result).must_rematch :keyset_result _(result[:next]).must_be_nil end diff --git a/test/pagy/extras/jsonapi_test.rb.yaml b/test/pagy/extras/jsonapi_test.rb.yaml index c6f209bcf..b6f81e350 100644 --- a/test/pagy/extras/jsonapi_test.rb.yaml +++ b/test/pagy/extras/jsonapi_test.rb.yaml @@ -1,22 +1,22 @@ --- -pagy/mixins/jsonapi___pagy_jsonapi_links_(keyset)_test_0002_sets_the_next_value_to_null_when_the_link_is_unavailable: - :keyset_result: - :first: "/foo?page%5Bsize%5D=50&page%5Blatest%5D=1" -pagy/mixins/jsonapi___pagy_jsonapi_links_(keyset)_test_0001_returns_the_ordered_links: - :keyset_result: - :first: "/foo?page%5Blatest%5D=1&page%5Bsize%5D=10" - :next: "/foo?page%5Blatest%5D=WzMwXQ&page%5Bsize%5D=10" -pagy/mixins/jsonapi__JsonApi_with_custom_named_params_test_0002_sets_custom_named_params: +jsonapi__JsonApi_with_custom_named_params_test_0002_sets_custom_named_params: :url: "/foo?page%5Bnumber%5D=4&page%5Bsize%5D=10" -pagy/mixins/jsonapi___pagy_jsonapi_links_test_0001_returns_the_ordered_links: +jsonapi___pagy_links_test_0001_returns_the_ordered_links: :result: :first: "/foo?page%5Bnumber%5D=1&page%5Bsize%5D=10" :prev: "/foo?page%5Bnumber%5D=2&page%5Bsize%5D=10" :next: "/foo?page%5Bnumber%5D=4&page%5Bsize%5D=10" :last: "/foo?page%5Bnumber%5D=100&page%5Bsize%5D=10" -pagy/mixins/jsonapi__JsonApi_test_0002_uses_the__jsonapi_with_page_3: - :url_1: "/foo?page%5Bpage%5D=2" - :url_2: "/foo?page%5Bpage%5D=2&page%5Blimit%5D=20" -pagy/mixins/jsonapi__JsonApi_test_0001_uses_the__jsonapi_with_page_nil: +jsonapi__JsonApi_test_0001_uses_the__jsonapi_with_page_nil: :url_1: "/foo?page%5Bpage%5D=1" :url_2: "/foo?page%5Bpage%5D=1&page%5Blimit%5D=20" +jsonapi__JsonApi_test_0002_uses_the__jsonapi_with_page_3: + :url_1: "/foo?page%5Bpage%5D=2" + :url_2: "/foo?page%5Bpage%5D=2&page%5Blimit%5D=20" +jsonapi___pagy_links_(keyset)_test_0001_returns_the_ordered_links: + :keyset_result: + :first: "/foo?page%5Blatest%5D=1&page%5Bsize%5D=10" + :next: "/foo?page%5Blatest%5D=WzMwXQ&page%5Bsize%5D=10" +jsonapi___pagy_links_(keyset)_test_0002_sets_the_next_value_to_null_when_the_link_is_unavailable: + :keyset_result: + :first: "/foo?page%5Blatest%5D=1&page%5Bsize%5D=50"