pagy 3.8.2 → 9.4.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +737 -0
  4. data/apps/demo.ru +449 -0
  5. data/apps/index.rb +7 -0
  6. data/apps/keyset_ar.ru +228 -0
  7. data/apps/keyset_s.ru +220 -0
  8. data/apps/rails.ru +217 -0
  9. data/apps/repro.ru +182 -0
  10. data/bin/pagy +98 -0
  11. data/config/pagy.rb +220 -0
  12. data/javascripts/pagy.d.ts +5 -0
  13. data/javascripts/pagy.min.js +4 -0
  14. data/javascripts/pagy.min.js.map +10 -0
  15. data/javascripts/pagy.mjs +100 -0
  16. data/lib/optimist.rb +1022 -0
  17. data/lib/pagy/b64.rb +33 -0
  18. data/lib/pagy/backend.rb +30 -19
  19. data/lib/pagy/calendar/day.rb +41 -0
  20. data/lib/pagy/calendar/month.rb +42 -0
  21. data/lib/pagy/calendar/quarter.rb +49 -0
  22. data/lib/pagy/calendar/unit.rb +103 -0
  23. data/lib/pagy/calendar/week.rb +39 -0
  24. data/lib/pagy/calendar/year.rb +35 -0
  25. data/lib/pagy/calendar.rb +84 -0
  26. data/lib/pagy/console.rb +23 -0
  27. data/lib/pagy/countless.rb +27 -22
  28. data/lib/pagy/exceptions.rb +16 -13
  29. data/lib/pagy/extras/arel.rb +11 -14
  30. data/lib/pagy/extras/array.rb +12 -16
  31. data/lib/pagy/extras/bootstrap.rb +83 -41
  32. data/lib/pagy/extras/bulma.rb +79 -46
  33. data/lib/pagy/extras/calendar.rb +79 -0
  34. data/lib/pagy/extras/countless.rb +20 -25
  35. data/lib/pagy/extras/elasticsearch_rails.rb +59 -38
  36. data/lib/pagy/extras/gearbox.rb +55 -0
  37. data/lib/pagy/extras/headers.rb +38 -23
  38. data/lib/pagy/extras/i18n.rb +19 -18
  39. data/lib/pagy/extras/js_tools.rb +70 -0
  40. data/lib/pagy/extras/jsonapi.rb +88 -0
  41. data/lib/pagy/extras/keyset.rb +30 -0
  42. data/lib/pagy/extras/limit.rb +63 -0
  43. data/lib/pagy/extras/meilisearch.rb +57 -0
  44. data/lib/pagy/extras/metadata.rb +32 -27
  45. data/lib/pagy/extras/overflow.rb +61 -53
  46. data/lib/pagy/extras/pagy.rb +82 -0
  47. data/lib/pagy/extras/searchkick.rb +52 -41
  48. data/lib/pagy/extras/size.rb +40 -0
  49. data/lib/pagy/extras/standalone.rb +60 -0
  50. data/lib/pagy/extras/trim.rb +19 -13
  51. data/lib/pagy/frontend.rb +76 -51
  52. data/lib/pagy/i18n.rb +167 -0
  53. data/lib/pagy/keyset/active_record.rb +44 -0
  54. data/lib/pagy/keyset/sequel.rb +57 -0
  55. data/lib/pagy/keyset.rb +118 -0
  56. data/lib/pagy/shared_methods.rb +26 -0
  57. data/lib/pagy/url_helpers.rb +26 -0
  58. data/lib/pagy.rb +91 -37
  59. data/locales/ar.yml +29 -0
  60. data/locales/be.yml +25 -0
  61. data/locales/bg.yml +21 -0
  62. data/locales/bs.yml +25 -0
  63. data/locales/ca.yml +21 -0
  64. data/locales/ckb.yml +18 -0
  65. data/locales/cs.yml +23 -0
  66. data/locales/da.yml +21 -0
  67. data/locales/de.yml +21 -0
  68. data/locales/dz.yml +17 -0
  69. data/locales/en.yml +21 -0
  70. data/{lib/locales → locales}/es.yml +11 -12
  71. data/locales/fr.yml +21 -0
  72. data/locales/hr.yml +25 -0
  73. data/locales/id.yml +19 -0
  74. data/locales/it.yml +21 -0
  75. data/locales/ja.yml +19 -0
  76. data/locales/km.yml +19 -0
  77. data/locales/ko.yml +17 -0
  78. data/locales/nb.yml +21 -0
  79. data/locales/nl.yml +21 -0
  80. data/locales/nn.yml +21 -0
  81. data/locales/pl.yml +25 -0
  82. data/{lib/locales → locales}/pt-BR.yml +11 -12
  83. data/locales/pt.yml +21 -0
  84. data/locales/ru.yml +25 -0
  85. data/locales/sk.yml +23 -0
  86. data/locales/sr.yml +25 -0
  87. data/locales/sv-SE.yml +21 -0
  88. data/locales/sv.yml +21 -0
  89. data/locales/sw.yml +25 -0
  90. data/locales/ta.yml +21 -0
  91. data/locales/tr.yml +19 -0
  92. data/locales/uk.yml +25 -0
  93. data/locales/vi.yml +17 -0
  94. data/locales/zh-CN.yml +17 -0
  95. data/locales/zh-HK.yml +17 -0
  96. data/locales/zh-TW.yml +17 -0
  97. data/stylesheets/pagy.css +46 -0
  98. data/stylesheets/pagy.scss +48 -0
  99. data/stylesheets/pagy.tailwind.css +21 -0
  100. metadata +95 -67
  101. data/lib/config/pagy.rb +0 -170
  102. data/lib/javascripts/pagy.js +0 -106
  103. data/lib/locales/README.md +0 -35
  104. data/lib/locales/bg.yml +0 -22
  105. data/lib/locales/ca.yml +0 -22
  106. data/lib/locales/da.yml +0 -22
  107. data/lib/locales/de.yml +0 -22
  108. data/lib/locales/en.yml +0 -22
  109. data/lib/locales/fr.yml +0 -22
  110. data/lib/locales/id.yml +0 -20
  111. data/lib/locales/it.yml +0 -22
  112. data/lib/locales/ja.yml +0 -20
  113. data/lib/locales/km.yml +0 -19
  114. data/lib/locales/ko.yml +0 -20
  115. data/lib/locales/nb.yml +0 -22
  116. data/lib/locales/nl.yml +0 -22
  117. data/lib/locales/pl.yml +0 -24
  118. data/lib/locales/ru.yml +0 -24
  119. data/lib/locales/sv-SE.yml +0 -23
  120. data/lib/locales/sv.yml +0 -23
  121. data/lib/locales/tr.yml +0 -20
  122. data/lib/locales/utils/i18n.rb +0 -25
  123. data/lib/locales/utils/loader.rb +0 -34
  124. data/lib/locales/utils/p11n.rb +0 -80
  125. data/lib/locales/zh-CN.yml +0 -20
  126. data/lib/locales/zh-HK.yml +0 -20
  127. data/lib/locales/zh-TW.yml +0 -20
  128. data/lib/pagy/extras/foundation.rb +0 -57
  129. data/lib/pagy/extras/items.rb +0 -65
  130. data/lib/pagy/extras/materialize.rb +0 -59
  131. data/lib/pagy/extras/navs.rb +0 -38
  132. data/lib/pagy/extras/pagy_search.rb +0 -18
  133. data/lib/pagy/extras/semantic.rb +0 -55
  134. data/lib/pagy/extras/shared.rb +0 -53
  135. data/lib/pagy/extras/support.rb +0 -29
  136. data/lib/pagy/extras/uikit.rb +0 -62
  137. data/lib/templates/bootstrap_nav.html.erb +0 -24
  138. data/lib/templates/bootstrap_nav.html.haml +0 -34
  139. data/lib/templates/bootstrap_nav.html.slim +0 -34
  140. data/lib/templates/bulma_nav.html.erb +0 -24
  141. data/lib/templates/bulma_nav.html.haml +0 -32
  142. data/lib/templates/bulma_nav.html.slim +0 -32
  143. data/lib/templates/foundation_nav.html.erb +0 -24
  144. data/lib/templates/foundation_nav.html.haml +0 -34
  145. data/lib/templates/foundation_nav.html.slim +0 -34
  146. data/lib/templates/nav.html.erb +0 -22
  147. data/lib/templates/nav.html.haml +0 -30
  148. data/lib/templates/nav.html.slim +0 -29
  149. data/lib/templates/uikit_nav.html.erb +0 -15
  150. data/lib/templates/uikit_nav.html.haml +0 -28
  151. data/lib/templates/uikit_nav.html.slim +0 -28
  152. data/pagy.gemspec +0 -16
