pagy 8.6.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +5 -5
  3. data/apps/demo.ru +4 -4
  4. data/apps/keyset_ar.ru +236 -0
  5. data/apps/keyset_s.ru +238 -0
  6. data/apps/rails.ru +7 -8
  7. data/apps/repro.ru +4 -4
  8. data/apps/tmp/calendar.sqlite3 +0 -0
  9. data/apps/tmp/calendar.sqlite3-shm +0 -0
  10. data/apps/tmp/calendar.sqlite3-wal +0 -0
  11. data/apps/tmp/local_secret.txt +1 -1
  12. data/apps/tmp/pagy-keyset-ar.sqlite3 +0 -0
  13. data/apps/tmp/pagy-keyset-ar.sqlite3-shm +0 -0
  14. data/apps/tmp/pagy-keyset-ar.sqlite3-wal +0 -0
  15. data/apps/tmp/pagy-keyset-s.sqlite3 +0 -0
  16. data/bin/pagy +4 -2
  17. data/config/pagy.rb +14 -13
  18. data/javascripts/pagy-module.js +1 -1
  19. data/javascripts/pagy.js +2 -2
  20. data/javascripts/pagy.min.js +2 -2
  21. data/javascripts/pagy.min.js.map +2 -2
  22. data/javascripts/pagy.mjs +2 -2
  23. data/lib/pagy/b64.rb +33 -0
  24. data/lib/pagy/backend.rb +21 -17
  25. data/lib/pagy/calendar/day.rb +1 -1
  26. data/lib/pagy/calendar/month.rb +1 -1
  27. data/lib/pagy/calendar/quarter.rb +1 -1
  28. data/lib/pagy/calendar/unit.rb +7 -10
  29. data/lib/pagy/calendar/week.rb +1 -1
  30. data/lib/pagy/calendar/year.rb +1 -1
  31. data/lib/pagy/calendar.rb +5 -5
  32. data/lib/pagy/countless.rb +10 -14
  33. data/lib/pagy/extras/arel.rb +8 -10
  34. data/lib/pagy/extras/array.rb +4 -6
  35. data/lib/pagy/extras/bootstrap.rb +5 -5
  36. data/lib/pagy/extras/bulma.rb +10 -7
  37. data/lib/pagy/extras/calendar.rb +4 -5
  38. data/lib/pagy/extras/countless.rb +6 -13
  39. data/lib/pagy/extras/elasticsearch_rails.rb +15 -15
  40. data/lib/pagy/extras/gearbox.rb +18 -18
  41. data/lib/pagy/extras/headers.rb +25 -24
  42. data/lib/pagy/extras/js_tools.rb +4 -4
  43. data/lib/pagy/extras/jsonapi.rb +26 -16
  44. data/lib/pagy/extras/keyset.rb +26 -0
  45. data/lib/pagy/extras/limit.rb +63 -0
  46. data/lib/pagy/extras/meilisearch.rb +11 -11
  47. data/lib/pagy/extras/metadata.rb +2 -2
  48. data/lib/pagy/extras/overflow.rb +5 -5
  49. data/lib/pagy/extras/pagy.rb +16 -16
  50. data/lib/pagy/extras/searchkick.rb +11 -11
  51. data/lib/pagy/extras/size.rb +1 -1
  52. data/lib/pagy/extras/standalone.rb +6 -6
  53. data/lib/pagy/extras/trim.rb +2 -2
  54. data/lib/pagy/frontend.rb +32 -33
  55. data/lib/pagy/i18n.rb +1 -1
  56. data/lib/pagy/keyset/active_record.rb +38 -0
  57. data/lib/pagy/keyset/sequel.rb +51 -0
  58. data/lib/pagy/keyset.rb +99 -0
  59. data/lib/pagy/url_helpers.rb +5 -5
  60. data/lib/pagy.rb +73 -65
  61. data/locales/ar.yml +2 -1
  62. data/locales/be.yml +1 -1
  63. data/locales/bg.yml +1 -1
  64. data/locales/bs.yml +1 -1
  65. data/locales/ca.yml +1 -1
  66. data/locales/ckb.yml +1 -1
  67. data/locales/cs.yml +1 -1
  68. data/locales/da.yml +1 -1
  69. data/locales/de.yml +1 -1
  70. data/locales/en.yml +1 -1
  71. data/locales/es.yml +1 -1
  72. data/locales/fr.yml +1 -1
  73. data/locales/hr.yml +1 -1
  74. data/locales/id.yml +1 -1
  75. data/locales/it.yml +1 -1
  76. data/locales/ja.yml +1 -1
  77. data/locales/km.yml +1 -1
  78. data/locales/ko.yml +1 -1
  79. data/locales/nb.yml +1 -1
  80. data/locales/nl.yml +1 -1
  81. data/locales/nn.yml +1 -1
  82. data/locales/pl.yml +1 -1
  83. data/locales/pt-BR.yml +1 -1
  84. data/locales/pt.yml +1 -1
  85. data/locales/ru.yml +1 -1
  86. data/locales/sr.yml +1 -1
  87. data/locales/sv-SE.yml +1 -1
  88. data/locales/sv.yml +1 -1
  89. data/locales/sw.yml +1 -1
  90. data/locales/ta.yml +1 -1
  91. data/locales/tr.yml +1 -1
  92. data/locales/uk.yml +1 -1
  93. data/locales/vi.yml +1 -1
  94. data/locales/zh-CN.yml +1 -1
  95. data/locales/zh-HK.yml +1 -1
  96. data/locales/zh-TW.yml +1 -1
  97. data/pkg/pagy-9.0.0.gem +0 -0
  98. metadata +16 -15
  99. data/lib/pagy/extras/foundation.rb +0 -95
  100. data/lib/pagy/extras/items.rb +0 -64
  101. data/lib/pagy/extras/materialize.rb +0 -100
  102. data/lib/pagy/extras/semantic.rb +0 -94
  103. data/lib/pagy/extras/uikit.rb +0 -98
