rb_pager 0.2.0 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdd82410767649de7ec56d84ba2c311a08555eb4e53ca47e0974f8e54540cf02
4
- data.tar.gz: fcc26daca9475701f37e4e5f259e8921edb2fd961ce64e874df1cf3cca84e33a
3
+ metadata.gz: 8a0e694279afddf482e72b7f03ced23218b0690fb91652ae41b24bd352f0e7b0
4
+ data.tar.gz: 256f353dce8cf5cf3d7c8350e16495b8fdfa6fac3324ca728bf6ba6aa92119d9
5
5
  SHA512:
6
- metadata.gz: 335ff86d22e237f2f14826edaf9357b220e0b48019dc9f0534b7f611370ae7f15fdff6956da2a552c25a9fe316de7b5215079a690485c95a08171dd6d633a9d3
7
- data.tar.gz: 12bc348e3abd3f82bfaee15fbd52a8ea15ca4ab9aca9da92cc0e3ba9e3345cb205bda27e4e1b9b05ae44fae84c330a77e978d2e3273027dec25e5eed4089720c
6
+ metadata.gz: 7997c4ad68aa0381d6bdde26f81ad5eddab52284e64b8f9d452422e033b64308932eac85a8ce1c846bed3dd87f9cc2a5526f8a12c578e766e0d52384ef381275
7
+ data.tar.gz: 75f5cdf577645968acd5b3155cb488d6abe907fc84c65d27717b29bdd237b18b07dcb1fd347fbfaf85d12ada565bbefda0ebd502b39c7df89c22b8d21ed44f97
data/README.md CHANGED
@@ -9,7 +9,7 @@ For example, with offset–limit pagination, if an item from a prior page is del
9
9
 
10
10
  Cursor-based pagination also performs better for large data sets under most implementations.
11
11
 
12
- To support cursor-based pagination, this specification defines three query parameters `after`, `limit`, `sort`
12
+ To support cursor-based pagination, this specification defines four query parameters `after`, `before`, `limit`, and `sort`
13
13
 
14
14
  ## Installation
15
15
 
@@ -52,22 +52,25 @@ Employee.pager(after: 'w33t44==', limit: 10, sort: 'created_at')
52
52
  Employee.pager(after: 'w33t44==', limit: 10, sort: '-created_at')
53
53
 
54
54
  # [order by non uniq column](https://engineering.shopify.com/blogs/engineering/pagination-relative-cursors)
55
- # /employee?limit=10&sort=name,id
55
+ # /employee?limit=10&sort=name,id&after=w33t44==
56
56
  Employee.pager(after: 'w33t44==', limit: 10, sort: 'name,id')
57
57
 
58
+ # /employee?limit=10&sort=name,id&before=w33t44==
59
+ Employee.pager(before: 'w33t44==', limit: 10, sort: 'name,id')
60
+
58
61
  # on EmployeesController
59
62
  records, meta = Employee.pager(limit: 15)
60
- # meta => { next_cursor => 'uahOI==' }
63
+ # meta => { prev_cursor => 'ergOW==' next_cursor => 'uahOI==' }
61
64
  render json: EmployeeSerializer.new(records, meta: meta).serialized_json, status: :ok