data/lib/pagy/b64.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy # :nodoc:
4
+ # Cheap Base64 specialized methods to avoid dependencies
5
+ module B64
6
+ module_function
7
+
8
+ def encode(bin)
9
+ [bin].pack('m0')
10
+ end
11
+
12
+ def decode(str)
13
+ str.unpack1('m0')
14
+ end
15
+
16
+ def urlsafe_encode(bin)
17
+ str = encode(bin)
18
+ str.chomp!('==') or str.chomp!('=')
19
+ str.tr!('+/', '-_')
20
+ str
21
+ end
22
+
23
+ def urlsafe_decode(str)
24
+ if !str.end_with?('=') && str.length % 4 != 0
25
+ str = str.ljust((str.length + 3) & ~3, '=')
26
+ str.tr!('-_', '+/')
27
+ else
28
+ str = str.tr('-_', '+/')
29
+ end
30
+ decode(str)
31
+ end
32
+ end
33
+ end
data/lib/pagy/backend.rb CHANGED
@@ -1,33 +1,44 @@
1
- # See Pagy::Backend API documentation: https://ddnexus.github.io/pagy/api/backend
2
- # encoding: utf-8
1
+ # See Pagy::Backend API documentation: https://ddnexus.github.io/pagy/docs/api/backend
3
2
  # frozen_string_literal: true