@@ -23,11 +23,11 @@ class Pagy # :nodoc:
23
23
  # Additions for the Pagy class
24
24
  module PagyExtension
25
25
  # Create a Pagy object from a Searchkick::Results object
26
- def new_from_searchkick(results, vars = {})
27
- vars[:items] = results.options[:per_page]
26
+ def new_from_searchkick(results, **vars)
27
+ vars[:limit] = results.options[:per_page]
28
28
  vars[:page] = results.options[:page]
29
29
  vars[:count] = results.total_count
30
- new(vars)
30
+ new(**vars)
31
31
  end
32
32
  end
33
33
  Pagy.extend PagyExtension
@@ -37,17 +37,17 @@ class Pagy # :nodoc:
37
37
  private
38
38
 
39
39
  # Return Pagy object and results
40
- def pagy_searchkick(pagy_search_args, vars = {})
40
+ def pagy_searchkick(pagy_search_args, **vars)
41
41
  model, term, options, block, *called = pagy_search_args
42
42
  vars = pagy_searchkick_get_vars(nil, vars)
43
- options[:per_page] = vars[:items]
43
+ options[:per_page] = vars[:limit]
44
44
  options[:page] = vars[:page]
45
45
  results = model.send(DEFAULT[:searchkick_search], term, **options, &block)
46
46
  vars[:count] = results.total_count
47
47
 
48
- pagy = ::Pagy.new(vars)
48
+ pagy = ::Pagy.new(**vars)
49
49
  # with :last_page overflow we need to re-run the method in order to get the hits
50
- return pagy_searchkick(pagy_search_args, vars.merge(page: pagy.page)) \
50
+ return pagy_searchkick(pagy_search_args, **vars, page: pagy.page) \
51
51
  if defined?(::Pagy::OverflowExtra) && pagy.overflow? && pagy.vars[:overflow] == :last_page
52
52
 
53
53
  [pagy, called.empty? ? results : results.send(*called)]
@@ -56,10 +56,10 @@ class Pagy # :nodoc:
56
56
  # Sub-method called only by #pagy_searchkick: here for easy customization of variables by overriding
57
57
  # the _collection argument is not available when the method is called
58
58
  def pagy_searchkick_get_vars(_collection, vars)
59
- pagy_set_items_from_params(vars) if defined?(ItemsExtra)
60
- vars[:items] ||= DEFAULT[:items]
61
- vars[:page] ||= pagy_get_page(vars)
62
- vars
59
+ vars.tap do |v|
60
+ v[:page] ||= pagy_get_page(v)
61
+ v[:limit] ||= pagy_get_limit(v) || DEFAULT[:limit]
62
+ end
63
63
  end
64
64
  end
65
65
  Backend.prepend BackendAddOn
