pagy 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60384daf0fd49d4815f49d4ab0bf65840835bb42f7c9c38979fae883f9e0c4c5
4
- data.tar.gz: 4aaa58eba02a590254ecf82d80f750aa4cd0b3bf7da1d745f92d8a7125c19fc9
3
+ metadata.gz: a984f9eb44e5f2d637f92e5b5317edbff0691c2b2d4e89d8bf02f2e1a11939b8
4
+ data.tar.gz: 01dc6a1f9ca9bba43400f00a645a47c02cf4aa11868705193a9b4c33a50e1811
5
5
  SHA512:
6
- metadata.gz: 3006bd929cce65f58b5d0fd4c512e791fc939de6661055359c918f035f981645ce705d3d5e02f55aa7c43816d309000ee10991c54b89144768f986f6e6f18c0d
7
- data.tar.gz: 4ad8a43b6138a671e84e074155e60f80713cd68a41f71fab6ac69d5a88bc5a48fbac0b49cb0a9b50e6c15b416c72373ceafe01e9bc841f9fe2b72965deb9dfaa
6
+ metadata.gz: b2ed0782436c442bb3127598cb1f8b8aaeb7afaf373836933e61a93b834bc91125a1f44fb34fad18b362a989c59b2c1ba3683483cf2098b36657be75420f3fc3
7
+ data.tar.gz: e7399e4568cf8fbfa537a4a193c928d0810ce47ea4d5c877765d5669d077618e7ab1e715d9892c7bf51e8e827a06b897afef12d417c2994f7be6f65c69cd7fe1
data/README.md CHANGED
@@ -1,30 +1,92 @@
1
1
  # Pagy
2
2
 
3
- Dead simple pagination in pure ruby. Easy, fast and very light. No restrictions imposed: use it with any MVC framework, any ORM, any DB type, any templating system or none at all. Use the built-in templates or create yours the way you want.
3
+ Pagy is the ultimate pagination gem that outperforms the others in each and every benchmark and comparison.
4
4
 
5
- ## Alpha Version!
5
+ ### Benchmarks
6
6
 
7
- This gem is still missing documentation. TBD... soon ;).
7
+ The best way to quickly get an idea about its features is comparing it to the other well known gems.
8
8
 