4
3
 
5
4
  class Pagy
6
- # Defines a few generic methods to paginate an ORM collection out of the box,
7
- # or any collection by overriding pagy_get_items and/or pagy_get_vars in your controller
8
-
5
+ # Define a few generic methods to paginate a collection out of the box,
6
+ # or any collection by overriding any of the `pagy_*` methods in your controller.
9
7
  # See also the extras if you need specialized methods to paginate Arrays or other collections
8
+ module Backend
9
+ private
10
10
 
11
- module Backend ; private # the whole module is private so no problem with including it in a controller
12
-
13
- # Return Pagy object and items
14
- def pagy(collection, vars={})
15
- pagy = Pagy.new(pagy_get_vars(collection, vars))
16
- return pagy, pagy_get_items(collection, pagy)
11
+ # Return Pagy object and paginated results
12
+ def pagy(collection, **vars)
13
+ vars[:count] ||= pagy_get_count(collection, vars)
14
+ vars[:limit] ||= pagy_get_limit(vars)
15
+ vars[:page] ||= pagy_get_page(vars)
16
+ pagy = Pagy.new(**vars)
17
+ [pagy, pagy_get_items(collection, pagy)]
17
18
  end
18
19
 
19
- # Sub-method called only by #pagy: here for easy customization of variables by overriding
20
- def pagy_get_vars(collection, vars)
21
- vars[:count] ||= (c = collection.count(:all)).is_a?(Hash) ? c.size : c
22
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
23
- vars
20
+ # Get the count from the collection
21
+ def pagy_get_count(collection, vars)
22
+ count_args = vars[:count_args] || DEFAULT[:count_args]
23
+ (count = collection.count(*count_args)).is_a?(Hash) ? count.size : count
24
24
  end
25
25
 
26
- # Sub-method called only by #pagy: here for easy customization of record-extraction by overriding
26
+ # Sub-method called only by #pagy: here for easy customization of fetching by overriding
27
+ # You may need to override this method for collections without offset|limit
27
28
  def pagy_get_items(collection, pagy)
28
- # This should work with ActiveRecord, Sequel, Mongoid...
29
- collection.offset(pagy.offset).limit(pagy.items)
29
+ collection.offset(pagy.offset).limit(pagy.limit)
30
30
  end