@@ -5,7 +5,7 @@ class Pagy # :nodoc:
5
5
  # Implement the legacy bar using the array size.
6
6
  # Unless you have very specific requirements, use the faster and better looking default bar.
7
7
  module SizeExtra
8
- # Setup @items based on the :gearbox_items variable
8
+ # Implements the old series algorithm
9
9
  def series(size: @vars[:size], **_)
10
10
  return super unless size.is_a?(Array)
11
11
  return [] if size == []
@@ -12,10 +12,6 @@ class Pagy # :nodoc:
12
12
  module QueryUtils
13
13
  module_function
14
14
 
15
- def escape(str)
16
- URI.encode_www_form_component(str)
17
- end
18
-
19
15
  def build_nested_query(value, prefix = nil)
20
16
  case value
21
17
  when Array
@@ -32,13 +28,17 @@ class Pagy # :nodoc:
32
28
  "#{escape(prefix)}=#{escape(value)}"
33
29
  end
34
30
  end
31
+
32
+ def escape(str)
33
+ URI.encode_www_form_component(str)
34
+ end
35
35
  end
36
36
  # :nocov:
37
37
 
38
38
  # Return the URL for the page. If there is no pagy.vars[:url]
39
39
  # it works exactly as the regular #pagy_url_for, relying on the params method and Rack.
40
40
  # If there is a defined pagy.vars[:url] variable it does not need the params method nor Rack.
41
- def pagy_url_for(pagy, page, absolute: false, **_)
41
+ def pagy_url_for(pagy, page, fragment: nil, **_)
42
42
  return super unless pagy.vars[:url]
43
43
 
44
44
  vars = pagy.vars
@@ -46,7 +46,7 @@ class Pagy # :nodoc:
46
46
  pagy_set_query_params(page, vars, params)
47
47
  params = vars[:params].(params) if vars[:params].is_a?(Proc)
48
48
  query_string = "?#{QueryUtils.build_nested_query(params)}"
49
- "#{vars[:url]}#{query_string}#{vars[:fragment]}"
49
+ "#{vars[:url]}#{query_string}#{fragment}"
50
50
  end
51
51
  end
52
52
  UrlHelpers.prepend StandaloneExtra
@@ -6,9 +6,9 @@ class Pagy # :nodoc:
6
6
 
7
7
  # Remove the page=1 param from the first page link
8
8
  module TrimExtra
9
- # Override the original pagy_a_proc.
9
+ # Override the original pagy_anchor.
10
10
  # Call the pagy_trim method for page 1 if the trim_extra is enabled
11
- def pagy_anchor(pagy)
11
+ def pagy_anchor(pagy, **_)
12
12
  a_proc = super
13
13
  return a_proc unless pagy.vars[:trim_extra]
14
14
 
data/lib/pagy/frontend.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/docs/api/frontend
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative 'url_helpers'
5
4
  require_relative 'i18n'
5
+ require_relative 'url_helpers'
6
6
 
7
7
  class Pagy
8
8
  # Used for search and replace, hardcoded also in the pagy.js file
@@ -14,26 +14,17 @@ class Pagy
14
14
  module Frontend
15
15
  include UrlHelpers
16
16
 