9
- ## Installation
9
+ The values shown in the charts below have been recorded while each gem was producing the exact same output: same environment conditions, same task, just different gems _(see the complete [Gems Comparison](http://ddnexus.github.io/pagination-comparison/gems.html))_
10
10
 
11
- Add this line to your application's Gemfile:
11
+ #### Pagy is a lot faster
12
+
13
+ ![IPS Chart](images/ips-chart.png)
14
+
15
+ #### Pagy uses a lot less memory
16
+
17
+ ![Memory Chart](images/memory-chart.png)
18
+
19
+ #### Pagy is a lot simpler
20
+
21
+ ![Objects Chart](images/objects-chart.png)
22
+
23
+ #### Pagy is a lot more efficient
24
+
25
+ ![Efficiency Table](images/efficiency-table.png)
26
+
27
+ _The [IPS/Kb ratio](http://ddnexus.github.io/pagination-comparison/gems.html#efficiency-ratio) is calculated out of speed (IPS) and Memory (Kb): it shows how well each gem uses any Kb of memory it allocates/consumes._
28
+
29
+ #### Pagy does not suffer the typical limitations of the other gems:
30
+
31
+ - it works with collections/scopes that already used `limit` and `offset`
32
+ - it works with both helpers or templates (your choice)
33
+ - it raises real `Pagy::OutOfRangeError` exceptions that you can rescue from
34
+ - it does not impose any difficult-to-override logic or output
35
+
36
+ ### Features
37
+
38
+ #### Straightforward code
39
+
40
+ - Pagy is just ~120 lines of simple ruby, organized in 3 flat modules very easy to understand and use
41
+ - it produces its own HTML, URLs, pluralization and interpolation with its own specialized and fast code
42
+ - 100% of its methods are public API, accessible and overridable **right where you use them** (no need of monkey patching or subclassing)
43
+
44
+ #### Totally agnostic
45
+
46
+ - it doesn't need to know anything about your models, ORM or Storage, so it doesn't add any code to them
47
+ - it works with all kind of collections, even pre-paginated, records, Arrays, JSON data... and just whatever you can count
48
+ - it works with all Rack frameworks (Rails, Sinatra, Padrino, ecc.) out of the box
49
+ - it works with any possible non-Rack envoronment by just overriding one or two one-liner methods
50
+
51
+ #### Simple Usage
52
+
53
+ You can use pagy in a quite familiar way:
54
+
55
+ Paginate your collection in some controller:
12
56
 
13
57
  ```ruby
14
- gem 'pagy'
58
+ @pagy, @records = pagy(Product.some_scope)
59
+ ```
60
+
61
+ Render the navigation links with a super-fast helper in some view:
62
+
63
+ ```HTML+ERB
64
+ <%= pagy_nav(@pagy) %>
65
+ ```
66
+
67
+ Or - if you prefer - render the navigation links with a template:
68
+
69
+ ```HTML+ERB
70
+ <%= render 'pagy/nav', locals: {pagy: @pagy} %>
15
71
  ```
16
72
 
17
- And then execute:
73
+ ## Support, Comments and Feature Requests
74
+
75
+ [![Join the chat at https://gitter.im/ruby-pagy/Lobby](https://badges.gitter.im/ruby-pagy/Lobby.svg)](https://gitter.im/ruby-pagy/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
18
76
 
19
- $ bundle
77
+ ## Useful Links
20
78
 
21
- Or install it yourself as:
79
+ - [Documentation](https://ddnexus.github.io/pagy/index)
80
+ - [Pagination Comparison App](http://github.com/ddnexus/pagination-comparison)
22
81
 
23
- $ gem install pagy
82
+ ## Help Wanted
24
83
 
25
- ## Contributing
84
+ Pagy is a fresh project and your help would be great. If you like it, you have a few options to contribute:
26
85
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/ddnexus/pagy.
86
+ - write a tutorial or a post or even just a tweet (pagy is young and needs to be known)
87
+ - write a "How To" topic (the documentation is covering the basics and there is a lot of space for additions)
88
+ - submit a pull request to make pagy even faster, save more memory or improve its usability
89
+ - create an issue if anything should be improved/fixed
28
90
 
29
91
  ## License
30
92
 
Binary file
Binary file
Binary file
Binary file
data/lib/locales/pagy.yml CHANGED
@@ -1,17 +1,10 @@
1
- # Pagy uses an internal I18n-like :pagy_t helper that lookup
2
- # into this file at Pagy.root.join('locale', 'pagy.yml')
3
- # If you want to customize it you should set:
4
- # Pagy::Opts.i18n_file = 'path/to/your/file.yaml'
5
-
6
- # If you need to use this method with pluralization different than english
7
- # (i.e. not 'zero', 'one', 'other' plurals) then you should define the
8
- # Pagy::Opts.i18n_plurals proc to return the plural key based on the passed count.
1
+ # Standard pagy dictionary
9
2
 
10
3
  en:
11
4
  pagy:
12
5
  nav:
13
- prev: "&lsaquo; Prev"
14
- next: "Next &rsaquo;"
6
+ prev: "&lsaquo;&nbsp;Prev"
7
+ next: "Next&nbsp;&rsaquo;"
15
8
  gap: "&hellip;"
16
9
  info:
17
10
  single_page:
@@ -19,7 +12,7 @@ en:
19
12
  one: "Displaying <b>1</b> %{item_name}"
20
13
  other: "Displaying <b>all %{count}</b> %{item_name}"
21
14
  multiple_pages: "Displaying %{item_name} <b>%{from}-%{to}</b> of <b>%{count}</b> in total"
22
- item:
15
+ item_name:
23
16
  zero: "items"
24
17
  one: "item"
25
18
  other: "items"
data/lib/pagy.rb CHANGED
@@ -1,61 +1,57 @@
1
- require 'pathname'
2
- require 'ostruct'
1
+ # See Pagy API documentation: https://ddnexus.github.io/pagy/api/pagy
3
2
 
4
- # This class takes a few integers (such as collection-count, page-number,
5
- # items-per-page-limit, etc.), does some simple aritmetic and creates a very
6
- # small object (~3k) with all is needed for pagination and navigation.
7
- # Notice that it doesn't actually do any pagination, nor navigation... that is
8
- # done with a few helpers in the Pagy::Backend and Pagy::Frontend modules.
3
+ require 'pathname'
9
4
 
10
- class Pagy ; VERSION = '0.4.2'
5
+ class Pagy ; VERSION = '0.5.0'
11
6
 
12
7
  autoload :Backend, 'pagy/backend'
13
8
  autoload :Frontend, 'pagy/frontend'
14
9
 
15
10
  class OutOfRangeError < StandardError; end
16
11
 
17
- # root pathname to get the path of pagy files like templates or locales
12
+ # root pathname to get the path of pagy files like templates or dictionaries
18
13
  def self.root; Pathname.new(__FILE__).dirname end
19
14
 
20
- # default options
21
- # limit: max items per page: it gets adjusted for the last page,
22
- # so it will pull the right items if the collection was pre-limit(ed)
23
- # offset: the initial offset of the whole collection before pagination
24
- # set it only if the collection was pre-offset(ted): it gets added to the final offset
25
- # initial/final: max pages to show from the first/last page
26
- # before/after: max pages before/after the current page
27
- Opts = OpenStruct.new(limit:20, offset:0, initial:1, before:4, after:4, final:1)
15
+ # default core vars
16
+ VARS = { items:20, outset:0, initial:1, before:4, after:4, final:1 }
17
+
18
+ # default I18n vars
19
+ I18N = { file: Pagy.root.join('locales', 'pagy.yml').to_s, plurals: -> (c) {c==0 && 'zero' || c==1 && 'one' || 'other'} }
28
20
 
29
- attr_reader :count, :page, :limit, :opts, :pages, :last, :offset, :from, :to, :prev, :next, :series
21
+
22
+ attr_reader :count, :page, :items, :vars, :pages, :last, :offset, :from, :to, :prev, :next
30
23
 
31
24
  # merge and validate the options, do some simple aritmetic and set the instance variables
32
- def initialize(opts)
33
- @opts = Opts.to_h.merge!(opts) # global opts + instance opts (bang faster)
34
- @opts[:page] = (@opts[:page]||1).to_i # set page to 1 if nil
35
- [:count, :limit, :offset, :initial, :before, :page, :after, :final].each do |k|
36
- @opts[k] >= 0 rescue nil || raise(ArgumentError, "expected #{k} >= 0; got #{@opts[k].inspect}")
37
- instance_variable_set(:"@#{k}", @opts.delete(k)) # set all the metrics variables
25
+ def initialize(vars)
26
+ @vars = VARS.merge(vars) # default vars + instance vars
27
+ @vars[:page] = (@vars[:page]||1).to_i # set page to 1 if nil
28
+ {count:0, items:1, outset:0, initial:0, before:0, page:1, after:0, final:0}.each do |k,min|
29
+ @vars[k] >= min rescue nil || raise(ArgumentError, "expected :#{k} >= #{min}; got #{@vars[k].inspect}")
30
+ instance_variable_set(:"@#{k}", @vars.delete(k)) # set all the core variables
38
31
  end
39
- @pages = @last = [(@count.to_f / @limit).ceil, 1].max # cardinal and ordinal meanings
32
+ @pages = @last = [(@count.to_f / @items).ceil, 1].max # cardinal and ordinal meanings
40
33
  (1..@last).cover?(@page) || raise(OutOfRangeError, "expected :page in 1..#{@last}; got #{@page.inspect}")
41
- @offset += @limit * (@page - 1) # initial offset + offset for pagination
42
- @limit = @count % @limit if @page == @last # adjust limit for last page (for pre-limit(ed) collections)
43
- @from = @count == 0 ? 0 : @offset+1 # page begins from item
44
- @to = @offset + @limit # page ends to item
45
- @prev = (@page-1 unless @page == 1) # nil if no prev page
46
- @next = (@page+1 unless @page == @last) # nil if no next page
47
- @series = [] # e.g. [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
48
- all = (0..@last+1) # page range with boundaries
49
- around = ([@page-@before, 1].max .. [@page+@after, @last].min).to_a # before..after pages
50
- row = (all.first(@initial+1) | around | all.last(@final+1)).sort # row of pages with boundaries
51
- row.each_cons(2) do |a, b| # loop in consecutive pairs
52
- if a+1 == b ; @series.push(a) # no gap -> no additions
53
- elsif a+2 == b ; @series.push(a, a+1) # 1 page gap -> fill with missing page
54
- else @series.push(a, :gap) # n page gap -> add :gap
55
- end # skip the end-boundary (last+1)
34
+ @offset = @items * (@page - 1) + @outset # pagination offset + outset (initial offset)
35
+ @items = @count % @items if @page == @last # adjust items for last page
36
+ @from = @count == 0 ? 0 : @offset+1 - @outset # page begins from item
37
+ @to = @offset + @items - @outset # page ends to item
38
+ @prev = (@page-1 unless @page == 1) # nil if no prev page
39
+ @next = (@page+1 unless @page == @last) # nil if no next page
40
+ end
41
+
42
+ # return the array of page numbers and :gap items e.g. [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
43
+ def series
44
+ @series ||= [].tap do |series|
45
+ [*0..@initial, *@page-@before..@page+@after, *@last-@final+1..@last+1].sort!.each_cons(2) do |a, b|
46
+ if a<0 || a==b || a>@last # skip out of range and duplicates
47
+ elsif a+1 == b; series.push(a) # no gap -> no additions
48
+ elsif a+2 == b; series.push(a, a+1) # 1 page gap -> fill with missing page
49
+ else series.push(a, :gap) # n page gap -> add :gap
50
+ end # skip the end boundary (last+1)
51
+ end
52
+ series.shift # remove the start boundary (0)
53
+ series[series.index(@page)] = @page.to_s # convert the current page to String
56
54
  end
57
- @series.shift # remove the start-boundary (0)
58
- @series[@series.index(@page)] = @page.to_s # convert the current page to String
59
55
  end
60
56
 
61
57
  end
data/lib/pagy/backend.rb CHANGED
@@ -1,42 +1,24 @@
1
- class Pagy
2
-
3
- # Including this module (usually in your controller) is handy but totally optional.
4
- # It basically just encapsulates a couple of verbose statements in one single slick
5
- # #pagy method, but it does not add any functionality on its own.
6
- #
7
- # Using the module allows you to have a predefined method and a few sub-methods
8
- # (i.e. methods called only by the predefined method) handy if you need to override
9
- # some aspect of the predefined #pagy method.
10
- #
11
- # However, you can just explicitly write your own pagy method in just a couple of
12
- # lines, specially if you need to override two or more methods. For example:
13
- #
14
- # def pagy(scope, opts={})
15
- # pagy = Pagy.new scope.count, page: params[:page], **opts
16
- # return pagy, scope.offset(pagy.offset).limit(pagy.limit)
17
- # end
1
+ # See Pagy::Backend API documentation: https://ddnexus.github.io/pagy/api/backend
18
2
 
3
+ class Pagy
19
4
  module Backend ; private # the whole module is private so no problem with including it in a controller
20
5
 
21
- def pagy(obj, opts={})
22
- pagy = Pagy.new(count: pagy_get_count(obj), page: pagy_get_page, i18n_key: pagy_get_i18n_key(obj), **opts)
23
- return pagy, pagy_get_items(obj, pagy)
6
+ # return pagy object and items
7
+ def pagy(collection, vars=nil)
8
+ pagy = Pagy.new(vars ? pagy_get_vars(collection).merge!(vars) : pagy_get_vars(collection)) # conditional merge is faster and saves memory
9
+ return pagy, pagy_get_items(collection, pagy)
24
10
  end
25
11
 
26
- def pagy_get_count(obj)
27
- obj.count
12
+ # sub-method called only by #pagy: here for easy customization of variables by overriding
13
+ def pagy_get_vars(collection)
14
+ # return the variables to initialize the pagy object
15
+ { count: collection.count, page: params[:page] }
28
16
  end
29
17
 
30
- def pagy_get_page
31
- params[:page]
32
- end
33
-
34
- def pagy_get_i18n_key(obj) end
35
-
36
- # this should work with ActiveRecord, Sequel, Mongoid...
37
- # override it if obj does not implement it that way
38
- def pagy_get_items(obj, pagy)
39
- obj.offset(pagy.offset).limit(pagy.limit)
18
+ # sub-method called only by #pagy: here for easy customization of record-extraction by overriding
19
+ def pagy_get_items(collection, pagy)
20
+ # this should work with ActiveRecord, Sequel, Mongoid...
21
+ collection.offset(pagy.offset).limit(pagy.items)
40
22
  end
41
23
 
42
24
  end
data/lib/pagy/frontend.rb CHANGED
@@ -1,165 +1,100 @@
1
+ # See Pagy::Frontend API documentation: https://ddnexus.github.io/pagy/api/frontend
2
+
3
+ # this file will get autoloaded, so environment constants like ::I18n will be already set
1
4
  require 'yaml'
2
5
  class Pagy
3
6
 
4
- # This module supplies a few methods to deal with the navigation aspect of the pagination.
5
- # You will usually include it in some helper module, eventually overriding the #pagy_url_for
6
- # in order to fit its behavior with your app needs (e.g.: removing and adding some param or
7
- # allowing fancy routes, etc.)
8
- #
9
7
  # All the code here has been optimized for performance: it may not look very pretty
10
8
  # (as most code dealing with many long strings), but its performance makes it very sexy! ;)
11
9
  module Frontend
12
10
 
13
11
  # Generic pagination: it returns the html with the series of links to the pages
14
- def pagy_nav(pagy, opts=nil)
15
- opts = opts ? pagy.opts.merge(opts) : pagy.opts
16
- link = pagy_link_proc(pagy)
17
- tags = []
12
+ def pagy_nav(pagy)
13
+ tags = []; link = pagy_link_proc(pagy)
18
14
 
19
15
  tags << (pagy.prev ? %(<span class="page prev">#{link.call pagy.prev, pagy_t('pagy.nav.prev'.freeze), 'aria-label="previous"'.freeze}</span>)
20
16
  : %(<span class="page prev disabled">#{pagy_t('pagy.nav.prev'.freeze)}</span>))
21
17
  pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
22
- tags << case item
23
- when Integer; %(<span class="page">#{link.call item}</span>) # page link
24
- when String ; %(<span class="page active">#{item}</span>) # current page
25
- when :gap ; %(<span class="page gap">#{pagy_t('pagy.nav.gap'.freeze)}</span>) # page gap
18
+ tags << if item.is_a?(Integer); %(<span class="page">#{link.call item}</span>) # page link
19
+ elsif item.is_a?(String) ; %(<span class="page active">#{item}</span>) # current page
20
+ elsif item == :gap ; %(<span class="page gap">#{pagy_t('pagy.nav.gap'.freeze)}</span>) # page gap
26
21
  end
27
22
  end
28
23
  tags << (pagy.next ? %(<span class="page next">#{link.call pagy.next, pagy_t('pagy.nav.next'.freeze), 'aria-label="next"'.freeze}</span>)
29
24
  : %(<span class="page next disabled">#{pagy_t('pagy.nav.next'.freeze)}</span>))
30
- %(<nav class="#{opts[:class]||'pagination'.freeze}" role="navigation" aria-label="pager">#{tags.join(opts[:separator]||' '.freeze)}</nav>)
25
+ %(<nav class="pagination" role="navigation" aria-label="pager">#{tags.join(' '.freeze)}</nav>)
31
26
  end
32
27
 
33
28
 
34
29
  # Pagination for bootstrap: it returns the html with the series of links to the pages
35
- def pagy_nav_bootstrap(pagy, opts=nil)
36
- opts = opts ? pagy.opts.merge(opts) : pagy.opts
37
- link = pagy_link_proc(pagy, 'class="page-link"'.freeze)
38
- tags = []
30
+ def pagy_nav_bootstrap(pagy)
31
+ tags = []; link = pagy_link_proc(pagy, 'class="page-link"'.freeze)
39
32
 
40
33
  tags << (pagy.prev ? %(<li class="page-item prev">#{link.call pagy.prev, pagy_t('pagy.nav.prev'.freeze), 'aria-label="previous"'.freeze}</li>)
41
34
  : %(<li class="page-item prev disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.prev'.freeze)}</a></li>))
42
35
  pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
43
- tags << case item
44
- when Integer; %(<li class="page-item">#{link.call item}</li>) # page link
45
- when String ; %(<li class="page-item active">#{link.call item}</li>) # active page
46
- when :gap ; %(<li class="page-item gap disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.gap'.freeze)}</a></li>) # page gap
36
+ tags << if item.is_a?(Integer); %(<li class="page-item">#{link.call item}</li>) # page link
37
+ elsif item.is_a?(String) ; %(<li class="page-item active">#{link.call item}</li>) # active page
38
+ elsif item == :gap ; %(<li class="page-item gap disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.gap'.freeze)}</a></li>) # page gap
47
39
  end
48
40
  end
49
41
  tags << (pagy.next ? %(<li class="page-item next">#{link.call pagy.next, pagy_t('pagy.nav.next'.freeze), 'aria-label="next"'.freeze}</li>)
50
42
  : %(<li class="page-item next disabled"><a href="#" class="page-link">#{pagy_t('pagy.nav.next'.freeze)}</a></li>))
51
- %(<nav class="#{opts[:class]||'pagination'.freeze}" role="navigation" aria-label="pager"><ul class="pagination">#{tags.join}</ul></nav>)
43
+ %(<nav class="pagination" role="navigation" aria-label="pager"><ul class="pagination">#{tags.join}</ul></nav>)
52
44
  end
53
45
 
54
46
 
55
47
  # return examples: "Displaying items 41-60 of 324 in total" or "Displaying Products 41-60 of 324 in total"
56
- def pagy_info(pagy, vars=nil)
57
- name = vars && vars[:item_name] || pagy_t(pagy.opts[:i18n_key] || 'pagy.info.item'.freeze, count: pagy.count)
58
- name = pagy_t('pagy.info.item'.freeze, count: pagy.count) if name.start_with?('translation missing:'.freeze)
48
+ def pagy_info(pagy)
49
+ name = pagy_t(pagy.vars[:item_path] || 'pagy.info.item_name'.freeze, count: pagy.count)
59
50
  key = pagy.pages == 1 ? 'single_page'.freeze : 'multiple_pages'.freeze
60
- pagy_t "pagy.info.#{key}", item_name: name, count: pagy.count, from: pagy.from, to: pagy.to
51
+ pagy_t("pagy.info.#{key}", item_name: name, count: pagy.count, from: pagy.from, to: pagy.to)
61
52
  end
62
53
 
63
54
 
64
55
  # this works with all Rack-based frameworks (Sinatra, Padrino, Rails, ...)
65
56
  def pagy_url_for(n)
66
57
  url = File.join(request.script_name.to_s, request.path_info)
67
- params = request.GET.merge('page' => n.to_s)
68
- url << '?' << Rack::Utils.build_nested_query(params)
58
+ params = request.GET.merge('page'.freeze => n.to_s)
59
+ url << '?' << Rack::Utils.build_nested_query(pagy_get_params(params))
69
60
  end
70
61
 
71
62
 
72
- # The :pagy_t method is the internal implementation of I18n.t, used when I18n is missing or
73
- # not needed (for example when your application is a single-locale app, e.g. only 'en', or only 'fr'...).
74
- #
75
- # It implements only the very basic functionality of the I18n.t method
76
- # but it's still good enough for the limited Pagy's needs and it is faster.
77
- #
78
- # You can still use this method with a pluralization different than English
79
- # (i.e. not 'zero', 'one', 'other' plurals). In that case you should define the
80
- # Pagy::Opts.i18n_plurals proc to return the plural key based on the passed count.
81
- #
82
- # If you need full I18n functionality, you should override this method with something like:
83
- # def pagy_t(*a); I18n.t(*a) end
84
- # and add your translations to the I18n usual locales files
85
-
86
- hash = YAML.load_file Opts.i18n_file || Pagy.root.join('locales', 'pagy.yml')
87
- I18N = hash[hash.keys.first].freeze
88
-
89
- # Similar to I18n.t for interpolation and pluralization, with the following constraints:
90
- # - the path/keys option is supported only in dot-separated string or symbol format
91
- # - the :scope and :default options are not supported
92
- # - no exception are raised: the errors are returned as translated strings
93
- def pagy_t(path, vars={})
94
- value = I18N.dig(*path.to_s.split('.'.freeze))
95
- if value.is_a?(Hash)
96
- vars.has_key?(:count) or return value
97
- plural = (Opts.i18n_plurals ||= -> (c) {c==0 && 'zero' || c==1 && 'one' || 'other'}).call(vars[:count])
98
- value.has_key?(plural) or return %(invalid pluralization data: "#{path}" cannot be used with count: #{vars[:count]}; key "#{plural}" is missing.)
99
- value = value[plural]
100
- end
101
- value or return %(translation missing: "#{path}")
102
- sprintf value, Hash.new{|h,k| "%{#{k}}"}.merge!(vars) # interpolation
103
- end
104
-
63
+ # sub-method called only by #pagy_url_for: here for easy customization of params by overriding
64
+ def pagy_get_params(p) p end
105
65
 
106
- # NOTICE: This method is used internally, so you need to know about it only if you
107
- # are going to override a *_nav helper or a template AND change the link tags.
108
- #
109
- # You call this method in order to get a proc that you will call to produce the page links.
110
- # The reasaon it is a 2 step process instead of a single method call is performance.
111
- #
112
- # Call the method to get the proc (once):
113
- # link = pagy_link_proc( pagy [, extra_attributes_string ])
114
- #
115
- # Call the proc to get the links (multiple times):
116
- # link.call( page_number [, label [, extra_attributes_string ]])
117
- #
118
- # You can pass extra attribute strings to get inserted in the link tags at many different levels.
119
- # Depending by the scope you want your attributes to be added, (from wide to narrow) you can:
120
- #
121
- # 1. For all pagy objects: set the global option :link_extra:
122
- #
123
- # Pagy::Opts.extra_link = 'data-remote="true"'
124
- # link.call(page_number=2)
125
- # #=> <a href="...?page=2" data-remote="true">2</a>
126
- #
127
- # 2. For one pagy object: pass a :link_extra option to a pagy constructor (Pagy.new or pagy controller method):
128
- #
129
- # @pagy, @records = pagy(my_scope, extra_link: 'data-remote="true"')
130
- # link.call(page_number)
131
- # #=> <a href="...?page=2" data-remote="true">2</a>
132
- #
133
- # 3. For one pagy_nav render: pass a :link_extra option to a *_nav method:
134
- #
135
- # pagy_nav(@pagy, extra_link: 'data-remote="true"') #
136
- # link.call(page_number)
137
- # #=> <a href="...?page=2" data-remote="true">2</a>
138
- #
139
- # 4. For all the calls to the returned pagy_link_proc: pass an extra attributes string when you get the proc:
140
- #
141
- # link = pagy_link_proc(pagy, 'class="page-link"')
142
- # link.call(page_number)
143
- # #=> <a href="...?page=2" data-remote="true" class="page-link">2</a>
144
- #
145
- # 5. For a single link.call: pass an extra attributes string when you call the proc:
146
- #
147
- # link.call(page_number, 'aria-label="my-label"')
148
- # #=> <a href="...?page=2" data-remote="true" class="page-link" aria-label="my-label">2</a>
149
- #
150
- # WARNING: since we use only strings for performance, the attribute strings get concatenated, not merged!
151
- # Be careful not to pass the same attribute at different levels multiple times. That would generate a duplicated
152
- # attribute which is illegal html (although handled by all mayor browsers by ignoring all the duplicates but the first)
153
66
 
154
67
  MARKER = "-pagy-#{'pagy'.hash}-".freeze
155
68
 
156
- def pagy_link_proc(pagy, lx=''.freeze) # link extra
157
- p_prev, p_next, p_lx = pagy.prev, pagy.next, pagy.opts[:link_extra]
69
+ # returns a specialized proc to generate the HTML links
70
+ def pagy_link_proc(pagy, lx=''.freeze) # "lx" means "link extra"
71
+ p_prev, p_next, p_lx = pagy.prev, pagy.next, pagy.vars[:link_extra]
158
72
  a, b = %(<a href="#{pagy_url_for(MARKER)}"#{p_lx ? %( #{p_lx}) : ''.freeze}#{lx.empty? ? lx : %( #{lx})}).split(MARKER)
159
73
  -> (n, text=n, x=''.freeze) { "#{a}#{n}#{b}#{ if n == p_prev ; ' rel="prev"'.freeze
160
74
  elsif n == p_next ; ' rel="next"'.freeze
161
- else ''.freeze
162
- end }#{x.empty? ? x : %( #{x})}>#{text}</a>" }
75
+ else ''.freeze end }#{x.empty? ? x : %( #{x})}>#{text}</a>" }
76
+ end
77
+
78
+
79
+ # define #pagy_t depending on I18N[:gem] and I18n
80
+ if I18N[:gem] || I18N[:gem].nil? && defined?(I18n)
81
+ I18n.load_path << I18N[:file]
82
+ def pagy_t(*a); I18n.t(*a) end
83
+ else
84
+ # load data from the first locale in the file
85
+ I18N_DATA = YAML.load_file(I18N[:file]).first[1].freeze
86
+ # Similar to I18n.t for interpolation and pluralization but without translation
87
+ # Use only for single-language apps: it is specialized for pagy and 5x faster than I18n.t
88
+ def pagy_t(path, vars={})
89
+ value = I18N_DATA.dig(*path.to_s.split('.'.freeze)) or return %(translation missing: "#{path}")
90
+ if value.is_a?(Hash)
91
+ vars.key?(:count) or return value
92
+ plural = I18N[:plurals].call(vars[:count])
93
+ value.key?(plural) or return %(invalid pluralization data: "#{path}" cannot be used with count: #{vars[:count]}; key "#{plural}" is missing.)
94
+ value = value[plural] or return %(translation missing: "#{path}")
95
+ end
96
+ sprintf value, Hash.new{|h,k| "%{#{k}}"}.merge!(vars) # interpolation
97
+ end
163
98
  end
164
99
 
165
100
  end
@@ -0,0 +1,22 @@
1
+ <%#
2
+ This template is i18n-ready: if you don't use i18n, then you can replace the pagy_t
3
+ calls with the actual strings ("&lsaquo; Prev", "Next &rsaquo;", "&hellip;").
4
+
5
+ The link variable is set to a proc that returns the link tag.
6
+ Usage: link.call( page_number [, text [, extra_attributes_string ]])
7
+ -%>
8
+ <% link = pagy_link_proc(pagy) -%>
9
+ <%# -%><nav aria-label="pager" class="pagination" role="navigation">
10
+ <% if pagy.prev -%> <span class="page prev"><%== link.call(pagy.prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"') %></span>
11
+ <% else -%> <span class="page prev disabled"><%== pagy_t('pagy.nav.prev') %></span>
12
+ <% end -%>
13
+ <% pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] -%>
14
+ <% if item.is_a?(Integer) -%> <span class="page"><%== link.call(item) %></span>
15
+ <% elsif item.is_a?(String) -%> <span class="page current"><%= item %></span>
16
+ <% elsif item == :gap -%> <span class="page gap"><%== pagy_t('pagy.nav.gap') %></span>
17
+ <% end -%>
18
+ <% end -%>
19
+ <% if pagy.next -%> <span class="page next"><%== link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"') %></span>
20
+ <% else -%> <span class="page next disabled"><%== pagy_t('pagy.nav.next') %></span>
21
+ <% end -%>
22
+ <%# -%></nav>
@@ -13,18 +13,16 @@
13
13
  - else
14
14
  %span.page.prev.disabled!= pagy_t('pagy.nav.prev')
15
15
 
16
- - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
- - case item
16
+ - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
+ - if item.is_a?(Integer) # page link
18
+ %span.page
19
+ != link.call(item)
18
20
 
19
- - when Integer # page link
20
- %span.page
21
- != link.call(item)
21
+ - elsif item.is_a?(String) # current page
22
+ %span.page.current= item
22
23
 
23
- - when String # current page
24
- %span.page.current= item
25
-
26
- - when :gap # page gap
27
- %span.page.gap!= pagy_t('pagy.nav.gap')
24
+ - elsif item == :gap # page gap
25
+ %span.page.gap!= pagy_t('pagy.nav.gap')
28
26
 
29
27
  - if pagy.next
30
28
  %span.page.next!= link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"')
@@ -13,16 +13,15 @@ nav.pagination role="navigation" aria-label="pager"
13
13
  - else
14
14
  span.page.prev.disabled ==> pagy_t('pagy.nav.prev')
15
15
 
16
- - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
- - case item
18
- - when Integer # page link
19
- span.page ==> link.call(item)
16
+ - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
17
+ - if item.is_a?(Integer) # page link
18
+ span.page ==> link.call(item)
20
19
 
21
- - when String # current page
22
- span.page.current ==> item
20
+ - elsif item.is_a?(String) # current page
21
+ span.page.current ==> item
23
22
 
24
- - when :gap # page gap
25
- span.page.gap ==> pagy_t('pagy.nav.gap')
23
+ - elsif item == :gap # page gap
24
+ span.page.gap ==> pagy_t('pagy.nav.gap')
26
25
 
27
26
  - if pagy.next
28
27
  span.page.next == link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"')
@@ -0,0 +1,24 @@
1
+ <%#
2
+ This template is i18n-ready: if you don't use i18n, then you can replace the pagy_t
3
+ calls with the actual strings ("&lsaquo; Prev", "Next &rsaquo;", "&hellip;").
4
+
5
+ The link variable is set to a proc that returns the link tag.
6
+ Usage: link.call( page_number [, text [, extra_attributes_string ]])
7
+ -%>
8
+ <% link = pagy_link_proc(pagy, 'class="page-link"') -%>
9
+ <%# -%><nav aria-label="pager" class="pagination" role="navigation">
10
+ <%# -%> <ul class="pagination">
11
+ <% if pagy.prev -%> <li class="page-item prev"><%== link.call(pagy.prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"') %></li>
12
+ <% else -%> <li class="page-item prev disabled"><a href="#" class="page-link"><%== pagy_t('pagy.nav.prev') %></a></li>
13
+ <% end -%>
14
+ <% pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] -%>
15
+ <% if item.is_a?(Integer) -%> <li class="page-item"><%== link.call(item) %></li>
16
+ <% elsif item.is_a?(String) -%> <li class="page-item active"><%== link.call(item) %></li>
17
+ <% elsif item == :gap -%> <li class="page-item disabled gap"><a href="#" class="page-link"><%== pagy_t('pagy.nav.gap') %></a></li>
18
+ <% end -%>
19
+ <% end -%>
20
+ <% if pagy.next -%> <li class="page-item next"><%== link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"') %></li>
21
+ <% else -%> <li class="page-item next disabled"><a href="#" class="page-link"><%== pagy_t('pagy.nav.next') %></a></li>
22
+ <% end -%>
23
+ <%# -%> </ul>
24
+ <%# -%></nav>
@@ -16,17 +16,16 @@
16
16
  %li.page-item.prev.disabled
17
17
  %a.page-link{:href => '#'}!= pagy_t('pagy.nav.prev')
18
18
 
19
- - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
- - case item
21
- - when Integer # page link
22
- %li.page-item!= link.call(item)
19
+ - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
+ - if item.is_a?(Integer) # page link
21
+ %li.page-item!= link.call(item)
23
22
 
24
- - when String # current page
25
- %li.page-item.active!= link.call(item)
23
+ - elsif item.is_a?(String) # current page
24
+ %li.page-item.active!= link.call(item)
26
25
 
27
- - when :gap # page gap
28
- %li.page-item.disabled.gap
29
- %a.page-link{:href => "#"}!= pagy_t('pagy.nav.gap')
26
+ - elsif item == :gap # page gap
27
+ %li.page-item.disabled.gap
28
+ %a.page-link{:href => "#"}!= pagy_t('pagy.nav.gap')
30
29
 
31
30
  - if pagy.next
32
31
  %li.page-item.next!= link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"')
@@ -16,18 +16,16 @@ nav.pagination role="navigation" aria-label="pager"
16
16
  li.page-item.prev.disabled
17
17
  a.page-link href="#" == pagy_t('pagy.nav.prev')
18
18
 
19
- - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
- - case item
19
+ - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
20
+ - if item.is_a?(Integer) # page link
21
+ li.page-item == link.call(item)
21
22
 
22
- - when Integer # page link
23
- li.page-item == link.call(item)
23
+ - elsif item.is_a?(String) # current page
24
+ li.page-item.active == link.call(item)
24
25
 
25
- - when String # current page
26
- li.page-item.active == link.call(item)
27
-
28
- - when :gap # page gap
29
- li.page-item.disabled.gap
30
- a.page-link href="#" == pagy_t('pagy.nav.gap')
26
+ - elsif item == :gap # page gap
27
+ li.page-item.disabled.gap
28
+ a.page-link href="#" == pagy_t('pagy.nav.gap')
31
29
 
32
30
  - if pagy.next
33
31
  li.page-item.next == link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"')
data/pagy.gemspec CHANGED
@@ -11,14 +11,14 @@ Gem::Specification.new do |s|
11
11
  s.email = ['dd.nexus@gmail.com']
12
12
  s.date = Date.today.to_s
13
13
 
14
- s.summary = %q{Because pagination should not suck!}
15
- s.description = %q{Dead simple pagination in pure ruby. Easy, fast and very light. No restrictions imposed: use it with any MVC framework, any ORM, any DB type, any templating system or none at all. Use the built-in templates or create yours the way you want.}
14
+ s.summary = 'The Ultimate Pagination Ruby Gem'
15
+ s.description = 'Agnostic pagination in plain ruby: it works with any framework, ORM and DB type, with all kinds of collections, even pre-paginated, scopes, Arrays, JSON data... and just whatever you can count. Easy to use and customize, very fast and very light.'
16
16
  s.homepage = 'https://github.com/ddnexus/pagy'
17
17
  s.license = 'MIT'
18
18
  s.require_paths = ['lib']
19
19
 
20
- s.files = `git ls-files -z`.split("\x0")
21
- .reject{|f| f.start_with? '.', 'test', 'Gemfile', 'Rakefile' }
20
+ s.files = `git ls-files -z`.split("\x0").select{|f| f.start_with?('lib', 'pagy.gemspec', 'LICENSE', 'README', 'images') }
21
+
22
22
 
23
23
  s.add_development_dependency 'bundler', '~> 1.16'
24
24
  s.add_development_dependency 'rake', '~> 10.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pagy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-19 00:00:00.000000000 Z
11
+ date: 2018-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,9 +122,10 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- description: 'Dead simple pagination in pure ruby. Easy, fast and very light. No restrictions
126
- imposed: use it with any MVC framework, any ORM, any DB type, any templating system
127
- or none at all. Use the built-in templates or create yours the way you want.'
125
+ description: 'Agnostic pagination in plain ruby: it works with any framework, ORM
126
+ and DB type, with all kinds of collections, even pre-paginated, scopes, Arrays,
127
+ JSON data... and just whatever you can count. Easy to use and customize, very fast
128
+ and very light.'
128
129
  email:
129
130
  - dd.nexus@gmail.com
130
131
  executables: []
@@ -133,16 +134,20 @@ extra_rdoc_files: []
133
134
  files:
134
135
  - LICENSE.txt
135
136
  - README.md
137
+ - images/efficiency-table.png
138
+ - images/ips-chart.png
139
+ - images/memory-chart.png
140
+ - images/objects-chart.png
136
141
  - lib/locales/pagy.yml
137
142
  - lib/pagy.rb
138
143
  - lib/pagy/backend.rb
139
144
  - lib/pagy/frontend.rb
140
- - lib/templates/bootstrap.html.erb
141
- - lib/templates/bootstrap.html.haml
142
- - lib/templates/bootstrap.html.slim
143
- - lib/templates/default.html.erb
144
- - lib/templates/default.html.haml
145
- - lib/templates/default.html.slim
145
+ - lib/templates/nav.html.erb
146
+ - lib/templates/nav.html.haml
147
+ - lib/templates/nav.html.slim
148
+ - lib/templates/nav_bootstrap.html.erb
149
+ - lib/templates/nav_bootstrap.html.haml
150
+ - lib/templates/nav_bootstrap.html.slim
146
151
  - pagy.gemspec
147
152
  homepage: https://github.com/ddnexus/pagy
148
153
  licenses:
@@ -167,5 +172,5 @@ rubyforge_project:
167
172
  rubygems_version: 2.7.4
168
173
  signing_key:
169
174
  specification_version: 4
170
- summary: Because pagination should not suck!
175
+ summary: The Ultimate Pagination Ruby Gem
171
176
  test_files: []
@@ -1,28 +0,0 @@
1
- <%#
2
- This template is i18n-ready: if you don't use i18n, then you can replace the pagy_t
3
- calls with the actual strings ("&lsaquo; Prev", "Next &rsaquo;", "&hellip;").
4
-
5
- The link variable is set to a proc that returns the link tag.
6
- Usage: link.call( page_number [, text [, extra_attributes_string ]])
7
- -%>
8
- <% link = pagy_link_proc(pagy, 'class="page-link"') -%>
9
- <%# -%><nav aria-label="pager" class="pagination" role="navigation">
10
- <%# -%> <ul class="pagination">
11
- <% if pagy.prev -%> <li class="page-item prev"><%== link.call(pagy.prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"') %></li>
12
- <% else -%> <li class="page-item prev disabled"><a href="#" class="page-link"><%== pagy_t('pagy.nav.prev') %></a></li>
13
- <% end -%>
14
- <% pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] -%>
15
- <% case item
16
- when Integer -%>
17
- <%# -%> <li class="page-item"><%== link.call(item) %></li>
18
- <% when String -%>
19
- <%# -%> <li class="page-item active"><%== link.call(item) %></li>
20
- <% when :gap -%>
21
- <%# -%> <li class="page-item disabled gap"><a href="#" class="page-link"><%== pagy_t('pagy.nav.gap') %></a></li>
22
- <% end -%>
23
- <% end -%>
24
- <% if pagy.next -%> <li class="page-item next"><%== link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"') %></li>
25
- <% else -%> <li class="page-item next disabled"><a href="#" class="page-link"><%== pagy_t('pagy.nav.next') %></a></li>
26
- <% end -%>
27
- <%# -%> </ul>
28
- <%# -%></nav>
@@ -1,26 +0,0 @@
1
- <%#
2
- This template is i18n-ready: if you don't use i18n, then you can replace the pagy_t
3
- calls with the actual strings ("&lsaquo; Prev", "Next &rsaquo;", "&hellip;").
4
-
5
- The link variable is set to a proc that returns the link tag.
6
- Usage: link.call( page_number [, text [, extra_attributes_string ]])
7
- -%>
8
- <% link = pagy_link_proc(pagy) -%>
9
- <%# -%><nav aria-label="pager" class="pagination" role="navigation">
10
- <% if pagy.prev -%> <span class="page prev"><%== link.call(pagy.prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"') %></span>
11
- <% else -%> <span class="page prev disabled"><%== pagy_t('pagy.nav.prev') %></span>
12
- <% end -%>
13
- <% pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36] -%>
14
- <% case item
15
- when Integer -%>
16
- <%# -%> <span class="page"><%== link.call(item) %></span>
17
- <% when String -%>
18
- <%# -%> <span class="page current"><%= item %></span>
19
- <% when :gap -%>
20
- <%# -%> <span class="page gap"><%== pagy_t('pagy.nav.gap') %></span>
21
- <% end -%>
22
- <% end -%>
23
- <% if pagy.next -%> <span class="page next"><%== link.call(pagy.next, pagy_t('pagy.nav.next'), 'aria-label="next"') %></span>
24
- <% else -%> <span class="page next disabled"><%== pagy_t('pagy.nav.next') %></span>
25
- <% end -%>
26
- <%# -%></nav>