31
31
 
32
+ # Override for limit extra
33
+ def pagy_get_limit(_vars)
34
+ DEFAULT[:limit]
35
+ end
36
+
37
+ # Get the page integer from the params
38
+ # Overridable by the jsonapi extra
39
+ def pagy_get_page(vars, force_integer: true)
40
+ page = params[vars[:page_param] || DEFAULT[:page_param]]
41
+ force_integer ? (page || 1).to_i : page
42
+ end
32
43
  end
33
44
  end
@@ -0,0 +1,41 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ class Calendar # :nodoc:
6
+ # Day unit subclass
7
+ class Day < Unit
8
+ DEFAULT = { size: 31, # rubocop:disable Style/MutableConstant
9
+ ends: false,
10
+ order: :asc,
11
+ format: '%d' }
12
+
13
+ protected
14
+
15
+ # Set up the calendar variables
16
+ def assign_unit_vars
17
+ super
18
+ @initial = @starting.beginning_of_day
19
+ @final = @ending.tomorrow.beginning_of_day
20
+ @last = page_offset(@initial, @final)
21
+ @from = starting_time_for(@page)
22
+ @to = @from.tomorrow
23
+ end
24
+
25
+ # Starting time for the page
26
+ def starting_time_for(page)
27
+ @initial.days_since(time_offset_for(page))
28
+ end
29
+
30
+ def page_offset_at(time)
31
+ page_offset(@initial, time.beginning_of_day)
32
+ end
33
+
34
+ private
35
+
36
+ def page_offset(time_a, time_b) # remove in 6.0
37
+ (time_b.time - time_a.time).to_i / 1.day
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ class Calendar # :nodoc:
6
+ # Month unit subclass
7
+ class Month < Unit
8
+ DEFAULT = { size: 12, # rubocop:disable Style/MutableConstant
9
+ ends: false,
10
+ order: :asc,
11
+ format: '%b' }
12
+
13
+ protected
14
+
15
+ # Set up the calendar variables
16
+ def assign_unit_vars
17
+ super
18
+ @initial = @starting.beginning_of_month
19
+ @final = @ending.next_month.beginning_of_month
20
+ @last = (months_in(@final) - months_in(@initial))
21
+ @from = starting_time_for(@page)
22
+ @to = @from.next_month
23
+ end
24
+
25
+ # Starting time for the page
26
+ def starting_time_for(page)
27
+ @initial.months_since(time_offset_for(page))
28
+ end
29
+
30
+ def page_offset_at(time)
31
+ months_in(time.beginning_of_month) - months_in(@initial)
32
+ end
33
+
34
+ private
35
+
36
+ # Number of months in time
37
+ def months_in(time)
38
+ (time.year * 12) + time.month
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ class Calendar # :nodoc:
6
+ # Quarter unit subclass
7
+ class Quarter < Unit
8
+ DEFAULT = { size: 4, # rubocop:disable Style/MutableConstant
9
+ ends: false,
10
+ order: :asc,
11
+ format: 'Q%q' } # '%q' token
12
+
13
+ # The label for any page, with the substitution of the '%q' token
14
+ def label_for(page, opts = {})
15
+ starting_time = starting_time_for(page.to_i) # page could be a string
16
+ opts[:format] = (opts[:format] || @vars[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
17
+ localize(starting_time, opts)
18
+ end
19
+
20
+ protected
21
+
22
+ # Set up the calendar variables
23
+ def assign_unit_vars
24
+ super
25
+ @initial = @starting.beginning_of_quarter
26
+ @final = @ending.next_quarter.beginning_of_quarter
27
+ @last = (months_in(@final) - months_in(@initial)) / 3
28
+ @from = starting_time_for(@page)
29
+ @to = @from.next_quarter
30
+ end
31
+
32
+ # Starting time for the page
33
+ def starting_time_for(page)
34
+ @initial.months_since(time_offset_for(page) * 3)
35
+ end
36
+
37
+ def page_offset_at(time)
38
+ (months_in(time.beginning_of_quarter) - months_in(@initial)) / 3
39
+ end
40
+
41
+ private
42
+
43
+ # Number of months in time
44
+ def months_in(time)
45
+ (time.year * 12) + time.month
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/time'
5
+ require 'active_support/core_ext/date_and_time/calculations'
6
+ require 'active_support/core_ext/numeric/time'
7
+ require 'active_support/core_ext/integer/time'
8
+
9
+ class Pagy # :nodoc:
10
+ class Calendar < Hash # :nodoc:
11
+ # Base class for time units subclasses (Year, Quarter, Month, Week, Day)
12
+ class Unit < Pagy
13
+ attr_reader :order, :from, :to
14
+
15
+ # Merge and validate the options, do some simple arithmetic and set a few instance variables
16
+ def initialize(**vars) # rubocop:disable Lint/MissingSuper
17
+ raise InternalError, 'Pagy::Calendar::Unit is a base class; use one of its subclasses' \
18
+ if instance_of?(Pagy::Calendar::Unit)
19
+
20
+ assign_vars({ **Pagy::DEFAULT, **self.class::DEFAULT }, vars)
21
+ assign_and_check(page: 1)
22
+ assign_unit_vars
23
+ check_overflow
24
+ assign_prev_and_next
25
+ end
26
+
27
+ # The label for the current page (it can pass along the I18n gem opts when it's used with the i18n extra)
28
+ def label(opts = {})
29
+ label_for(@page, opts)
30
+ end
31
+
32
+ # The label for any page (it can pass along the I18n gem opts when it's used with the i18n extra)
33
+ def label_for(page, opts = {})
34
+ opts[:format] ||= @vars[:format]
35
+ localize(starting_time_for(page.to_i), opts) # page could be a string
36
+ end
37
+
38
+ protected
39
+
40
+ # The page that includes time
41
+ # In case of out of range time, the :fit_time option avoids the outOfRangeError
42
+ # and returns the closest page to the passed time argument (first or last page)
43
+ def page_at(time, **opts)
44
+ fit_time = time
45
+ fit_final = @final - 1
46
+ unless time.between?(@initial, fit_final)
47
+ raise OutOfRangeError.new(self, :time, "between #{@initial} and #{fit_final}", time) unless opts[:fit_time]
48
+
49
+ if time < @final
50
+ fit_time = @initial
51
+ ordinal = 'first'
52
+ else
53
+ fit_time = fit_final
54
+ ordinal = 'last'
55
+ end
56
+ warn "Pagy::Calendar#page_at: Rescued #{time} out of range by returning the #{ordinal} page."
57
+ end
58
+ offset = page_offset_at(fit_time) # offset starts from 0
59
+ @order == :asc ? offset + 1 : @last - offset
60
+ end
61
+
62
+ # Base class method for the setup of the unit variables (subclasses must implement it and call super)
63
+ def assign_unit_vars
64
+ raise VariableError.new(self, :format, 'to be a strftime format', @vars[:format]) unless @vars[:format].is_a?(String)
65
+ raise VariableError.new(self, :order, 'to be in [:asc, :desc]', @order) \
66
+ unless %i[asc desc].include?(@order = @vars[:order])
67
+
68
+ @starting, @ending = @vars[:period]
69
+ raise VariableError.new(self, :period, 'to be a an Array of min and max TimeWithZone instances', @vars[:period]) \
70
+ unless @starting.is_a?(ActiveSupport::TimeWithZone) \
71
+ && @ending.is_a?(ActiveSupport::TimeWithZone) && @starting <= @ending
72
+ end
73
+
74
+ # Apply the strftime format to the time (overridden by the i18n extra when localization is required)
75
+ def localize(time, opts)
76
+ time.strftime(opts[:format])
77
+ end
78
+
79
+ # Number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
80
+ # Used in starting_time_for(page) where page starts from 1 (e.g. page to starting_time means subtracting 1)
81
+ def time_offset_for(page)
82
+ @order == :asc ? page - 1 : @last - page
83
+ end
84
+
85
+ # Period of the active page (used internally for nested units)
86
+ def active_period
87
+ [[@starting, @from].max, [@to - 1, @ending].min] # -1 sec: include only last unit day
88
+ end
89
+
90
+ # :nocov:
91
+ # This method must be implemented by the unit subclass
92
+ def starting_time_for(*)
93
+ raise NoMethodError, 'the starting_time_for method must be implemented by the unit subclass'
94
+ end
95
+
96
+ # This method must be implemented by the unit subclass
97
+ def page_offset_at(*)
98
+ raise NoMethodError, 'the page_offset_at method must be implemented by the unit subclass'
99
+ end
100
+ # :nocov:
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,39 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ class Calendar # :nodoc:
6
+ # Week unit subclass
7
+ class Week < Unit
8
+ DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
9
+ format: '%Y-%W' }
10
+
11
+ protected
12
+
13
+ # Set up the calendar variables
14
+ def assign_unit_vars
15
+ super
16
+ @initial = @starting.beginning_of_week
17
+ @final = @ending.next_week.beginning_of_week
18
+ @last = page_offset(@initial, @final)
19
+ @from = starting_time_for(@page)
20
+ @to = @from.next_week
21
+ end
22
+
23
+ # Starting time for the page
24
+ def starting_time_for(page)
25
+ @initial.weeks_since(time_offset_for(page))
26
+ end
27
+
28
+ def page_offset_at(time)
29
+ page_offset(@initial, time.beginning_of_week)
30
+ end
31
+
32
+ private
33
+
34
+ def page_offset(time_a, time_b) # remove in 6.0
35
+ (time_b.time - time_a.time).to_i / 1.week
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ class Pagy # :nodoc:
5
+ class Calendar # :nodoc:
6
+ # Year unit subclass
7
+ class Year < Unit
8
+ DEFAULT = { size: 10, # rubocop:disable Style/MutableConstant
9
+ ends: false,
10
+ order: :asc,
11
+ format: '%Y' }
12
+
13
+ protected
14
+
15
+ # Set up the calendar variables
16
+ def assign_unit_vars
17
+ super
18
+ @initial = @starting.beginning_of_year
19
+ @final = @ending.next_year.beginning_of_year
20
+ @last = @final.year - @initial.year
21
+ @from = starting_time_for(@page)
22
+ @to = @from.next_year
23
+ end
24
+
25
+ # Starting time for the page
26
+ def starting_time_for(page)
27
+ @initial.years_since(time_offset_for(page))
28
+ end
29
+
30
+ def page_offset_at(time)
31
+ time.beginning_of_year.year - @initial.year
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,84 @@
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../pagy'
5
+ require_relative 'calendar/unit'
6
+
7
+ class Pagy # :nodoc:
8
+ # Calendar class
9
+ class Calendar < Hash
10
+ # Specific out of range error
11
+ class OutOfRangeError < VariableError; end
12
+
13
+ # List of units in desc order of duration. It can be used for custom units.
14
+ UNITS = %i[year quarter month week day] # rubocop:disable Style/MutableConstant
15
+
16
+ class << self
17
+ private
18
+
19
+ # Create a unit subclass instance by using the unit name (internal use)
20
+ def create(unit, **vars)
21
+ raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
22
+
23
+ name = unit.to_s
24
+ name[0] = name[0].capitalize
25
+ Object.const_get("Pagy::Calendar::#{name}").new(**vars)
26
+ end
27
+
28
+ # Return calendar, from, to
29
+ def init(...)
30
+ new.send(:init, ...)
31
+ end
32
+ end
33
+
34
+ # Return the current time of the smallest time unit shown
35
+ def showtime
36
+ self[@units.last].from
37
+ end
38
+
39
+ private
40
+
41
+ # Create the calendar
42
+ def init(conf, period, params)
43
+ @conf = Marshal.load(Marshal.dump(conf)) # store a copy
44
+ @units = Calendar::UNITS & @conf.keys # get the units in time length desc order
45
+ raise ArgumentError, 'no calendar unit found in pagy_calendar @configuration' if @units.empty?
46
+
47
+ @period = period
48
+ @params = params
49
+ @page_param = conf[:pagy][:page_param] || DEFAULT[:page_param]
50
+ # set all the :page_param vars for later deletion
51
+ @units.each { |unit| conf[unit][:page_param] = :"#{unit}_#{@page_param}" }
52
+ calendar = {}
53
+ object = nil
54
+ @units.each_with_index do |unit, index|
55
+ params_to_delete = @units[(index + 1), @units.size].map { |sub| conf[sub][:page_param] } + [@page_param]
56
+ conf[unit][:params] = ->(up) { up.except(*params_to_delete.map(&:to_s)) }
57
+ conf[unit][:period] = object&.send(:active_period) || @period
58
+ conf[unit][:page] = @params[:"#{unit}_#{@page_param}"] # requested page
59
+ # :nocov:
60
+ conf[unit][:counts] = yield(unit, conf[unit][:period]) if block_given? # nocov doesn't need to fail block_given?
61
+ # :nocov:
62
+ calendar[unit] = object \
63
+ = Calendar.send(:create, unit, **conf[unit])
64
+ end
65
+ [replace(calendar), object.from, object.to]
66
+ end
67
+
68
+ # Return the calendar object at time
69
+ def calendar_at(time, **opts)
70
+ conf = Marshal.load(Marshal.dump(@conf))
71
+ page_params = {}
72
+ @units.inject(nil) do |object, unit|
73
+ conf[unit][:period] = object&.send(:active_period) || @period
74
+ conf[unit][:page] = page_params[:"#{unit}_#{@page_param}"] \
75
+ = Calendar.send(:create, unit, **conf[unit]).send(:page_at, time, **opts)
76
+ conf[unit][:params] ||= {}
77
+ conf[unit][:params].merge!(page_params)
78
+ Calendar.send(:create, unit, **conf[unit])
79
+ end
80
+ end
81
+ end
82
+ # Require the subclass files in UNITS (no custom unit at this point yet)
83
+ Calendar::UNITS.each { |unit| require "pagy/calendar/#{unit}" }
84
+ end
@@ -0,0 +1,23 @@
1
+ # See Pagy::Console API documentation: https://ddnexus.github.io/pagy/docs/api/console
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../pagy' # so you can require just the extra in the console
5
+ require_relative 'extras/standalone'
6
+
7
+ class Pagy
8
+ # Provide a ready to use pagy environment when included in irb/rails console
9
+ module Console
10
+ # Include Backend, Frontend and set the default URL
11
+ def self.included(main)
12
+ main.include(Backend)
13
+ main.include(Frontend)
14
+ DEFAULT[:url] = 'http://www.example.com/subdir'
15
+ end
16
+
17
+ # Require the extras passed as arguments
18
+ def pagy_extras(*extras)
19
+ extras.each { |extra| require "pagy/extras/#{extra}" }
20
+ puts "Required extras: #{extras.map(&:inspect).join(', ')}"
21
+ end
22
+ end
23
+ end
@@ -1,33 +1,38 @@
1
- # encoding: utf-8
1
+ # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/countless
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pagy'
5
-
6
- class Pagy
7
-
4
+ class Pagy # :nodoc:
5
+ # No need to know the count to paginate
8
6
  class Countless < Pagy
9
-
10
7
  # Merge and validate the options, do some simple arithmetic and set a few instance variables
11
- def initialize(vars={})
12
- @vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars (can be overridden)
13
- { items:1, outset:0, page:1 }.each do |k,min| # validate instance variables
14
- (@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \
15
- or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
16
- end
17
- @offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset)
8
+ def initialize(**vars) # rubocop:disable Lint/MissingSuper
9
+ assign_vars(DEFAULT, vars)
10
+ assign_and_check(page: 1, outset: 0)
11
+ assign_limit
12
+ assign_offset
18
13
  end
19
14
 
20
- # Finalize the instance variables based on the fetched items
21
- def finalize(fetched)
22
- fetched == 0 && @page > 1 and raise(OverflowError.new(self), "page #{@page} got no items")
23
- @pages = @last = (fetched > @items ? @page + 1 : @page) # set the @pages and @last
24
- @items = fetched if fetched < @items && fetched > 0 # adjust items for last non-empty page
25
- @from = fetched == 0 ? 0 : @offset+1 - @outset # page begins from item
26
- @to = fetched == 0 ? 0 : @offset + @items - @outset # page ends to item
27
- @prev = (@page-1 unless @page == 1) # nil if no prev page
28
- @next = @page == @last ? (1 if @vars[:cycle]) : @page+1 # nil if no next page, 1 if :cycle
15
+ # Finalize the instance variables based on the fetched size
16
+ def finalize(fetched_size)
17
+ raise OverflowError.new(self, :page, "to be < #{@page}", @page) if fetched_size.zero? && @page > 1
18
+
19
+ @last = fetched_size > @limit ? @page + 1 : @page
20
+ @last = @vars[:max_pages] if @vars[:max_pages] && @last > @vars[:max_pages]
21
+ check_overflow
22
+ @in = [fetched_size, @limit].min
23
+ @from = @in.zero? ? 0 : @offset - @outset + 1
24
+ @to = @offset - @outset + @in
25
+ assign_prev_and_next
29
26
  self
30
27
  end
28
+ end
31
29
 
30
+ module SeriesOverride # :nodoc:
31
+ # Override the original series.
32
+ # Return nil if :countless_minimal is enabled
33
+ def series(**)
34
+ super unless @vars[:countless_minimal]
35
+ end
32
36
  end
37
+ prepend SeriesOverride
33
38
  end
@@ -1,22 +1,25 @@
1
- class Pagy
1
+ # frozen_string_literal: true
2
2
 
3
+ class Pagy
4
+ # Generic variable error
3
5
  class VariableError < ArgumentError
4
- attr_reader :pagy
5
-
6
- def initialize(pagy)
7
- @pagy = pagy
8
- end
6
+ attr_reader :pagy, :variable, :value
9
7
 
10
- def variable
11
- message =~ /expected :([\w]+)/
12
- $1.to_sym if $1
13
- end
14
-
15
- def value
16
- pagy.vars[variable]
8
+ # Set the variables and prepare the message
9
+ def initialize(pagy, variable, description, value)
10
+ @pagy = pagy
11
+ @variable = variable
12
+ @value = value
13
+ super("expected :#{@variable} #{description}; got #{@value.inspect}")
17
14
  end
18
15
  end
19
16
 
17
+ # Specific overflow error
20
18
  class OverflowError < VariableError; end
21
19
 
20
+ # I18n configuration error
21
+ class I18nError < StandardError; end
22
+
23
+ # Generic internal error
24
+ class InternalError < StandardError; end
22
25
  end
@@ -1,21 +1,18 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/arel
2
- # encoding: utf-8
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/arel
3
2
  # frozen_string_literal: true
4
3
 
5
- class Pagy
6
- module Backend ; private
4
+ class Pagy # :nodoc:
5
+ # Better performance of grouped ActiveRecord collections
6
+ module ArelExtra
7
+ private
7
8
 
8
- def pagy_arel(collection, vars={})
9
- pagy = Pagy.new(pagy_arel_get_vars(collection, vars))
10
- return pagy, pagy_get_items(collection, pagy)
11
- end
12
-
13
- def pagy_arel_get_vars(collection, vars)
9
+ # Return Pagy object and paginated collection/results
10
+ def pagy_arel(collection, **vars)
14
11
  vars[:count] ||= pagy_arel_count(collection)
15
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
16
- vars
12
+ pagy(collection, **vars)
17
13
  end
18
14
 
15
+ # Count using Arel when grouping
19
16
  def pagy_arel_count(collection)
20
17
  if collection.group_values.empty?
21
18
  # COUNT(*)
@@ -23,9 +20,9 @@ class Pagy
23
20
  else
24
21
  # COUNT(*) OVER ()
25
22
  sql = Arel.star.count.over(Arel::Nodes::Grouping.new([]))
26
- collection.unscope(:order).limit(1).pluck(sql).first.to_i
23
+ collection.unscope(:order).pick(sql).to_i
27
24
  end
28
25
  end
29
-
30
26
  end
27
+ Backend.prepend ArelExtra
31
28
  end