62
65
  ```
63
66
 
64
67
  ## On progress
65
68
 
66
- - [ ] implement `before` cursor
67
- - [ ] meta url for first, last, next and previous page, collection size
69
+ - [x] implement `before` cursor
70
+ - [x] meta url for next and previous page
71
+ - [ ] meta url for first, last and collection size
68
72
  - [ ] [error-cases](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#auto-id-error-cases)
69
73
  - [ ] custom encoder
70
- - [ ] ui helper
71
74
 
72
75
  ## Development
73
76
 
@@ -1,4 +1,4 @@
1
- # Pager.configure do |config|
1
+ # RbPager.configure do |config|
2
2
  # config.limit = 15
3
3
  # config.max_limit = 100
4
4
  # end
@@ -1,6 +1,7 @@
1
1
  require 'active_record'
2
2
  require 'rb_pager/version'
3
3
  require 'rb_pager/configuration'
4
+ require 'rb_pager/base64_encoder'
4
5
 
5
6
  module RbPager
6
7
  class InvalidLimitValueError < StandardError; end
@@ -17,5 +18,6 @@ end
17
18
 
18
19
  if defined?(ActiveRecord)
19
20
  require 'rb_pager/orm/pager_active_record'
21
+ require 'rb_pager/orm/active_record_relation_methods'
20
22
  ActiveRecord::Base.send :include, RbPager::ActiveRecord
21
23
  end
@@ -0,0 +1,19 @@
1
+ require 'base64'
2
+ module RbPager
3
+ module Base64Encoder
4
+ def encode(data)
5
+ Base64.strict_encode64(data)
6
+ end
7
+
8
+ def decode(data)
9
+ return nil if data.nil?
10
+
11
+ decoded_data = Base64.strict_decode64(data)
12
+ Hash[
13
+ decoded_data.split(',').map do |pair|
14
+ k, v = pair.split(':', 2)
15
+ end
16
+ ]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module RbPager
2
+ module ActiveRecord
3
+ module ActiveRecordRelationMethods
4
+ def left_over?
5
+ @left_over ||= begin
6
+ # Cache #size otherwise multiple calls to the database will occur.
7
+ size.positive? && size < total_size
8
+ end
9
+ end
10
+
11
+ # Returns number of records that exist in scope of the current cursor
12
+ def total_size(column_name = :all) #:nodoc:
13
+ # #count overrides the #select which could include generated columns
14
+ # referenced in #order, so skip #order here, where it's irrelevant to the
15
+ # result anyway.
16
+ @total_size ||= begin
17
+ context = except(:offset, :limit, :order)
18
+
19
+ # Remove includes only if they are irrelevant
20
+ context = context.except(:includes) unless references_eager_loaded_tables?
21
+
22
+ args = [column_name]
23
+
24
+ # .group returns an OrderedHash that responds to #count
25
+ context = context.count(*args)
26
+
27
+ if context.is_a?(Hash) || context.is_a?(ActiveSupport::OrderedHash)
28
+ context.count
29
+ else
30
+ context.respond_to?(:count) ? context.count(*args) : context
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -3,40 +3,28 @@ module RbPager
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
+ include ::RbPager::Base64Encoder
7
+
6
8
  AR_ORDER = { '+' => :asc, '-' => :desc }
7
9
 
8
10
  def pager(after: nil, before: nil, limit: nil, sort: nil)
9
11
  raise InvalidLimitValueError if limit && limit < 1
10
12
  instance_variable_set(:@sorted_columns, nil)
13
+ instance_variable_set(:@collection, nil)
14
+ instance_variable_set(:@records, nil)
11
15
 
12
- page_limit = limit || RbPager.configuration.limit
16
+ @page_limit = limit || RbPager.configuration.limit
13
17
  @sort = sort
18
+
14
19
  @after = decode(after)
15
20
  @before = decode(before)
16
21
  @direction = :next
17
22
 
18
- collection = where(apply_after)
19
- collection = collection.where(apply_before)
20
- collection = collection.order(sorted_columns)
21
- .extending(ActiveRecordRelationMethods)
22
- .limit(page_limit)
23
-
24
- create_paginate_meta(collection)
23
+ create_paginate_meta
25
24
  end
26
25
 
27
26
  private
28
27
 
29
- def decode(cursor)
30
- return nil if cursor.nil?
31
-
32
- decode = Base64.strict_decode64(cursor)
33
- Hash[
34
- decode.split(',').map do |pair|
35
- k, v = pair.split(':', 2)
36
- end
37
- ]
38
- end
39
-
40
28
  def apply_after
41
29
  return nil if @after.nil?
42
30
 
@@ -90,71 +78,50 @@ module RbPager
90
78
  sorted_params
91
79
  end
92
80
 
93
- def create_paginate_meta(collection)
81
+ def create_paginate_meta
94
82
  cursor = cursor(collection)
95
83
 
96
84
  meta = { prev_cursor: cursor.first, next_cursor: cursor.last }
97
85
  [collection, meta]
98
86
  end
99
87
 
88
+ def collection
89
+ @collection ||= where(apply_after)
90
+ .where(apply_before)
91
+ .order(sorted_columns)
92
+ .extending(ActiveRecordRelationMethods)
93
+ .limit(@page_limit)
94
+ end
95
+
96
+ def records
97
+ @records || collection.to_a
98
+ end
99
+
100
100
  def cursor(collection)
101
101
  return ['', ''] if collection.total_size.zero?
102
102
 
103
103
  prev_cursor, next_cursor = [], []
104
104
 
105
105
  if sorted_columns.blank?
106
- prev_cursor = ["#{primary_key}:#{collection.first.send(primary_key)}"]
107
- next_cursor = ["#{primary_key}:#{collection.last.send(primary_key)}"]
106
+ prev_cursor = ["#{primary_key}:#{records.first.send(primary_key)}"]
107
+ next_cursor = ["#{primary_key}:#{records.last.send(primary_key)}"]
108
108
  else
109
109
  sorted_columns.each do |key, _value|
110
110
  if type_for_attribute(key).type.eql? :datetime
111
- prev_cursor << "#{key}:#{collection.first.send(key).rfc3339(9)}"
112
- next_cursor << "#{key}:#{collection.last.send(key).rfc3339(9)}"
111
+ prev_cursor << "#{key}:#{records.first.send(key).rfc3339(9)}"
112
+ next_cursor << "#{key}:#{records.last.send(key).rfc3339(9)}"
113
113
  next
114
114
  end
115
115
 
116
- prev_cursor << "#{key}:#{collection.first.send(key)}"
117
- next_cursor << "#{key}:#{collection.last.send(key)}"
116
+ prev_cursor << "#{key}:#{records.first.send(key)}"
117
+ next_cursor << "#{key}:#{records.last.send(key)}"
118
118
  end
119
119
  end
120
120
 
121
- return ['', Base64.strict_encode64(next_cursor.join(','))] if (@after.nil? && @before.nil?) || @direction.eql?(:prev) && !collection.left_over?
122
- return [Base64.strict_encode64(prev_cursor.join(',')), ''] if @direction.eql?(:next) && !collection.left_over?
121
+ return ['', encode(next_cursor.join(','))] if (@after.nil? && @before.nil?) || @direction.eql?(:prev) && !collection.left_over?
122
+ return [encode(prev_cursor.join(',')), ''] if @direction.eql?(:next) && !collection.left_over?
123
123
 
124
- [Base64.strict_encode64(prev_cursor.join(',')), Base64.strict_encode64(next_cursor.join(','))]
125
- end
126
- end
127
-
128
- module ActiveRecordRelationMethods
129
- def left_over?
130
- @left_over ||= begin
131
- # Cache #size otherwise multiple calls to the database will occur.
132
- size.positive? && size < total_size
133
- end
134
- end
135
-
136
- # Returns number of records that exist in scope of the current cursor
137
- def total_size(column_name = :all) #:nodoc:
138
- # #count overrides the #select which could include generated columns
139
- # referenced in #order, so skip #order here, where it's irrelevant to the
140
- # result anyway.
141
- @total_size ||= begin
142
- context = except(:offset, :limit, :order)
143
-
144
- # Remove includes only if they are irrelevant
145
- context = context.except(:includes) unless references_eager_loaded_tables?
146
-
147
- args = [column_name]
148
-
149
- # .group returns an OrderedHash that responds to #count
150
- context = context.count(*args)
151
-
152
- if context.is_a?(Hash) || context.is_a?(ActiveSupport::OrderedHash)
153
- context.count
154
- else
155
- context.respond_to?(:count) ? context.count(*args) : context
156
- end
157
- end
124
+ [encode(prev_cursor.join(',')), encode(next_cursor.join(','))]
158
125
  end
159
126
  end
160
127
  end
@@ -1,3 +1,3 @@
1
1
  module RbPager
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rb_pager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - BambangSinaga
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-15 00:00:00.000000000 Z
11
+ date: 2020-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -88,7 +88,9 @@ files:
88
88
  - bin/setup
89
89
  - lib/config/rb_pager.rb
90
90
  - lib/rb_pager.rb
91
+ - lib/rb_pager/base64_encoder.rb
91
92
  - lib/rb_pager/configuration.rb
93
+ - lib/rb_pager/orm/active_record_relation_methods.rb
92
94
  - lib/rb_pager/orm/pager_active_record.rb
93
95
  - lib/rb_pager/version.rb
94
96
  - rb_pager.gemspec