From f0e0d416ff97b7a0bfdf18d8e8e03dada9156e8f Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Mon, 5 Apr 2021 15:30:16 +0700 Subject: [PATCH 01/17] removed require for minitest/reporters --- test/test_helper.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 060f5445c..ece1e0e08 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -22,8 +22,3 @@ require_relative 'mock_helpers/view' require_relative 'mock_helpers/controller' require "minitest/autorun" - -unless ENV['TRAVIS'] - require 'minitest/reporters' - MiniTest::Reporters.use! -end From a8060b1d9367e21985fd92a01fcd9fca4e1e736a Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Mon, 5 Apr 2021 15:31:40 +0700 Subject: [PATCH 02/17] fixed doc typos --- docs/api/backend.md | 2 +- docs/api/frontend.md | 2 +- docs/index.md | 2 +- {docker => pagy-on-docker}/README.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename {docker => pagy-on-docker}/README.md (96%) diff --git a/docs/api/backend.md b/docs/api/backend.md index b46c99fb1..96d4f9d17 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -32,7 +32,7 @@ end All the methods in this module are prefixed with the `"pagy_"` string, to avoid any possible conflict with your own methods when you include the module in your controller. They are also all private, so they will not be available as actions. The methods prefixed with the `"pagy_get_"` string are sub-methods/getter methods that are intended to be overridden, not used directly. -Please, keep in mind that overriding any method is very easy with Pagy. Indeed you can do it right where you are using it: no need of monkey-patching or perform any tricky gymnic. +Please, keep in mind that overriding any method is very easy with Pagy. Indeed you can do it right where you are using it: no need of monkey-patching or perform any tricky gymmickry. ### pagy(collection, vars=nil) diff --git a/docs/api/frontend.md b/docs/api/frontend.md index fe358f103..c8275928c 100644 --- a/docs/api/frontend.md +++ b/docs/api/frontend.md @@ -30,7 +30,7 @@ use some of its method in some view: All the methods in this module are prefixed with the `"pagy_"` string in order to avoid any possible conflict with your own methods when you include the module in your helper. The methods prefixed with the `"pagy_get_"` string are sub-methods/getter methods that are intended to be overridden and not used directly. -Please, keep in mind that overriding any method is very easy with Pagy. Indeed you can do it right where you are using it: no need of monkey-patching or tricky gymnic. +Please, keep in mind that overriding any method is very easy with Pagy. Indeed you can do it right where you are using it: no need of monkey-patching or tricky gymmickry. ### pagy_nav(pagy) diff --git a/docs/index.md b/docs/index.md index dd1028ec8..0926634c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,6 +41,6 @@ You could even use it directly (without using any other Pagy code) in a small pa ## Really easy to customize -If options and configuration are not enough, you have a few specialized [extras](extras.md), and if all that is still not enough, any other special customization is at most _one step far_ from your own code. All the Pagy helpers are contained in 2 plain modules with no nesting. You include them in your code, so you can override any method right where you use it: no tricky gymnic required. +If options and configuration are not enough, you have a few specialized [extras](extras.md), and if all that is still not enough, any other special customization is at most _one step far_ from your own code. All the Pagy helpers are contained in 2 plain modules with no nesting. You include them in your code, so you can override any method right where you use it: no tricky gymmickry required. What could be easier? diff --git a/docker/README.md b/pagy-on-docker/README.md similarity index 96% rename from docker/README.md rename to pagy-on-docker/README.md index 6cfed3b55..17323641f 100644 --- a/docker/README.md +++ b/pagy-on-docker/README.md @@ -8,7 +8,7 @@ You can use it to develop changes, run tests and check a live preview of the doc The pagy docker environment has been designed to be useful for developing: -- It provides the infrastructure required (right version of ruby, jekyll server, env variable, tests, etc.) without the hassle to install and maintain anything in your system +- It provides the infrastructure required (right version of ruby, jekyll server, env variables, tests, etc.) without the hassle to install and maintain anything in your system - The local `pagy` dir is mounted at the container dir `/opt/project` so you can edit the files in your local pagy dir or in the container: they are the same files. - The gems are installed in the container `BUNDLE_PATH=/usr/local/bundle` and that dir is `chown`ed to your user, and mounted as the docker volume `pagy_bundle`. You can use the `bundle` command and it will be persisted in the volume, no need to rebuild the image nor pollute your own system. - Your container user `HOME` is preserved in the `pagy_user_home` volume, so you can even get back to the shell history in future sessions. From 8cd25d25346d5c990abcc7b2d16feac265a54424 Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Mon, 5 Apr 2021 15:32:45 +0700 Subject: [PATCH 03/17] renamed docker > pagy-on-docker --- {docker => pagy-on-docker}/docker-compose.yml | 4 ++-- {docker => pagy-on-docker}/pagy-jekyll.dockerfile | 0 {docker => pagy-on-docker}/pagy.dockerfile | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename {docker => pagy-on-docker}/docker-compose.yml (94%) rename {docker => pagy-on-docker}/pagy-jekyll.dockerfile (100%) rename {docker => pagy-on-docker}/pagy.dockerfile (100%) diff --git a/docker/docker-compose.yml b/pagy-on-docker/docker-compose.yml similarity index 94% rename from docker/docker-compose.yml rename to pagy-on-docker/docker-compose.yml index 3d6cab591..4fee04e96 100644 --- a/docker/docker-compose.yml +++ b/pagy-on-docker/docker-compose.yml @@ -1,5 +1,5 @@ -# Basic docker development environment -# it will keep the installed gems and HOME in the pagy_bundle and pagy_user_home docker volumes +# Basic pagy-on-docker development environment +# it will keep the installed gems and HOME in the pagy_bundle and pagy_user_home pagy-on-docker volumes # the gh-pages service will be updating a live preview for the documentation diff --git a/docker/pagy-jekyll.dockerfile b/pagy-on-docker/pagy-jekyll.dockerfile similarity index 100% rename from docker/pagy-jekyll.dockerfile rename to pagy-on-docker/pagy-jekyll.dockerfile diff --git a/docker/pagy.dockerfile b/pagy-on-docker/pagy.dockerfile similarity index 100% rename from docker/pagy.dockerfile rename to pagy-on-docker/pagy.dockerfile From 3a9e70fc982dbc459da46ebec06b2245ef3caafd Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 8 Apr 2021 20:46:45 +1200 Subject: [PATCH 04/17] Fix extra i18n namespacing (#293) * Add a test that looks at i18n working in Modules included * Demonstrates https://github.com/ddnexus/pagy/issues/290 * Correct name collision with I18n with UseI18n (fixes #290) --- lib/pagy/extras/i18n.rb | 4 ++-- test/mock_helpers/application_helper.rb | 8 ++++++++ test/pagy/extras/i18n_test.rb | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 test/mock_helpers/application_helper.rb diff --git a/lib/pagy/extras/i18n.rb b/lib/pagy/extras/i18n.rb index 2a16f3717..d125855a7 100644 --- a/lib/pagy/extras/i18n.rb +++ b/lib/pagy/extras/i18n.rb @@ -10,10 +10,10 @@ module Frontend Pagy::I18n.clear.instance_eval { undef :load; undef :t } # unload the pagy default constant for efficiency - module I18n + module UseI18nGem def pagy_t(key, **opts) = ::I18n.t(key, **opts) end - prepend I18n + prepend UseI18nGem end end diff --git a/test/mock_helpers/application_helper.rb b/test/mock_helpers/application_helper.rb new file mode 100644 index 000000000..b742b8505 --- /dev/null +++ b/test/mock_helpers/application_helper.rb @@ -0,0 +1,8 @@ +module ApplicationHelper + include Pagy::Frontend + + def any_method_name() + I18n.t("test") + end + +end \ No newline at end of file diff --git a/test/pagy/extras/i18n_test.rb b/test/pagy/extras/i18n_test.rb index e507d69f6..4975a362d 100644 --- a/test/pagy/extras/i18n_test.rb +++ b/test/pagy/extras/i18n_test.rb @@ -7,11 +7,17 @@ SimpleCov.command_name 'i18n' if ENV['RUN_SIMPLECOV'] == 'true' +require_relative '../../mock_helpers/application_helper' +include ApplicationHelper + describe Pagy::Frontend do let(:view) { MockView.new } describe "#pagy_t with I18n" do + it 'works with an included Module' do + ApplicationHelper.any_method_name() + end it 'pluralizes' do _(view.pagy_t('pagy.nav.prev')).must_equal "‹ Prev" From b2aaa01cd3d65715201cf46064f1ebb01f7bcda2 Mon Sep 17 00:00:00 2001 From: szTheory Date: Thu, 8 Apr 2021 08:51:54 +0000 Subject: [PATCH 05/17] README: fix typo (#289) [ci-skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dd29a099..fb30cae96 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Besides the classic pagination offered by the `pagy_nav` helpers, you can use a ### Related Projects -- [pagy-cursor](https://github.com/Uysim/pagy-cursor) An early stage proget that implements cursor pagination for AR +- [pagy-cursor](https://github.com/Uysim/pagy-cursor) An early stage project that implements cursor pagination for AR ## Resources From 3eef450e51a83bc5d0663911bbec059ebf38749c Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Thu, 8 Apr 2021 19:53:32 +0700 Subject: [PATCH 06/17] Simpler test for I18n namespace conflict --- test/mock_helpers/application_helper.rb | 8 -------- test/mock_helpers/view.rb | 4 ++++ test/pagy/extras/i18n_test.rb | 8 +++----- 3 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 test/mock_helpers/application_helper.rb diff --git a/test/mock_helpers/application_helper.rb b/test/mock_helpers/application_helper.rb deleted file mode 100644 index b742b8505..000000000 --- a/test/mock_helpers/application_helper.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ApplicationHelper - include Pagy::Frontend - - def any_method_name() - I18n.t("test") - end - -end \ No newline at end of file diff --git a/test/mock_helpers/view.rb b/test/mock_helpers/view.rb index e7d0fa897..6909d7ad0 100644 --- a/test/mock_helpers/view.rb +++ b/test/mock_helpers/view.rb @@ -8,6 +8,10 @@ def initialize(url='http://example.com:3000/foo?page=2') @url = url end + def test_i18n_call + I18n.t('test') + end + def request Rack::Request.new(Rack::MockRequest.env_for(@url)) end diff --git a/test/pagy/extras/i18n_test.rb b/test/pagy/extras/i18n_test.rb index 4975a362d..33766bd73 100644 --- a/test/pagy/extras/i18n_test.rb +++ b/test/pagy/extras/i18n_test.rb @@ -7,16 +7,14 @@ SimpleCov.command_name 'i18n' if ENV['RUN_SIMPLECOV'] == 'true' -require_relative '../../mock_helpers/application_helper' -include ApplicationHelper - describe Pagy::Frontend do let(:view) { MockView.new } describe "#pagy_t with I18n" do - it 'works with an included Module' do - ApplicationHelper.any_method_name() + + it 'does not conflict with the I18n gem namespace' do + view.test_i18n_call end it 'pluralizes' do From aac54aad8a6a0cc338f65ce07f19dd61132790f5 Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Thu, 8 Apr 2021 15:58:02 +0700 Subject: [PATCH 07/17] Renaming prepended modules with more specific and safe naming convention (see Issue #290 PR #293) --- lib/pagy/extras/elasticsearch_rails.rb | 2 +- lib/pagy/extras/items.rb | 8 ++++---- lib/pagy/extras/overflow.rb | 8 ++++---- lib/pagy/extras/searchkick.rb | 2 +- lib/pagy/extras/shared.rb | 4 ++-- lib/pagy/extras/trim.rb | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/pagy/extras/elasticsearch_rails.rb b/lib/pagy/extras/elasticsearch_rails.rb index f43eb7725..a16b03ffc 100644 --- a/lib/pagy/extras/elasticsearch_rails.rb +++ b/lib/pagy/extras/elasticsearch_rails.rb @@ -41,7 +41,7 @@ def pagy_elasticsearch_rails(pagy_search_args, vars={}) vars[:count] = total.is_a?(Hash) ? total['value'] : total pagy = Pagy.new(vars) # with :last_page overflow we need to re-run the method in order to get the hits - if defined?(Pagy::Overflow) && pagy.overflow? && pagy.vars[:overflow] == :last_page + if defined?(Pagy::UseOverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) end return pagy, called.empty? ? response : response.send(*called) diff --git a/lib/pagy/extras/items.rb b/lib/pagy/extras/items.rb index 646e627b9..75181696e 100644 --- a/lib/pagy/extras/items.rb +++ b/lib/pagy/extras/items.rb @@ -12,7 +12,7 @@ class Pagy ITEMS_PLACEHOLDER = '__pagy_items__' - module Items ; private + module UseItemsExtra ; private [:pagy_get_vars, :pagy_countless_get_vars, :pagy_elasticsearch_rails_get_vars, :pagy_searchkick_get_vars].each do |meth| if Backend.private_method_defined?(meth, true) @@ -25,12 +25,12 @@ module Items ; private end end - Backend.prepend Items + Backend.prepend UseItemsExtra module Frontend - module Items + module UseItemsExtra def pagy_url_for(page, pagy, url=false) p_vars = pagy.vars params = request.GET.merge(p_vars[:params]) @@ -39,7 +39,7 @@ def pagy_url_for(page, pagy, url=false) "#{request.base_url if url}#{request.path}?#{Rack::Utils.build_nested_query(pagy_get_params(params))}#{p_vars[:anchor]}" end end - prepend Items + prepend UseItemsExtra # Return the items selector HTML. For example "Show [20] items per page" def pagy_items_selector_js(pagy, id=pagy_id) diff --git a/lib/pagy/extras/overflow.rb b/lib/pagy/extras/overflow.rb index 3afbcf798..5fd7e147c 100644 --- a/lib/pagy/extras/overflow.rb +++ b/lib/pagy/extras/overflow.rb @@ -4,7 +4,7 @@ class Pagy - module Overflow + module UseOverflowExtra VARS[:overflow] = :empty_page def overflow? = @overflow @@ -41,14 +41,14 @@ def series(size=@vars[:size]) end end - prepend Overflow + prepend UseOverflowExtra # support for Pagy::Countless if defined?(Pagy::Countless) class Countless - module Overflow + module UseOverflowExtra def finalize(items) @overflow = false @@ -68,7 +68,7 @@ def finalize(items) end end - prepend Overflow + prepend UseOverflowExtra end end diff --git a/lib/pagy/extras/searchkick.rb b/lib/pagy/extras/searchkick.rb index 11f04dbdf..77db7f6f2 100644 --- a/lib/pagy/extras/searchkick.rb +++ b/lib/pagy/extras/searchkick.rb @@ -39,7 +39,7 @@ def pagy_searchkick(pagy_search_args, vars={}) vars[:count] = results.total_count pagy = Pagy.new(vars) # with :last_page overflow we need to re-run the method in order to get the hits - if defined?(Pagy::Overflow) && pagy.overflow? && pagy.vars[:overflow] == :last_page + if defined?(Pagy::UseOverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page return pagy_searchkick(pagy_search_args, vars.merge(page: pagy.page)) end return pagy, called.empty? ? results : results.send(*called) diff --git a/lib/pagy/extras/shared.rb b/lib/pagy/extras/shared.rb index 98172b948..a2edb1e91 100644 --- a/lib/pagy/extras/shared.rb +++ b/lib/pagy/extras/shared.rb @@ -30,14 +30,14 @@ module Frontend if defined?(Oj) # it returns a script tag with the JSON-serialized args generated with the faster oj gem def pagy_json_tag(pagy, *args) - args << ( defined?(Trim) && pagy.vars[:page_param] ) + args << ( defined?(UseTrimExtra) && pagy.vars[:page_param] ) %() end else require 'json' # it returns a script tag with the JSON-serialized args generated with the slower to_json def pagy_json_tag(pagy, *args) - args << ( defined?(Trim) && pagy.vars[:page_param] ) + args << ( defined?(UseTrimExtra) && pagy.vars[:page_param] ) %() end end diff --git a/lib/pagy/extras/trim.rb b/lib/pagy/extras/trim.rb index be97050ff..b5e92473d 100644 --- a/lib/pagy/extras/trim.rb +++ b/lib/pagy/extras/trim.rb @@ -4,7 +4,7 @@ class Pagy - module Trim + module UseTrimExtra def pagy_link_proc(pagy, link_extra='') link_proc = super(pagy, link_extra) re = /[?&]#{pagy.vars[:page_param]}=1\b(?!&)|\b#{pagy.vars[:page_param]}=1&/ @@ -14,6 +14,6 @@ def pagy_link_proc(pagy, link_extra='') end end end - Frontend.prepend Trim + Frontend.prepend UseTrimExtra end From b6387648450c3d3b018691b944c48d33ae58a30e Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Thu, 8 Apr 2021 20:09:39 +0700 Subject: [PATCH 08/17] code-restyling: root files and Pagy Core --- .rubocop.yml | 26 ++++++++++++---- Gemfile | 12 +++----- Rakefile | 1 - lib/pagy.rb | 70 ++++++++++++++++++++++++++---------------- lib/pagy/backend.rb | 7 +++-- lib/pagy/countless.rb | 18 ++++++----- lib/pagy/exceptions.rb | 8 +++-- lib/pagy/frontend.rb | 66 ++++++++++++++++++++++++++------------- 8 files changed, 133 insertions(+), 75 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 1d033bad8..b66f74d19 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,24 +3,38 @@ require: - rubocop-rake - rubocop-minitest +# trying to be good O:) AllCops: TargetRubyVersion: 3.0 NewCops: enable +# No, thank you! Layout: Enabled: false +# affect performance Metrics: Enabled: false -Style: +# not cool +Style/CommentedKeyword: Enabled: false - -Bundler/OrderedGems: +Style/EmptyCaseCondition: + Enabled: false +Style/Documentation: Enabled: false -Lint/RaiseException: +# these cops are disabled only in the test files in order to +# allow to copy and paste the failed output for test reconciliation +Style/StringLiterals: Enabled: true - -Lint/StructNewOverride: + Exclude: + - test/**/* +Style/HashSyntax: + Enabled: true + Exclude: + - test/**/* +Style/SymbolArray: Enabled: true + Exclude: + - test/**/* diff --git a/Gemfile b/Gemfile index eec1827e3..4288f25b6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,23 +2,22 @@ source "https://rubygems.org" gemspec -gem 'rake' -gem 'rack' gem 'i18n' - gem 'oj', require: false # false is for testing with or without it +gem 'rack' +gem 'rake' gem 'puma' group :test do + gem 'codecov', require: false + gem 'minitest' + gem 'minitest-reporters' gem 'rubocop', '~> 1.11', require: false gem 'rubocop-performance', require: false gem 'rubocop-rake', require: false gem 'rubocop-minitest', require: false gem 'simplecov', require: false - gem 'codecov', require: false - gem 'minitest' - gem 'minitest-reporters' end group :apps do @@ -29,7 +28,6 @@ group :apps do end group :performance do - #benchmark/profiling gem 'benchmark-ips' gem 'kalibera' gem 'memory_profiler' diff --git a/Rakefile b/Rakefile index 8a3348e11..0bad66eb2 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require "bundler/setup" diff --git a/lib/pagy.rb b/lib/pagy.rb index 95ed36226..6084658a9 100644 --- a/lib/pagy.rb +++ b/lib/pagy.rb @@ -1,48 +1,66 @@ # See Pagy API documentation: https://ddnexus.github.io/pagy/api/pagy -# encoding: utf-8 # frozen_string_literal: true require 'pathname' -class Pagy ; VERSION = '4.1.0' +# main class +class Pagy + VERSION = '4.1.0' # Root pathname to get the path of Pagy files like templates or dictionaries - def self.root = @root ||= Pathname.new(__FILE__).dirname.freeze + def self.root + @root ||= Pathname.new(__dir__).freeze + end # default vars - VARS = { page:1, items:20, outset:0, size:[1,4,4,1], page_param: :page, params:{}, anchor:'', link_extra:'', i18n_key:'pagy.item_name', cycle:false } + VARS = { page: 1, items: 20, outset: 0, size: [1, 4, 4, 1], page_param: :page, # rubocop:disable Style/MutableConstant + params: {}, anchor: '', link_extra: '', i18n_key: 'pagy.item_name', cycle: false } attr_reader :count, :page, :items, :vars, :pages, :last, :offset, :from, :to, :prev, :next + INSTANCE_VARS_MIN = { count: 0, items: 1, page: 1, outset: 0 }.freeze + # Merge and validate the options, do some simple arithmetic and set the instance variables def initialize(vars) - @vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars - { count:0, items:1, outset:0, page:1 }.each do |k,min| # validate instance variables - (@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \ - or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}") + @vars = VARS.merge( vars.delete_if{|_,v| v.nil? || v == '' } ) + + INSTANCE_VARS_MIN.each do |name,min| + raise VariableError.new(self), "expected :#{name} >= #{min}; got #{@vars[name].inspect}" \ + unless @vars[name] && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min end - @pages = @last = [(@count.to_f / @items).ceil, 1].max # cardinal and ordinal meanings - @page <= @last or raise(OverflowError.new(self), "expected :page in 1..#{@last}; got #{@page.inspect}") - @offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset) - @items = @count - ((@pages-1) * @items) if @page == @last && @count > 0 # adjust items for last non-empty page - @from = @count == 0 ? 0 : @offset + 1 - @outset # page begins from item - @to = @count == 0 ? 0 : @offset + @items - @outset # page ends to item - @prev = (@page-1 unless @page == 1) # nil if no prev page - @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1 # nil if no next page, 1 if :cycle + @pages = @last = [(@count.to_f / @items).ceil, 1].max + raise OverflowError.new(self), "expected :page in 1..#{@last}; got #{@page.inspect}" if @page > @last + + @offset = @items * (@page - 1) + @outset + @items = @count - ((@pages-1) * @items) if @page == @last && @count.positive? + @from = @count.zero? ? 0 : @offset + 1 - @outset + @to = @count.zero? ? 0 : @offset + @items - @outset + @prev = (@page-1 unless @page == 1) + @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1 end # Return the array of page numbers and :gap items e.g. [1, :gap, 7, 8, "9", 10, 11, :gap, 36] def series(size=@vars[:size]) - (series = []) and size.empty? and return series - 4.times{|i| (size[i]>=0 rescue nil) or raise(VariableError.new(self), "expected 4 items >= 0 in :size; got #{size.inspect}")} - [*0..size[0], *@page-size[1]..@page+size[2], *@last-size[3]+1..@last+1].sort!.each_cons(2) do |a, b| - if a<0 || a==b || a>@last; next # skip out of range and duplicates - elsif a+1 == b; series.push(a) # no gap -> no additions - elsif a+2 == b; series.push(a, a+1) # 1 page gap -> fill with missing page - else series.push(a, :gap) # n page gap -> add gap - end # skip the end boundary (last+1) - end # shift the start boundary (0) and - series.shift; series[series.index(@page)] = @page.to_s; series # convert the current page to String + return [] if size.empty? + raise VariableError.new(self), "expected 4 items >= 0 in :size; got #{size.inspect}" \ + unless size.size == 4 && size.all?{ |num| num >= 0 rescue false } # rubocop:disable Style/RescueModifier + + [].tap do |series| + [ *0..size[0], # initial pages from 0 + *@page-size[1]..@page+size[2], # around current page + *@last-size[3]+1..@last+1 # final pages till @last+1 + ].sort!.each_cons(2) do |left, right| # sort and loop by 2 + next if left.negative? || left == right # skip out of range and duplicates + break if left > @last # break if out of @last boundary + case right + when left+1 then series.push(left) # no gap -> no additions + when left+2 then series.push(left, left+1) # 1 page gap -> fill with missing page + else series.push(left, :gap) # n page gap -> add gap + end + end + series.shift # shift the start boundary (0) + series[series.index(@page)] = @page.to_s # convert the current page to String + end end end diff --git a/lib/pagy/backend.rb b/lib/pagy/backend.rb index 925e67a31..5c094071b 100644 --- a/lib/pagy/backend.rb +++ b/lib/pagy/backend.rb @@ -1,5 +1,4 @@ # See Pagy::Backend API documentation: https://ddnexus.github.io/pagy/api/backend -# encoding: utf-8 # frozen_string_literal: true class Pagy @@ -8,12 +7,14 @@ class Pagy # See also the extras if you need specialized methods to paginate Arrays or other collections - module Backend ; private # the whole module is private so no problem with including it in a controller + + module Backend + private # the whole module is private so no problem with including it in a controller # Return Pagy object and items def pagy(collection, vars={}) pagy = Pagy.new(pagy_get_vars(collection, vars)) - return pagy, pagy_get_items(collection, pagy) + [ pagy, pagy_get_items(collection, pagy) ] end # Sub-method called only by #pagy: here for easy customization of variables by overriding diff --git a/lib/pagy/countless.rb b/lib/pagy/countless.rb index 69d9f02f8..fa7277bfc 100644 --- a/lib/pagy/countless.rb +++ b/lib/pagy/countless.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require 'pagy' @@ -7,23 +6,26 @@ class Pagy class Countless < Pagy + INSTANCE_VARS_MIN = { items: 1, page: 1, outset: 0 }.freeze + # Merge and validate the options, do some simple arithmetic and set a few instance variables def initialize(vars={}) # rubocop:disable Lint/MissingSuper @vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars (can be overridden) - { items:1, outset:0, page:1 }.each do |k,min| # validate instance variables - (@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \ - or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}") + INSTANCE_VARS_MIN.each do |k,min| # validate instance variables + raise VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}" \ + unless @vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min end @offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset) end # Finalize the instance variables based on the fetched items def finalize(fetched) - fetched == 0 && @page > 1 and raise(OverflowError.new(self), "page #{@page} got no items") + raise OverflowError.new(self), "page #{@page} got no items" \ + if fetched.zero? && @page > 1 @pages = @last = (fetched > @items ? @page + 1 : @page) # set the @pages and @last - @items = fetched if fetched < @items && fetched > 0 # adjust items for last non-empty page - @from = fetched == 0 ? 0 : @offset + 1 - @outset # page begins from item - @to = fetched == 0 ? 0 : @offset + @items - @outset # page ends to item + @items = fetched if fetched < @items && fetched.positive? # adjust items for last non-empty page + @from = fetched.zero? ? 0 : @offset + 1 - @outset # page begins from item + @to = fetched.zero? ? 0 : @offset + @items - @outset # page ends to item @prev = (@page-1 unless @page == 1) # nil if no prev page @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1 # nil if no next page, 1 if :cycle self diff --git a/lib/pagy/exceptions.rb b/lib/pagy/exceptions.rb index ebfb88c38..8c8fa417c 100644 --- a/lib/pagy/exceptions.rb +++ b/lib/pagy/exceptions.rb @@ -1,5 +1,8 @@ +# frozen_string_literal: true + class Pagy + # generic variable error class VariableError < ArgumentError attr_reader :pagy @@ -9,13 +12,14 @@ def initialize(pagy) end def variable - message =~ /expected :([\w]+)/ - $1.to_sym if $1 + message =~ /expected :(\w+)/ + Regexp.last_match(1)&.to_sym end def value = pagy.vars[variable] end + # specific overflow error class OverflowError < VariableError; end end diff --git a/lib/pagy/frontend.rb b/lib/pagy/frontend.rb index f61554784..3e1c418ce 100644 --- a/lib/pagy/frontend.rb +++ b/lib/pagy/frontend.rb @@ -1,5 +1,4 @@ # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/api/frontend -# encoding: utf-8 # frozen_string_literal: true require 'yaml' @@ -10,11 +9,11 @@ class Pagy # I18n static hash loaded at startup, used as default alternative to the i18n gem. # see https://ddnexus.github.io/pagy/api/frontend#i18n - I18n = eval(Pagy.root.join('locales', 'utils', 'i18n.rb').read) #rubocop:disable Security/Eval + I18n = eval Pagy.root.join('locales', 'utils', 'i18n.rb').read #rubocop:disable Security/Eval module Helpers # This works with all Rack-based frameworks (Sinatra, Padrino, Rails, ...) - def pagy_url_for(page, pagy, url=false) + def pagy_url_for(page, pagy, url=nil) p_vars = pagy.vars params = request.GET.merge(p_vars[:params]) params[p_vars[:page_param].to_s] = page @@ -33,42 +32,65 @@ module Frontend # Generic pagination: it returns the html with the series of links to the pages def pagy_nav(pagy) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next - - html = (p_prev ? %(#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'} ) - : %(#{pagy_t('pagy.nav.prev')} )) + link = pagy_link_proc(pagy) + p_prev = pagy.prev + p_next = pagy.next + html = if p_prev + %(#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'} ) + else + %(#{pagy_t('pagy.nav.prev')} ) + end pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] - html << if item.is_a?(Integer); %(#{link.call item} ) # page link - elsif item.is_a?(String) ; %(#{item} ) # current page - elsif item == :gap ; %(#{pagy_t('pagy.nav.gap')} ) # page gap + html << case item + when Integer then %(#{link.call item} ) # page link + when String then %(#{item} ) # current page + when :gap then %(#{pagy_t('pagy.nav.gap')} ) # page gap end end - html << (p_next ? %(#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}) - : %(#{pagy_t('pagy.nav.next')})) + html << if p_next + %(#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}) + else + %(#{pagy_t('pagy.nav.next')}) + end %() end # Return examples: "Displaying items 41-60 of 324 in total" of "Displaying Products 41-60 of 324 in total" def pagy_info(pagy, item_name=nil) - key = if (count = pagy.count) == 0 ; 'pagy.info.no_items' - else pagy.pages == 1 ? 'pagy.info.single_page' : 'pagy.info.multiple_pages' - end - pagy_t(key, item_name: item_name || pagy_t(pagy.vars[:i18n_key], count: count), count: count, from: pagy.from, to: pagy.to) + count = pagy.count + key = if count.zero? + 'pagy.info.no_items' + elsif pagy.pages == 1 + 'pagy.info.single_page' + else + 'pagy.info.multiple_pages' + end + pagy_t key, item_name: item_name || pagy_t(pagy.vars[:i18n_key], count: count), + count: count, + from: pagy.from, + to: pagy.to end # Returns a performance optimized proc to generate the HTML links # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to def pagy_link_proc(pagy, link_extra='') - p_prev, p_next = pagy.prev, pagy.next - a, b = %("} + p_prev = pagy.prev + p_next = pagy.next + left, right = %(" + end end # Similar to I18n.t: just ~18x faster using ~10x less memory # (@pagy_locale explicitly initilized in order to avoid warning) - def pagy_t(key, **opts) = Pagy::I18n.t(@pagy_locale||=nil, key, **opts) + def pagy_t(key, **opts) + Pagy::I18n.t @pagy_locale||=nil, key, **opts + end end end From b2dc35aeaba217f5b65e24900c4f9a5f60472105 Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Thu, 8 Apr 2021 20:11:46 +0700 Subject: [PATCH 09/17] code-restyling: locales, config --- lib/config/pagy.rb | 1 - lib/locales/utils/i18n.rb | 1 - lib/locales/utils/loader.rb | 12 +++++----- lib/locales/utils/p11n.rb | 44 ++++++++++++++++++++----------------- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/config/pagy.rb b/lib/config/pagy.rb index f6865b37a..c76139539 100644 --- a/lib/config/pagy.rb +++ b/lib/config/pagy.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true # Pagy initializer file (4.1.0) diff --git a/lib/locales/utils/i18n.rb b/lib/locales/utils/i18n.rb index 4dcc5c91d..ce93da5e0 100644 --- a/lib/locales/utils/i18n.rb +++ b/lib/locales/utils/i18n.rb @@ -1,5 +1,4 @@ # See https://ddnexus.github.io/pagy/api/frontend#i18n -# encoding: utf-8 # frozen_string_literal: true # this file returns the I18n hash used as default alternative to the i18n gem diff --git a/lib/locales/utils/loader.rb b/lib/locales/utils/loader.rb index 942dfb827..c6812875f 100644 --- a/lib/locales/utils/loader.rb +++ b/lib/locales/utils/loader.rb @@ -1,18 +1,20 @@ -# encoding: utf-8 # frozen_string_literal: true # the whole file will be eval'ed/executed and gc-collected after returning/executing the loader proc # eval: no need for the whole file in memory -plurals, _ = eval(Pagy.root.join('locales', 'utils', 'p11n.rb').read) #rubocop:disable Security/Eval +plurals, = eval Pagy.root.join('locales', 'utils', 'p11n.rb').read # rubocop:disable Security/Eval # flatten the dictionary file nested keys # convert each value to a simple ruby interpolation proc flatten = lambda do |hash, key=''| hash.each.reduce({}) do |h, (k, v)| - v.is_a?(Hash) \ - ? h.merge!(flatten.call(v, "#{key}#{k}.")) - : h.merge!(eval %({"#{key}#{k}" => lambda{|vars|"#{v.gsub(/%{[^}]+?}/){|m| "\#{vars[:#{m[2..-2]}]||'#{m}'}" }}"}})) #rubocop:disable Security/Eval + if v.is_a?(Hash) + h.merge! flatten.call(v, "#{key}#{k}.") + else + code = %({"#{key}#{k}" => lambda{|vars|"#{v.gsub(/%{[^}]+?}/){|m| "\#{vars[:#{m[2..-2]}]||'#{m}'}" }}"}}) + h.merge! eval(code) # rubocop:disable Security/Eval + end end end diff --git a/lib/locales/utils/p11n.rb b/lib/locales/utils/p11n.rb index bad7a2368..9685af75c 100644 --- a/lib/locales/utils/p11n.rb +++ b/lib/locales/utils/p11n.rb @@ -1,5 +1,4 @@ # See https://ddnexus.github.io/pagy/api/frontend#i18n -# encoding: utf-8 # frozen_string_literal: true # This file adds support for multiple built-in plualization types. @@ -18,46 +17,51 @@ # Each proc may apply to one or more locales below. # Pluralization logic adapted from https://github.com/svenfuchs/rails-i18n p11n = { - one_other: lambda {|n| n == 1 ? 'one' : 'other'}, # default + one_other: -> (n){ n == 1 ? 'one' : 'other' }, # default east_slavic: lambda do |n| n ||= 0 mod10 = n % 10 mod100 = n % 100 - if mod10 == 1 && mod100 != 11 ; 'one' - elsif from2to4.include?(mod10) && !from12to14.include?(mod100) ; 'few' - elsif mod10 == 0 || from5to9.include?(mod10) || from11to14.include?(mod100) ; 'many' - else 'other' + case + when mod10 == 1 && mod100 != 11 then 'one' + when from2to4.include?(mod10) && !from12to14.include?(mod100) then 'few' + when mod10 == 0 || from5to9.include?(mod10) || from11to14.include?(mod100) then 'many' # rubocop:disable Style/NumericPredicate + else 'other' end end, west_slavic: lambda do |n| - if n == 1 ; 'one' - elsif [2, 3, 4].include?(n) ; 'few' - else ; 'other' - end + case n + when 1 then 'one' + when 2, 3, 4 then 'few' + else 'other' + end end, one_two_other: lambda do |n| - if n == 1 ; 'one' - elsif n == 2 ; 'two' - else 'other' + case n + when 1 then 'one' + when 2 then 'two' + else 'other' end end, - one_upto_two_other: lambda {|n| n && n >= 0 && n < 2 ? 'one' : 'other'}, + one_upto_two_other: -> (n){ n && n >= 0 && n < 2 ? 'one' : 'other' }, - other: Proc.new { 'other' }, + other: -> (*){ 'other' }, polish: lambda do |n| n ||= 0 - mod10 = n % 10 + mod10 = n % 10 mod100 = n % 100 - if n == 1 ; 'one' - elsif from2to4.include?(mod10) && !from12to14.include?(mod100) ; 'few' - elsif (from0to1 + from5to9).include?(mod10) || from12to14.include?(mod100) ; 'many' - else 'other' + + case + when n == 1 then 'one' + when from2to4.include?(mod10) && !from12to14.include?(mod100) then 'few' + when (from0to1 + from5to9).include?(mod10) || from12to14.include?(mod100) then 'many' + else 'other' end end } From cb673530eca0916771d9f4eebe56899f9eac9ad8 Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Thu, 8 Apr 2021 20:13:10 +0700 Subject: [PATCH 10/17] code-restyling: extras --- lib/pagy.rb | 28 ++++----- lib/pagy/countless.rb | 1 + lib/pagy/extras/arel.rb | 6 +- lib/pagy/extras/array.rb | 6 +- lib/pagy/extras/bootstrap.rb | 81 ++++++++++++++++-------- lib/pagy/extras/bulma.rb | 86 +++++++++++++++---------- lib/pagy/extras/countless.rb | 9 +-- lib/pagy/extras/elasticsearch_rails.rb | 13 ++-- lib/pagy/extras/foundation.rb | 81 +++++++++++++++--------- lib/pagy/extras/headers.rb | 22 ++++--- lib/pagy/extras/i18n.rb | 7 ++- lib/pagy/extras/items.rb | 29 +++++---- lib/pagy/extras/materialize.rb | 80 ++++++++++++++--------- lib/pagy/extras/metadata.rb | 45 +++++++------ lib/pagy/extras/navs.rb | 55 +++++++++++----- lib/pagy/extras/overflow.rb | 13 ++-- lib/pagy/extras/searchkick.rb | 15 ++--- lib/pagy/extras/semantic.rb | 75 ++++++++++++++-------- lib/pagy/extras/shared.rb | 18 +++--- lib/pagy/extras/support.rb | 15 +++-- lib/pagy/extras/trim.rb | 14 +++-- lib/pagy/extras/uikit.rb | 87 +++++++++++++++++--------- lib/pagy/frontend.rb | 23 ++++--- test/pagy/extras/semantic_test.rb | 1 + 24 files changed, 499 insertions(+), 311 deletions(-) diff --git a/lib/pagy.rb b/lib/pagy.rb index 6084658a9..5c7d79b0d 100644 --- a/lib/pagy.rb +++ b/lib/pagy.rb @@ -45,22 +45,22 @@ def series(size=@vars[:size]) raise VariableError.new(self), "expected 4 items >= 0 in :size; got #{size.inspect}" \ unless size.size == 4 && size.all?{ |num| num >= 0 rescue false } # rubocop:disable Style/RescueModifier - [].tap do |series| - [ *0..size[0], # initial pages from 0 - *@page-size[1]..@page+size[2], # around current page - *@last-size[3]+1..@last+1 # final pages till @last+1 - ].sort!.each_cons(2) do |left, right| # sort and loop by 2 - next if left.negative? || left == right # skip out of range and duplicates - break if left > @last # break if out of @last boundary - case right - when left+1 then series.push(left) # no gap -> no additions - when left+2 then series.push(left, left+1) # 1 page gap -> fill with missing page - else series.push(left, :gap) # n page gap -> add gap - end + series = [] + [ *0..size[0], # initial pages from 0 + *@page-size[1]..@page+size[2], # around current page + *@last-size[3]+1..@last+1 # final pages till @last+1 + ].sort!.each_cons(2) do |left, right| # sort and loop by 2 + next if left.negative? || left == right # skip out of range and duplicates + break if left > @last # break if out of @last boundary + case right + when left+1 then series.push(left) # no gap -> no additions + when left+2 then series.push(left, left+1) # 1 page gap -> fill with missing page + else series.push(left, :gap) # n page gap -> add gap end - series.shift # shift the start boundary (0) - series[series.index(@page)] = @page.to_s # convert the current page to String end + series.shift # shift the start boundary (0) + series[series.index(@page)] = @page.to_s # convert the current page to String + series end end diff --git a/lib/pagy/countless.rb b/lib/pagy/countless.rb index fa7277bfc..3ea12c46d 100644 --- a/lib/pagy/countless.rb +++ b/lib/pagy/countless.rb @@ -22,6 +22,7 @@ def initialize(vars={}) # rubocop:disable Lint/MissingSuper def finalize(fetched) raise OverflowError.new(self), "page #{@page} got no items" \ if fetched.zero? && @page > 1 + @pages = @last = (fetched > @items ? @page + 1 : @page) # set the @pages and @last @items = fetched if fetched < @items && fetched.positive? # adjust items for last non-empty page @from = fetched.zero? ? 0 : @offset + 1 - @outset # page begins from item diff --git a/lib/pagy/extras/arel.rb b/lib/pagy/extras/arel.rb index 3e6dfaef9..b92fd2233 100644 --- a/lib/pagy/extras/arel.rb +++ b/lib/pagy/extras/arel.rb @@ -1,13 +1,13 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/arel -# encoding: utf-8 # frozen_string_literal: true class Pagy - module Backend ; private + module Backend + private def pagy_arel(collection, vars={}) pagy = Pagy.new(pagy_arel_get_vars(collection, vars)) - return pagy, pagy_get_items(collection, pagy) + [ pagy, pagy_get_items(collection, pagy) ] end def pagy_arel_get_vars(collection, vars) diff --git a/lib/pagy/extras/array.rb b/lib/pagy/extras/array.rb index 35870cb93..3dc08621d 100644 --- a/lib/pagy/extras/array.rb +++ b/lib/pagy/extras/array.rb @@ -1,15 +1,15 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/array -# encoding: utf-8 # frozen_string_literal: true class Pagy # Add specialized backend methods to paginate array collections - module Backend ; private + module Backend + private # Return Pagy object and items def pagy_array(array, vars={}) pagy = Pagy.new(pagy_array_get_vars(array, vars)) - return pagy, array[pagy.offset, pagy.items] + [ pagy, array[pagy.offset, pagy.items] ] end # Sub-method called only by #pagy_array: here for easy customization of variables by overriding diff --git a/lib/pagy/extras/bootstrap.rb b/lib/pagy/extras/bootstrap.rb index d5c3b8da8..89a008414 100644 --- a/lib/pagy/extras/bootstrap.rb +++ b/lib/pagy/extras/bootstrap.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/bootstrap -# encoding: utf-8 # frozen_string_literal: true require 'pagy/extras/shared' @@ -9,47 +8,75 @@ module Frontend # Pagination for bootstrap: it returns the html with the series of links to the pages def pagy_bootstrap_nav(pagy) - link, p_prev, p_next = pagy_link_proc(pagy, 'class="page-link"'), pagy.prev, pagy.next + link = pagy_link_proc(pagy, 'class="page-link"') - html = (p_prev ? %() - : %()) + html = +%() end # Javascript pagination for bootstrap: it returns a nav and a JSON tag used by the Pagy.nav javascript def pagy_bootstrap_nav_js(pagy, id=pagy_id) - link, p_prev, p_next = pagy_link_proc(pagy, 'class="page-link"'), pagy.prev, pagy.next - tags = { 'before' => p_prev ? %(
    ) - : %(
      ), + link = pagy_link_proc(pagy, 'class="page-link"') + tags = { 'before' => %(
        #{pagy_bootstrap_prev_html pagy, link}), 'link' => %(
      • #{mark = link.call(PAGE_PLACEHOLDER)}
      • ), 'active' => %(
      • #{mark}
      • ), - 'gap' => %(
      • #{pagy_t('pagy.nav.gap')}
      • ), - 'after' => p_next ? %(
      ) - : %(
    ) } - %(#{pagy_json_tag(pagy, :nav, id, tags, pagy.sequels)}) + 'gap' => %(
  • #{pagy_t 'pagy.nav.gap'}
  • ), + 'after' => %(#{pagy_bootstrap_next_html pagy, link}
) } + + html = %() + html << pagy_json_tag(pagy, :nav, id, tags, pagy.sequels) end # Javascript combo pagination for bootstrap: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript def pagy_bootstrap_combo_nav_js(pagy, id=pagy_id) - link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages - - html = %(#{pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link))}) + link = pagy_link_proc(pagy) + p_page = pagy.page + p_pages = pagy.pages + input = %() + + %(#{ + pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link)) + }) end + private + + def pagy_bootstrap_prev_html(pagy, link) + if (p_prev = pagy.prev) + %() + else + %() + end + end + + def pagy_bootstrap_next_html(pagy, link) + if (p_next = pagy.next) + %() + else + %() + end + end + end end diff --git a/lib/pagy/extras/bulma.rb b/lib/pagy/extras/bulma.rb index ccbd793a4..51f03da3b 100644 --- a/lib/pagy/extras/bulma.rb +++ b/lib/pagy/extras/bulma.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/bulma -# encoding: utf-8 # frozen_string_literal: true require 'pagy/extras/shared' @@ -9,52 +8,71 @@ module Frontend # Pagination for Bulma: it returns the html with the series of links to the pages def pagy_bulma_nav(pagy) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next + link = pagy_link_proc(pagy) - html = (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"') - : %(#{pagy_t('pagy.nav.prev')})) \ - + (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"') - : %(#{pagy_t('pagy.nav.next')})) - html << '
    ' + html = +%(
) end - # Javascript pagination for bulma: it returns a nav and a JSON tag used by the Pagy.nav javascript def pagy_bulma_nav_js(pagy, id=pagy_id) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next - tags = { 'before' => ( (p_prev ? link.call(p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"') - : %(#{pagy_t('pagy.nav.prev')})) \ - + (p_next ? link.call(p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"') - : %(#{pagy_t('pagy.nav.next')})) \ - + '
    ' ), - 'link' => %(
  • #{link.call(PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link" aria-label="goto page #{PAGE_PLACEHOLDER}"))}
  • ), - 'active' => %(
  • #{link.call(PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link is-current" aria-current="page" aria-label="page #{PAGE_PLACEHOLDER}"))}
  • ), - 'gap' => %(
  • #{pagy_t('pagy.nav.gap')}
  • ), + link = pagy_link_proc(pagy) + tags = { 'before' => %(#{pagy_bulma_prev_next_html(pagy, link)}
      ), + 'link' => %(
    • #{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link" aria-label="goto page #{PAGE_PLACEHOLDER}")}
    • ), + 'active' => %(
    • #{link.call PAGE_PLACEHOLDER, PAGE_PLACEHOLDER, %(class="pagination-link is-current" aria-current="page" aria-label="page #{PAGE_PLACEHOLDER}")}
    • ), + 'gap' => %(
    • #{pagy_t 'pagy.nav.gap' }
    • ), 'after' => '
    ' } - %(#{pagy_json_tag(pagy, :nav, id, tags, pagy.sequels)}) + + html = %() + html << pagy_json_tag(pagy, :nav, id, tags, pagy.sequels) end # Javascript combo pagination for Bulma: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript def pagy_bulma_combo_nav_js(pagy, id=pagy_id) - link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages - - html = %(#{pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link))}) + link = pagy_link_proc(pagy) + p_page = pagy.page + p_pages = pagy.pages + input = %() + + %(#{ + pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link)) + }) end + private + + def pagy_bulma_prev_next_html(pagy, link) + html = if (p_prev = pagy.prev) + link.call p_prev, pagy_t('pagy.nav.prev'), 'class="pagination-previous" aria-label="previous page"' + else + %(#{pagy_t 'pagy.nav.prev'}) + end + html << if (p_next = pagy.next) + link.call p_next, pagy_t('pagy.nav.next'), 'class="pagination-next" aria-label="next page"' + else + %(#{pagy_t 'pagy.nav.next' }) + end + end + end end diff --git a/lib/pagy/extras/countless.rb b/lib/pagy/extras/countless.rb index 2b50ec9d4..d7bbdd33b 100644 --- a/lib/pagy/extras/countless.rb +++ b/lib/pagy/extras/countless.rb @@ -1,17 +1,17 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/countless -# encoding: utf-8 # frozen_string_literal: true require 'pagy/countless' class Pagy - module Backend ; private # the whole module is private so no problem with including it in a controller + module Backend + private # the whole module is private so no problem with including it in a controller # Return Pagy object and items def pagy_countless(collection, vars={}) pagy = Pagy::Countless.new(pagy_countless_get_vars(collection, vars)) - return pagy, pagy_countless_get_items(collection, pagy) + [ pagy, pagy_countless_get_items(collection, pagy) ] end # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding @@ -26,7 +26,8 @@ def pagy_countless_get_items(collection, pagy) items = collection.offset(pagy.offset).limit(pagy.items + 1).to_a items_size = items.size items.pop if items_size == pagy.items + 1 - pagy.finalize(items_size) # finalize may adjust pagy.items, so must be used after checking the size + # finalize may adjust pagy.items, so must be used after checking the size + pagy.finalize(items_size) items end diff --git a/lib/pagy/extras/elasticsearch_rails.rb b/lib/pagy/extras/elasticsearch_rails.rb index a16b03ffc..f3121df59 100644 --- a/lib/pagy/extras/elasticsearch_rails.rb +++ b/lib/pagy/extras/elasticsearch_rails.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/elasticsearch_rails -# encoding: utf-8 # frozen_string_literal: true class Pagy @@ -28,7 +27,8 @@ def self.new_from_elasticsearch_rails(response, vars={}) end # Add specialized backend methods to paginate ElasticsearchRails searches - module Backend ; private + module Backend + private # Return Pagy object and items def pagy_elasticsearch_rails(pagy_search_args, vars={}) @@ -39,12 +39,13 @@ def pagy_elasticsearch_rails(pagy_search_args, vars={}) response = model.search(query_or_payload, **options) total = response.respond_to?(:raw_response) ? response.raw_response['hits']['total'] : response.response['hits']['total'] vars[:count] = total.is_a?(Hash) ? total['value'] : total + pagy = Pagy.new(vars) # with :last_page overflow we need to re-run the method in order to get the hits - if defined?(Pagy::UseOverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page - return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) - end - return pagy, called.empty? ? response : response.send(*called) + return pagy_elasticsearch_rails(pagy_search_args, vars.merge(page: pagy.page)) \ + if defined?(Pagy::UseOverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page + + [ pagy, called.empty? ? response : response.send(*called) ] end # Sub-method called only by #pagy_elasticsearch_rails: here for easy customization of variables by overriding diff --git a/lib/pagy/extras/foundation.rb b/lib/pagy/extras/foundation.rb index dcefc3d09..e2b22c2dc 100644 --- a/lib/pagy/extras/foundation.rb +++ b/lib/pagy/extras/foundation.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/foundation -# encoding: utf-8 # frozen_string_literal: true require 'pagy/extras/shared' @@ -9,49 +8,73 @@ module Frontend # Pagination for Foundation: it returns the html with the series of links to the pages def pagy_foundation_nav(pagy) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next + link = pagy_link_proc(pagy) - html = +(p_prev ? %() - : %()) + html = +%() end # Javascript pagination for foundation: it returns a nav and a JSON tag used by the Pagy.nav javascript def pagy_foundation_nav_js(pagy, id=pagy_id) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next - tags = { 'before' => ( '
      ' \ - + (p_prev ? %() - : %()) ), - 'link' => %(
    • #{link.call(PAGE_PLACEHOLDER)}
    • ), + link = pagy_link_proc(pagy) + tags = { 'before' => %(
        #{pagy_foundation_prev_html(pagy, link)}), + 'link' => %(
      • #{link.call PAGE_PLACEHOLDER}
      • ), 'active' => %(
      • #{pagy.page}
      • ), 'gap' => %(), - 'after' => ( (p_next ? %() - : %()) \ - + '
      ' ) } - %(#{pagy_json_tag(pagy, :nav, id, tags, pagy.sequels)}) + 'after' => %(#{pagy_foundation_next_html(pagy, link)}
    ) } + + html = %() + html << pagy_json_tag(pagy, :nav, id, tags, pagy.sequels) end # Javascript combo pagination for Foundation: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript def pagy_foundation_combo_nav_js(pagy, id=pagy_id) - link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages - - html = %(#{pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link))}) + link = pagy_link_proc(pagy) + p_page = pagy.page + p_pages = pagy.pages + input = %() + + %(#{pagy_json_tag(pagy, :combo_nav, id, p_page, pagy_marked_link(link))}) end + private + + def pagy_foundation_prev_html(pagy, link) + if (p_prev = pagy.prev) + %() + else + %() + end + end + + def pagy_foundation_next_html(pagy, link) + if (p_next = pagy.next) + %() + else + %() + end + end + end end diff --git a/lib/pagy/extras/headers.rb b/lib/pagy/extras/headers.rb index 591325cbb..892a2aee1 100644 --- a/lib/pagy/extras/headers.rb +++ b/lib/pagy/extras/headers.rb @@ -1,10 +1,10 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/headers -# encoding: utf-8 # frozen_string_literal: true class Pagy # Add specialized backend methods to add pagination response headers - module Backend ; private + module Backend + private VARS[:headers] = { page: 'Current-Page', items: 'Page-Items', count: 'Total-Count', pages: 'Total-Pages' } @@ -15,17 +15,21 @@ def pagy_headers_merge(pagy) end def pagy_headers(pagy) - hash = pagy_headers_hash(pagy) - hash['Link'] = hash['Link'].map{|rel, link| %(<#{link}>; rel="#{rel}")}.join(', ') - hash + pagy_headers_hash(pagy).tap do |hash| + hash['Link'] = hash['Link'].map{|rel, link| %(<#{link}>; rel="#{rel}")}.join(', ') + end end def pagy_headers_hash(pagy) countless = defined?(Pagy::Countless) && pagy.is_a?(Pagy::Countless) - rels = { 'first' => 1, 'prev' => pagy.prev, 'next' => pagy.next }; rels['last'] = pagy.last unless countless - url_str = pagy_url_for(PAGE_PLACEHOLDER, pagy, :url) - hash = { 'Link' => Hash[rels.map{|rel, n|[rel, url_str.sub(PAGE_PLACEHOLDER, n.to_s)] if n}.compact] } - headers = pagy.vars[:headers] + rels = { 'first' => 1, 'prev' => pagy.prev, 'next' => pagy.next } + rels['last'] = pagy.last unless countless + url_str = pagy_url_for(PAGE_PLACEHOLDER, pagy, :url) + hash = { 'Link' => rels.map do |rel, num| + next unless num + [ rel, url_str.sub(PAGE_PLACEHOLDER, num.to_s) ] + end.compact.to_h } + headers = pagy.vars[:headers] hash[headers[:page]] = pagy.page.to_s if headers[:page] hash[headers[:items]] = pagy.vars[:items].to_s if headers[:items] unless countless diff --git a/lib/pagy/extras/i18n.rb b/lib/pagy/extras/i18n.rb index d125855a7..2766b2711 100644 --- a/lib/pagy/extras/i18n.rb +++ b/lib/pagy/extras/i18n.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/i18n -# encoding: utf-8 # frozen_string_literal: true class Pagy @@ -8,7 +7,11 @@ module Frontend ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')] - Pagy::I18n.clear.instance_eval { undef :load; undef :t } # unload the pagy default constant for efficiency + # unload the pagy default constant for efficiency + Pagy::I18n.clear.instance_eval do + undef :load + undef :t + end module UseI18nGem def pagy_t(key, **opts) = ::I18n.t(key, **opts) diff --git a/lib/pagy/extras/items.rb b/lib/pagy/extras/items.rb index 75181696e..99188d063 100644 --- a/lib/pagy/extras/items.rb +++ b/lib/pagy/extras/items.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/items -# encoding: utf-8 # frozen_string_literal: true require 'pagy/extras/shared' @@ -12,15 +11,21 @@ class Pagy ITEMS_PLACEHOLDER = '__pagy_items__' - module UseItemsExtra ; private + module UseItemsExtra + private - [:pagy_get_vars, :pagy_countless_get_vars, :pagy_elasticsearch_rails_get_vars, :pagy_searchkick_get_vars].each do |meth| - if Backend.private_method_defined?(meth, true) - define_method(meth) do |collection, vars| - vars[:items] ||= (items = params[vars[:items_param] || VARS[:items_param]]) && # :items from :items_param + %i[ pagy_get_vars + pagy_countless_get_vars + pagy_elasticsearch_rails_get_vars + pagy_searchkick_get_vars + ].each do |meth| + next unless Backend.private_method_defined?(meth, true) + + define_method(meth) do |collection, vars| + vars[:items] ||= if (items = params[vars[:items_param] || VARS[:items_param]]) # :items from :items_param [items.to_i, vars.key?(:max_items) ? vars[:max_items] : VARS[:max_items]].compact.min # :items capped to :max_items - super(collection, vars) - end + end + super collection, vars end end @@ -31,7 +36,7 @@ module UseItemsExtra ; private module Frontend module UseItemsExtra - def pagy_url_for(page, pagy, url=false) + def pagy_url_for(page, pagy, url=nil) p_vars = pagy.vars params = request.GET.merge(p_vars[:params]) params[p_vars[:page_param].to_s] = page @@ -49,9 +54,11 @@ def pagy_items_selector_js(pagy, id=pagy_id) link = pagy_marked_link(pagy_link_proc(pagy)) p_vars[:items] = p_items # restore the items - html = %() + html = %() input = %() - html << %(#{pagy_t('pagy.items_selector_js', item_name: pagy_t(p_vars[:i18n_key], count: p_items), items_input: input, count: p_items)}) + html << pagy_t('pagy.items_selector_js', item_name: pagy_t(p_vars[:i18n_key], count: p_items), + items_input: input, + count: p_items) html << %(#{pagy_json_tag(pagy, :items_selector, id, pagy.from, link)}) end diff --git a/lib/pagy/extras/materialize.rb b/lib/pagy/extras/materialize.rb index a9ccfb532..440d6772d 100644 --- a/lib/pagy/extras/materialize.rb +++ b/lib/pagy/extras/materialize.rb @@ -1,5 +1,4 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/materialize -# encoding: utf-8 # frozen_string_literal: true require 'pagy/extras/shared' @@ -9,51 +8,70 @@ module Frontend # Pagination for materialize: it returns the html with the series of links to the pages def pagy_materialize_nav(pagy) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next - html = (p_prev ? %() - : +%()) + link = pagy_link_proc(pagy) + + html = +%() end # Javascript pagination for materialize: it returns a nav and a JSON tag used by the Pagy.nav javascript def pagy_materialize_nav_js(pagy, id=pagy_id) - link, p_prev, p_next = pagy_link_proc(pagy), pagy.prev, pagy.next - tags = { 'before' => ( '
      ' \ - + (p_prev ? %() - : %()) ), + link = pagy_link_proc(pagy) + tags = { 'before' => %(
        #{pagy_materialize_prev_html(pagy, link)}), 'link' => %(
      • #{mark = link.call(PAGE_PLACEHOLDER)}
      • ), 'active' => %(
      • #{mark}
      • ), 'gap' => %(
      • #{pagy_t('pagy.nav.gap')}
      • ), - 'after' => ( (p_next ? %() - : %()) \ - + '
      ' ) } - %(#{pagy_json_tag(pagy, :nav, id, tags, pagy.sequels)}) + 'after' => %(#{pagy_materialize_next_html(pagy, link)}
    ) } + + html = %() + html << pagy_json_tag(pagy, :nav, id, tags, pagy.sequels) end # Javascript combo pagination for materialize: it returns a nav and a JSON tag used by the Pagy.combo_nav javascript def pagy_materialize_combo_nav_js(pagy, id=pagy_id) - link, p_prev, p_next, p_page, p_pages = pagy_link_proc(pagy), pagy.prev, pagy.next, pagy.page, pagy.pages - - html = %(