pagy 8.6.2 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
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} لە هەر پەڕێک"