17
- # Generic pagination: it returns the html with the series of links to the pages
18
- def pagy_nav(pagy, id: nil, aria_label: nil, **vars)
19
- id = %( id="#{id}") if id
20
- a = pagy_anchor(pagy)
21
-
22
- html = %(<nav#{id} class="pagy nav" #{nav_aria_label(pagy, aria_label:)}>#{
23
- prev_a(pagy, a)})
24
- pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
25
- html << case item
26
- when Integer
27
- a.(item)
28
- when String
29
- %(<a role="link" aria-disabled="true" aria-current="page" class="current">#{pagy.label_for(item)}</a>)
30
- when :gap
31
- %(<a role="link" aria-disabled="true" class="gap">#{pagy_t('pagy.gap')}</a>)
32
- else
33
- raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
34
- end
17
+ # Return a performance optimized lambda to generate the HTML anchor element (a tag)
18
+ # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
19
+ def pagy_anchor(pagy, anchor_string: nil, **vars)
20
+ anchor_string &&= %( #{anchor_string})
21
+ left, right = %(<a#{anchor_string} href="#{pagy_url_for(pagy, PAGE_TOKEN, **vars)}").split(PAGE_TOKEN, 2)
22
+ # lambda used by all the helpers
23
+ lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
24
+ classes = %( class="#{classes}") if classes
25
+ aria_label = %( aria-label="#{aria_label}") if aria_label
26
+ %(#{left}#{page}#{right}#{classes}#{aria_label}>#{text}</a>)
35
27
  end
36
- html << %(#{next_a(pagy, a)}</nav>)
37
28
  end
38
29
 
39
30
  # Return examples: "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
@@ -54,24 +45,32 @@ class Pagy
54
45
  }</span>)
55
46
  end
56
47
 
57
- # Return a performance optimized lambda to generate the HTML anchor element (a tag)
58
- # Benchmarked on a 20 link nav: it is ~22x faster and uses ~18x less memory than rails' link_to
59
- def pagy_anchor(pagy)
60
- a_string = pagy.vars[:anchor_string]
61
- a_string = %( #{a_string}) if a_string
62
- left, right = %(<a#{a_string} href="#{pagy_url_for(pagy, PAGE_TOKEN)}").split(PAGE_TOKEN, 2)
63
- # lambda used by all the helpers
64
- lambda do |page, text = pagy.label_for(page), classes: nil, aria_label: nil|
65
- classes = %( class="#{classes}") if classes
66
- aria_label = %( aria-label="#{aria_label}") if aria_label
67
- %(#{left}#{page}#{right}#{classes}#{aria_label}>#{text}</a>)
48
+ # Generic pagination: it returns the html with the series of links to the pages
49
+ def pagy_nav(pagy, id: nil, aria_label: nil, anchor_string: nil, **vars)
50
+ id = %( id="#{id}") if id
51
+ a = pagy_anchor(pagy, anchor_string:)
52
+
53
+ html = %(<nav#{id} class="pagy nav" #{nav_aria_label(pagy, aria_label:)}>#{
54
+ prev_a(pagy, a)})
55
+ pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
56
+ html << case item
57
+ when Integer
58
+ a.(item)
59
+ when String
60
+ %(<a role="link" aria-disabled="true" aria-current="page" class="current">#{pagy.label_for(item)}</a>)
61
+ when :gap
62
+ %(<a role="link" aria-disabled="true" class="gap">#{pagy_t('pagy.gap')}</a>)
63
+ else
64
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
65
+ end
68
66
  end
67
+ html << %(#{next_a(pagy, a)}</nav>)
69
68
  end
70
69
 
71
70
  # Similar to I18n.t: just ~18x faster using ~10x less memory
72
71
  # (@pagy_locale explicitly initialized in order to avoid warning)
73
- def pagy_t(key, opts = {})
74
- Pagy::I18n.translate(@pagy_locale ||= nil, key, opts)
72
+ def pagy_t(key, **opts)
73
+ Pagy::I18n.translate(@pagy_locale ||= nil, key, **opts)
75
74
  end
76
75
 
77
76
  private
data/lib/pagy/i18n.rb CHANGED
@@ -154,7 +154,7 @@ class Pagy
154
154
  end
155
155
 
156
156
  # Translate and pluralize the key with the locale DATA
157
- def translate(locale, key, opts = {})
157
+ def translate(locale, key, **opts)
158
158
  data, pluralize = DATA[locale]
159
159
  translation = data[key] || (opts[:count] && data[key += ".#{pluralize.call(opts[:count])}"]) \
160
160
  or return %([translation missing: "#{key}"])
@@ -0,0 +1,38 @@
1
+ # See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy
5
+ class Keyset
6
+ # Keyset adapter for ActiveRecord
7
+ class ActiveRecord < Keyset
8
+ protected
9
+
10
+ # Get the keyset attributes of the record
11
+ def latest_from(latest_record) = latest_record.slice(*@keyset.keys)
12
+
13
+ # Extract the keyset from the set
14
+ def extract_keyset
15
+ @set.order_values.each_with_object({}) do |node, keyset|
16
+ keyset[node.value.name.to_sym] = node.direction
17
+ end
18
+ end
19
+
20
+ # Filter out the already retrieved records
21
+ def after_latest = @set.where(after_latest_query, **@latest)
22
+
23
+ # Append the missing keyset keys if the set is restricted by select
24
+ def apply_select
25
+ @set.select(*@keyset.keys)
26
+ end
27
+
28
+ # Set with selected columns?
29
+ def select? = !@set.select_values.empty?
30
+
31
+ # Typecast the latest attributes
32
+ def typecast_latest(latest)
33
+ @set.model.new(latest).slice(latest.keys)
34
+ .to_hash.transform_keys(&:to_sym)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy
5
+ class Keyset
6
+ # Keyset adapter for sequel
7
+ class Sequel < Keyset
8
+ protected
9
+
10
+ # Get the keyset attributes of the latest record
11
+ def latest_from(latest_record) = latest_record.to_hash.slice(*@keyset.keys)
12
+
13
+ # Extract the keyset from the set
14
+ def extract_keyset
15
+ return {} unless @set.opts[:order]
16
+
17
+ @set.opts[:order].each_with_object({}) do |item, keyset|
18
+ case item
19
+ when Symbol
20
+ keyset[item] = :asc
21
+ when ::Sequel::SQL::OrderedExpression
22
+ keyset[item.expression] = item.descending ? :desc : :asc
23
+ else
24
+ raise TypeError, "#{item.class.inspect} is not a supported Sequel::SQL::OrderedExpression"
25
+ end
26
+ end
27
+ end
28
+
29
+ # Filter out the already retrieved records
30
+ def after_latest = @set.where(::Sequel.lit(after_latest_query, **@latest))
31
+
32
+ # Append the missing keyset keys if the set is restricted by select
33
+ def apply_select
34
+ selected = @set.opts[:select]
35
+ @set.select_append(*@keyset.keys.reject { |c| selected.include?(c) })
36
+ end
37
+
38
+ # Set with selected columns?
39
+ def select? = !@set.opts[:select].nil?
40
+
41
+ # Typecast the latest attributes
42
+ def typecast_latest(latest)
43
+ model = @set.opts[:model]
44
+ model.unrestrict_primary_key if (restricted_pk = model.restrict_primary_key?)
45
+ latest = model.new(latest).to_hash.slice(*latest.keys.map(&:to_sym))
46
+ model.restrict_primary_key if restricted_pk
47
+ latest
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,99 @@
1
+ # See Pagy API documentation: https://ddnexus.github.io/pagy/docs/api/keyset
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require_relative 'b64'
6
+
7
+ class Pagy
8
+ # Implement wicked-fast keyset pagination for big data
9
+ class Keyset
10
+ class TypeError < ::TypeError; end
11
+
12
+ include SharedMethods
13
+
14
+ # Pick the right adapter for the set
15
+ def self.new(set, **vars)
16
+ if self == Pagy::Keyset
17
+ if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
18
+ ActiveRecord
19
+ elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
20
+ Sequel
21
+ else
22
+ raise TypeError, "expected set to be an instance of ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
23
+ end.new(set, **vars)
24
+ else
25
+ allocate.tap { |instance| instance.send(:initialize, set, **vars) }
26
+ end
27
+ end
28
+
29
+ attr_reader :latest # Other readers from SharedMethods
30
+
31
+ def initialize(set, **vars)
32
+ default = DEFAULT.slice(:limit, :page_param, # from pagy
33
+ :headers, # from headers extra
34
+ :jsonapi, # from jsonapi extra
35
+ :limit_param, :limit_max, :limit_extra) # from limit_extra
36
+ assign_vars({ **default, page: nil }, vars)
37
+ assign_limit
38
+ @set = set
39
+ @page = @vars[:page]
40
+ @keyset = extract_keyset
41
+ raise InternalError, 'the set must be ordered' if @keyset.empty?
42
+ return unless @page
43
+
44
+ latest = JSON.parse(B64.urlsafe_decode(@page)).transform_keys(&:to_sym)
45
+ @latest = @vars[:typecast_latest]&.(latest) || typecast_latest(latest)
46
+ raise InternalError, 'page and keyset are not consistent' \
47
+ unless @latest.keys == @keyset.keys
48
+ end
49
+
50
+ # Return the next page
51
+ def next
52
+ records
53
+ return unless @more
54
+
55
+ @next ||= B64.urlsafe_encode(latest_from(@records.last).to_json)
56
+ end
57
+
58
+ # Retrieve the array of records for the current page
59
+ def records
60
+ @records ||= begin
61
+ @set = apply_select if select?
62
+ @set = @vars[:after_latest]&.(@set, @latest) || after_latest if @latest
63
+ records = @set.limit(@limit + 1).to_a
64
+ @more = records.size > @limit && !records.pop.nil?
65
+ records
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ # Prepare the literal query to filter out the already retrieved records
72
+ def after_latest_query
73
+ operator = { asc: '>', desc: '<' }
74
+ directions = @keyset.values
75
+ if @vars[:row_comparison]
76
+ # Row comparison: check if your DB supports it (especially if you have mixed order rows)
77
+ columns = @keyset.keys
78
+ placeholders = columns.map { |column| ":#{column}" }.join(', ')
79
+ "( #{columns.join(', ')} ) #{operator[directions.first]} ( #{placeholders} )"
80
+ else
81
+ # Generic comparison: works for keysets ordered in mixed or same directions
82
+ keyset = @keyset.to_a
83
+ where = []
84
+ until keyset.empty?
85
+ last_column, last_direction = keyset.pop
86
+ query = +'( '
87
+ query << (keyset.map { |column, _d| "#{column} = :#{column}" } \
88
+ << "#{last_column} #{operator[last_direction]} :#{last_column}").join(' AND ')
89
+ query << ' )'
90
+ where << query
91
+ end
92
+ where.join(' OR ')
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ require_relative 'keyset/active_record'
99
+ require_relative 'keyset/sequel'
@@ -6,21 +6,21 @@ class Pagy
6
6
  # Return the URL for the page, relying on the params method and Rack by default.
7
7
  # It supports all Rack-based frameworks (Sinatra, Padrino, Rails, ...).
8
8
  # For non-rack environments you can use the standalone extra
9
- def pagy_url_for(pagy, page, absolute: false, **_)
9
+ def pagy_url_for(pagy, page, absolute: false, fragment: nil, **_)
10
10
  vars = pagy.vars
11
11
  query_params = request.GET.clone
12
12
  query_params.merge!(vars[:params].transform_keys(&:to_s)) if vars[:params].is_a?(Hash)
13
13
  pagy_set_query_params(page, vars, query_params)
14
14
  query_params = vars[:params].(query_params) if vars[:params].is_a?(Proc)
15
15
  query_string = "?#{Rack::Utils.build_nested_query(query_params)}"
16
- "#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{vars[:fragment]}"
16
+ "#{request.base_url if absolute}#{vars[:request_path] || request.path}#{query_string}#{fragment}"
17
17
  end
18
18
 
19
- # Add the page and items params
19
+ # Add the page and limit params
20
20
  # Overridable by the jsonapi extra
21
21
  def pagy_set_query_params(page, vars, query_params)
22
- query_params[vars[:page_param].to_s] = page
23
- query_params[vars[:items_param].to_s] = vars[:items] if vars[:items_extra]
22
+ query_params[vars[:page_param].to_s] = page if page
23
+ query_params[vars[:limit_param].to_s] = vars[:limit] if vars[:limit_extra]
24
24
  end
25
25
  end
26
26
  end
data/lib/pagy.rb CHANGED
@@ -3,47 +3,98 @@
3
3
 
4
4
  require 'pathname'
5
5
 
6
- # Core class
6
+ # Top superclass: it should define only what's common to all the subclasses
7
7
  class Pagy
8
- VERSION = '8.6.2'
8
+ VERSION = '9.0.0'
9
+
10
+ # Core default: constant for easy access, but mutable for customizable defaults
11
+ DEFAULT = { count_args: [:all], # rubocop:disable Style/MutableConstant
12
+ ends: true,
13
+ limit: 20,
14
+ outset: 0,
15
+ page: 1,
16
+ page_param: :page,
17
+ size: 7 } # AR friendly
9
18
 
10
19
  # Gem root pathname to get the path of Pagy files stylesheets, javascripts, apps, locales, etc.
11
20
  def self.root
12
21
  @root ||= Pathname.new(__dir__).parent.freeze
13
22
  end
14
23
 
15
- # Core default: constant for easy access, but mutable for customizable defaults
16
- DEFAULT = { page: 1, # rubocop:disable Style/MutableConstant
17
- items: 20,
18
- outset: 0,
19
- size: 7,
20
- ends: true,
21
- count_args: [:all], # AR friendly
22
- page_param: :page }
24
+ # Shared with Keyset
25
+ module SharedMethods
26
+ attr_reader :page, :limit, :vars
23
27
 
24
- attr_reader :count, :page, :items, :vars, :last, :offset, :in, :from, :to, :prev, :next
28
+ # Validates and assign the passed vars: var must be present and value.to_i must be >= to min
29
+ def assign_and_check(name_min)
30
+ name_min.each do |name, min|
31
+ raise VariableError.new(self, name, ">= #{min}", @vars[name]) \
32
+ unless @vars[name]&.respond_to?(:to_i) && \
33
+ instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
34
+ end
35
+ end
36
+
37
+ # Assign @limit (overridden by the gearbox extra)
38
+ def assign_limit
39
+ assign_and_check(limit: 1)
40
+ end
41
+
42
+ # Assign @vars
43
+ def assign_vars(default, vars)
44
+ @vars = { **default, **vars.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') } }
45
+ end
46
+ end
47
+ include SharedMethods
48
+
49
+ attr_reader :count, :from, :in, :last, :next, :offset, :prev, :to
25
50
  alias pages last
26
51
 
27
52
  # Merge and validate the options, do some simple arithmetic and set the instance variables
28
- def initialize(vars)
29
- normalize_vars(vars)
30
- setup_vars(count: 0, page: 1, outset: 0)
31
- setup_items_var
32
- setup_offset_var
33
- setup_last_var
34
- raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
35
-
53
+ def initialize(**vars)
54
+ assign_vars(DEFAULT, vars)
55
+ assign_and_check(count: 0, page: 1, outset: 0)
56
+ assign_limit
57
+ assign_offset
58
+ assign_last
59
+ check_overflow
36
60
  @from = [@offset - @outset + 1, @count].min
37
- @to = [@offset - @outset + @items, @count].min
61
+ @to = [@offset - @outset + @limit, @count].min
38
62
  @in = [@to - @from + 1, @count].min
63
+ assign_prev_and_next
64
+ end
65
+
66
+ # Setup @last (overridden by the gearbox extra)
67
+ def assign_last
68
+ @last = [(@count.to_f / @limit).ceil, 1].max
69
+ @last = @vars[:max_pages] if @vars[:max_pages] && @last > vars[:max_pages]
70
+ end
71
+
72
+ # Assign @offset (overridden by the gearbox extra)
73
+ def assign_offset
74
+ @offset = (@limit * (@page - 1)) + @outset # may be already set from gear_box
75
+ end
76
+
77
+ # Assign @prev and @next
78
+ def assign_prev_and_next
39
79
  @prev = (@page - 1 unless @page == 1)
40
80
  @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
41
81
  end
42
82
 
43
- # Return the array of page numbers and :gap items e.g. [1, :gap, 8, "9", 10, :gap, 36]
83
+ # Checks the @page <= @last
84
+ def check_overflow
85
+ raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
86
+ end
87
+
88
+ # Label for the current page. Allow the customization of the output (overridden by the calendar extra)
89
+ def label = @page.to_s
90
+
91
+ # Label for any page. Allow the customization of the output (overridden by the calendar extra)
92
+ def label_for(page) = page.to_s
93
+
94
+ # Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
44
95
  def series(size: @vars[:size], **_)
45
96
  raise VariableError.new(self, :size, 'to be an Integer >= 0', size) \
46
- unless size.is_a?(Integer) && size >= 0
97
+ unless size.is_a?(Integer) && size >= 0
47
98
  return [] if size.zero?
48
99
 
49
100
  [].tap do |series|
@@ -70,51 +121,8 @@ class Pagy
70
121
  series[series.index(@page)] = @page.to_s
71
122
  end
72
123
  end
73
-
74
- # Label for any page. Allow the customization of the output (overridden by the calendar extra)
75
- def label_for(page)
76
- page.to_s
77
- end
78
-
79
- # Label for the current page. Allow the customization of the output (overridden by the calendar extra)
80
- def label
81
- @page.to_s
82
- end
83
-
84
- protected
85
-
86
- # Apply defaults, cleanup blanks and set @vars
87
- def normalize_vars(vars)
88
- @vars = DEFAULT.merge(vars.delete_if { |k, v| DEFAULT.key?(k) && (v.nil? || v == '') })
89
- end
90
-
91
- # Setup and validates the passed vars: var must be present and value.to_i must be >= to min
92
- def setup_vars(name_min)
93
- name_min.each do |name, min|
94
- raise VariableError.new(self, name, ">= #{min}", @vars[name]) \
95
- unless @vars[name]&.respond_to?(:to_i) && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
96
- end
97
- end
98
-
99
- # Setup @items (overridden by the gearbox extra)
100
- def setup_items_var
101
- setup_vars(items: 1)
102
- end
103
-
104
- # Setup @offset (overridden by the gearbox extra)
105
- def setup_offset_var
106
- @offset = (@items * (@page - 1)) + @outset # may be already set from gear_box
107
- end
108
-
109
- # Setup @last (overridden by the gearbox extra)
110
- def setup_last_var
111
- @last = [(@count.to_f / @items).ceil, 1].max
112
- @last = vars[:max_pages] if vars[:max_pages] && @last > vars[:max_pages]
113
- end
114
- alias setup_pages_var setup_last_var
115
124
  end
116
125
 
117
- require_relative 'pagy/extras/size' # will be opt in in v9.0
118
126
  require_relative 'pagy/backend'
119
127
  require_relative 'pagy/frontend'
120
128
  require_relative 'pagy/exceptions'
data/locales/ar.yml CHANGED
@@ -8,6 +8,7 @@ ar:
8
8
  two: "صفحتين"
9
9
  few: "صفحات"
10
10
  many: "صفحات"
11
+ other: "صفحات"
11
12
  prev: "السابق"
12
13
  next: "التالي"
13
14
  prev: "&lt;"
@@ -25,4 +26,4 @@ ar:
25
26
  single_page: "عرض %{count} %{item_name}"
26
27
  multiple_pages: "عرض %{item_name} %{from}-%{to} من اجمالي %{count}"
27
28
  combo_nav_js: "الصفحة %{page_input} من %{pages}"
28
- items_selector_js: "عرض %{items_input} %{item_name} لكل صفحة"
29
+ limit_selector_js: "عرض %{limit_input} %{item_name} لكل صفحة"
data/locales/be.yml CHANGED
@@ -22,4 +22,4 @@ be:
22
22
  single_page: "%{count} %{item_name}"
23
23
  multiple_pages: "Усяго %{count} %{item_name}, паказаны з %{from} па %{to}"
24
24
  combo_nav_js: "Старонка %{page_input} з %{pages}"
25
- items_selector_js: "Паказаць %{items_input} %{item_name} на старонцы"
25
+ limit_selector_js: "Паказаць %{limit_input} %{item_name} на старонцы"
data/locales/bg.yml CHANGED
@@ -18,4 +18,4 @@ bg:
18
18
  single_page: "Показани са %{count} %{item_name}"
19
19
  multiple_pages: "Показани са %{item_name} %{from}-%{to} от %{count} общо"
20
20
  combo_nav_js: "Страница %{page_input} от %{pages}"
21
- items_selector_js: "Покажи %{items_input} %{item_name} на страница"
21
+ limit_selector_js: "Покажи %{limit_input} %{item_name} на страница"
data/locales/bs.yml CHANGED
@@ -22,4 +22,4 @@ bs:
22
22
  single_page: "Prikazuje se %{count} %{item_name}"
23
23
  multiple_pages: "Prikaz %{item_name} %{from}-%{to} od %{count} ukupno"
24
24
  combo_nav_js: "Stranica %{page_input} od %{pages}"
25
- items_selector_js: "Prikaži %{items_input} %{item_name} po stranici"
25
+ limit_selector_js: "Prikaži %{limit_input} %{item_name} po stranici"
data/locales/ca.yml CHANGED
@@ -18,4 +18,4 @@ ca:
18
18
  single_page: "Mostrant %{count} %{item_name}"
19
19
  multiple_pages: "Mostrant %{item_name} %{from}-%{to} de %{count} en total"
20
20
  combo_nav_js: "Pàgina %{page_input} de %{pages}"
21
- items_selector_js: "Mostra %{items_input} %{item_name} per pàgina"
21
+ limit_selector_js: "Mostra %{limit_input} %{item_name} per pàgina"
data/locales/ckb.yml CHANGED
@@ -15,4 +15,4 @@ ckb:
15
15
  single_page: "پیشاندانی %{count} %{item_name}"
16
16
  multiple_pages: "پشاندانی %{item_name}ی %{from}-%{to} لە کۆی %{count} بە گشتی"
17
17
  combo_nav_js: "پەڕی %{page_input} لە %{pages}"
18
- items_selector_js: "نیشاندانی %{items_input} %{item_name} لە هەر پەڕێک"
18
+ limit_selector_js: "نیشاندانی %{limit_input} %{item_name} لە هەر پەڕێک"