Skip to content

Commit 530cb24

Browse files
committed
all the exceptions are namespaced and can return more information for easier rescue of special cases
1 parent 1a06640 commit 530cb24

12 files changed

Lines changed: 136 additions & 28 deletions

File tree

β€Ždocs/api/pagy.mdβ€Ž

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ They are all integers:
8181

8282
### Other Variables
8383

84-
| Variable | Description | Default |
85-
|:------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------|
86-
| `:size` | the size of the page links to show: array of initial pages, before current page, after current page, final pages. _(see also [Control the page links](../how-to.md#controlling-the-page-links))_ | `[1,4,4,1]` |
87-
| `:page_param` | the name of the page param name used in the url. _(see [Customizing the page param](../how-to.md#customizing-the-page-param))_ | `:page` |
88-
| `:params` | the arbitrary param hash to add to the url. _(see [Customizing the params](../how-to.md#customizing-the-params))_ | `{}` |
89-
| `:anchor` | the arbitrary anchor string (including the "#") to add to the url. _(see [Customizing the params](../how-to.md#customizing-the-params))_ | `""` |
90-
| `:link_extra` | the extra attributes string (formatted as a valid HTML attribute/value pairs) added to the page links _(see [Customizing the link attributes](../how-to.md#customizing-the-link-attributes))_ | `""` |
91-
| `:i18n_key` | the i18n key to lookup the `item_name` that gets interpolated in a few helper outputs _(see [Customizing the item name](../how-to.md#customizing-the-item-name))_ | `"pagy.item_name"` |
92-
| `:cycle` | enable cycling/circular/infinite pagination: `true` sets `next` to `1` when the current page is the last page | `false` |
84+
| Variable | Description | Default |
85+
|:--------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------|
86+
| `:size` | the size of the page links to show: array of initial pages, before current page, after current page, final pages. _(see also [Control the page links](../how-to.md#controlling-the-page-links))_ | `[1,4,4,1]` |
87+
| `:page_param` | the name of the page param name used in the url. _(see [Customizing the page param](../how-to.md#customizing-the-page-param))_ | `:page` |
88+
| `:params` | the arbitrary param hash to add to the url. _(see [Customizing the params](../how-to.md#customizing-the-params))_ | `{}` |
89+
| `:anchor` | the arbitrary anchor string (including the "#") to add to the url. _(see [Customizing the params](../how-to.md#customizing-the-params))_ | `""` |
90+
| `:link_extra` | the extra attributes string (formatted as a valid HTML attribute/value pairs) added to the page links _(see [Customizing the link attributes](../how-to.md#customizing-the-link-attributes))_ | `""` |
91+
| `:i18n_key` | the i18n key to lookup the `item_name` that gets interpolated in a few helper outputs _(see [Customizing the item name](../how-to.md#customizing-the-item-name))_ | `"pagy.item_name"` |
92+
| `:cycle` | enable cycling/circular/infinite pagination: `true` sets `next` to `1` when the current page is the last page | `false` |
9393

9494
There is no specific validation for non-instance variables.
9595

@@ -150,3 +150,23 @@ Which means:
150150
- the `series` array contains always at least the page #`1`, which for a single page is also the current page, thus a string. With `size: []` series returns `[]`
151151
- `from` and `to` of an empty page are both `0`
152152
- `prev` and `next` of a single page (not necessary an empty one) are both `nil` (i.e. there is no other page)
153+
154+
## Exceptions
155+
156+
### Pagy::VariableError
157+
158+
A subclass of `ArgumentError` that offers a few informations for easy exception handling when some of the variable passed to the constructor is not valid.
159+
160+
```ruby
161+
rescue Pagy::VariableError => e
162+
e.pagy #=> the pagy object that raised the exception
163+
e.variable #=> the offending variable symbol (e.g. :page)
164+
e.value #=> the value of the offending variable (e.g -3)
165+
end
166+
```
167+
168+
Mostly useful if you want to rescue from bad user input (e.g. illegal URL manipulation).
169+
170+
### Pagy::OverflowError
171+
172+
A subclass of `Pagy::VariableError`: it is raised when the `:page` variable exceed the maximum possible value calculated for the `:count`, i.e. the `:page` variable is in the correct range, but it's not consistent with the current `:count`. That may happen when the `:count` has been reduced after a page link has been generated (e.g. some record has been just removed from the DB). See also the [overflow](../extras/overflow.md) extra.

β€Ždocs/extras/navs.mdβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pagy, records = pagy(collection, steps: false )
8383

8484
The above statement means that from `0` to `540` pixels width, Pagy will use the `[2,3,3,2]` size, from `540` to `720` it will use the `[3,5,5,3]` size and over `720` it will use the `[5,7,7,5]` size. (Read more about the `:size` variable in the [How to control the page links](../how-to.md#controlling-the-page-links) section).
8585

86-
**IMPORTANT**: You can set any number of steps with any arbitrary width/size. The only requirement is that the `:steps` hash must contain always the `0` width or an `ArgumentError` exception will be raises.
86+
**IMPORTANT**: You can set any number of steps with any arbitrary width/size. The only requirement is that the `:steps` hash must contain always the `0` width or a `Pagy::VariableError` exception will be raised.
8787

8888
#### Setting the right sizes
8989

β€Žlib/locales/utils/loader.rbβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
arg[:filepath] ||= Pagy.root.join('locales', "#{arg[:locale]}.yml")
2424
arg[:pluralize] ||= plurals[arg[:locale]]
2525
hash = YAML.load(File.read(arg[:filepath], encoding: 'UTF-8')) #rubocop:disable Security/YAMLLoad
26-
hash.key?(arg[:locale]) or raise ArgumentError, %(Pagy::I18n.load: :locale "#{arg[:locale]}" not found in :filepath "#{arg[:filepath].inspect}")
26+
hash.key?(arg[:locale]) or raise VariableError, %(expected :locale "#{arg[:locale]}" not found in :filepath "#{arg[:filepath].inspect}")
2727
i18n[arg[:locale]] = [flatten.call(hash[arg[:locale]]), arg[:pluralize]]
2828
end
2929
end

β€Žlib/pagy.rbβ€Ž

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
class Pagy ; VERSION = '3.3.0'
88

9-
class OverflowError < StandardError; attr_reader :pagy; def initialize(pagy) @pagy = pagy end; end
10-
119
# Root pathname to get the path of Pagy files like templates or dictionaries
1210
def self.root; @root ||= Pathname.new(__FILE__).dirname.freeze end
1311

@@ -21,7 +19,7 @@ def initialize(vars)
2119
@vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars
2220
{ count:0, items:1, outset:0, page:1 }.each do |k,min| # validate instance variables
2321
(@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \
24-
or raise(ArgumentError, "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
22+
or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
2523
end
2624
@pages = @last = [(@count.to_f / @items).ceil, 1].max # cardinal and ordinal meanings
2725
@page <= @last or raise(OverflowError.new(self), "expected :page in 1..#{@last}; got #{@page.inspect}")
@@ -36,7 +34,7 @@ def initialize(vars)
3634
# Return the array of page numbers and :gap items e.g. [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
3735
def series(size=@vars[:size])
3836
(series = []) and size.empty? and return series
39-
4.times{|i| (size[i]>=0 rescue nil) or raise(ArgumentError, "expected 4 items >= 0 in :size; got #{size.inspect}")}
37+
4.times{|i| (size[i]>=0 rescue nil) or raise(VariableError.new(self), "expected 4 items >= 0 in :size; got #{size.inspect}")}
4038
[*0..size[0], *@page-size[1]..@page+size[2], *@last-size[3]+1..@last+1].sort!.each_cons(2) do |a, b|
4139
if a<0 || a==b || a>@last # skip out of range and duplicates
4240
elsif a+1 == b; series.push(a) # no gap -> no additions
@@ -51,3 +49,4 @@ def series(size=@vars[:size])
5149

5250
require 'pagy/backend'
5351
require 'pagy/frontend'
52+
require 'pagy/exceptions'

β€Žlib/pagy/countless.rbβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def initialize(vars={})
1212
@vars = VARS.merge(vars.delete_if{|_,v| v.nil? || v == '' }) # default vars + cleaned vars (can be overridden)
1313
{ items:1, outset:0, page:1 }.each do |k,min| # validate instance variables
1414
(@vars[k] && instance_variable_set(:"@#{k}", @vars[k].to_i) >= min) \
15-
or raise(ArgumentError, "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
15+
or raise(VariableError.new(self), "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
1616
end
1717
@offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset)
1818
end

β€Žlib/pagy/exceptions.rbβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Pagy
2+
3+
class VariableError < ArgumentError
4+
attr_reader :pagy
5+
6+
def initialize(pagy)
7+
@pagy = pagy
8+
end
9+
10+
def variable
11+
message =~ /expected :([\w]+)/
12+
$1.to_sym if $1
13+
end
14+
15+
def value
16+
pagy.vars[variable]
17+
end
18+
end
19+
20+
class OverflowError < VariableError; end
21+
22+
end

β€Žlib/pagy/extras/overflow.rbβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def initialize_with_overflow(vars)
2828
@prev = @last # prev relative to the actual page
2929
extend(Series) # special series for :empty_page
3030
else
31-
raise ArgumentError, "expected :overflow variable in [:last_page, :empty_page, :exception]; got #{@vars[:overflow].inspect}"
31+
raise VariableError.new(self), "expected :overflow variable in [:last_page, :empty_page, :exception]; got #{@vars[:overflow].inspect}"
3232
end
3333
end
3434
alias_method :initialize, :initialize_with_overflow
@@ -62,7 +62,7 @@ def finalize_with_overflow(items)
6262
@vars[:size] = [] # no page in the series
6363
self
6464
else
65-
raise ArgumentError, "expected :overflow variable in [:empty_page, :exception]; got #{@vars[:overflow].inspect}"
65+
raise VariableError.new(self), "expected :overflow variable in [:empty_page, :exception]; got #{@vars[:overflow].inspect}"
6666
end
6767
end
6868
alias_method :finalize, :finalize_with_overflow

β€Žlib/pagy/extras/shared.rbβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Pagy
1919
# Notice: if :steps is false it will use the single {0 => @vars[:size]} size
2020
def sequels
2121
steps = @vars[:steps] || {0 => @vars[:size]}
22-
steps.key?(0) or raise(ArgumentError, "expected :steps to define the 0 width; got #{steps.inspect}")
22+
steps.key?(0) or raise(VariableError.new(self), "expected :steps to define the 0 width; got #{steps.inspect}")
2323
sequels = {}; steps.each {|width, size| sequels[width.to_s] = series(size)}; sequels
2424
end
2525

β€Žtest/pagy/exceptions_test.rbβ€Ž

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# encoding: utf-8
2+
# frozen_string_literal: true
3+
4+
require_relative '../test_helper'
5+
6+
7+
describe Pagy::VariableError do
8+
9+
describe "#variable and #value" do
10+
11+
it 'raises for non overflow pages' do
12+
begin
13+
Pagy.new(count: 1, page: 0)
14+
rescue Pagy::VariableError => e
15+
e.variable.must_equal :page
16+
e.value.must_equal 0
17+
end
18+
19+
begin
20+
Pagy.new(count: 1, page: -10)
21+
rescue Pagy::VariableError => e
22+
e.variable.must_equal :page
23+
e.value.must_equal(-10)
24+
end
25+
26+
begin
27+
Pagy.new(count: 1, page: 'string')
28+
rescue Pagy::VariableError => e
29+
e.variable.must_equal :page
30+
e.value.must_equal 'string'
31+
end
32+
end
33+
34+
it 'raises for other variables' do
35+
begin
36+
Pagy.new(count: -10)
37+
rescue Pagy::VariableError => e
38+
e.variable.must_equal :count
39+
e.value.must_equal(-10)
40+
end
41+
begin
42+
Pagy.new(count: 'string')
43+
rescue Pagy::VariableError => e
44+
e.variable.must_equal :count
45+
e.value.must_equal 'string'
46+
end
47+
end
48+
49+
it 'rescues as ArgumentError (backward compatible)' do
50+
begin
51+
Pagy.new(count: 1, page: -10)
52+
rescue ArgumentError => e
53+
e.variable.must_equal :page
54+
e.value.must_equal(-10)
55+
end
56+
57+
begin
58+
Pagy.new(count: 'string')
59+
rescue ArgumentError => e
60+
e.variable.must_equal :count
61+
e.value.must_equal 'string'
62+
end
63+
end
64+
65+
end
66+
67+
end

β€Žtest/pagy/extras/overflow_test.rbβ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@
6565
pagy.prev.must_equal pagy.last
6666
end
6767

68-
it 'raises ArgumentError' do
69-
proc { Pagy.new(vars.merge(overflow: :unknown)) }.must_raise ArgumentError
70-
proc { Pagy::Countless.new(countless_vars.merge(overflow: :unknown)).finalize(0) }.must_raise ArgumentError
68+
it 'raises Pagy::VariableError' do
69+
proc { Pagy.new(vars.merge(overflow: :unknown)) }.must_raise Pagy::VariableError
70+
proc { Pagy::Countless.new(countless_vars.merge(overflow: :unknown)).finalize(0) }.must_raise Pagy::VariableError
7171
end
7272

7373
end

0 commit comments

Comments
 (0)