wor-paginate 0.1.7 → 0.3.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
- SHA1:
3
- metadata.gz: 8dd511971868270eef7127d6f538cc172293f668
4
- data.tar.gz: bf95f5b82639f6ebfc7fcfedf678d282e5156676
2
+ SHA256:
3
+ metadata.gz: 616213ea69272167621606631c90c60e4c14bdb5a2e31f933576ccc5d8dc04e8
4
+ data.tar.gz: 24486490fa441d027284f1ef4d53ec686d849dc21f0d12bbc81116ed51865c4e
5
5
  SHA512:
6
- metadata.gz: 35e3fab61135534b3503248ea77a5a3ba42e1e98d738acda1fefb88c61ca030e05f18e7d0b617cd5282290cc0ee8e99135d75f38bb9b4e90e314ba111db36e33
7
- data.tar.gz: d092291dba845fce0f0a21b1a5088124cc19f334fb84d21f6153433853c2ad8823df05ef4d94351047053fdd61de38a95230ceb9b27bda832a3bf09447851c39
6
+ metadata.gz: d3eb17ff497fae64cdc0edd117591ac02bc03b74669f82271475202e7d7d0d7c341c85804bbc93f7220751d28c45659265b1c68fd122557d8b558e00d7822d11
7
+ data.tar.gz: 8a76d0a58b6a8b9fb5af30e450de603a91fa8efd9de0f1e4a881cf813be5d19386b65fd60c7ed4d40d7b63c7a3ea4dd8d570f2a0f5ba08a7ebee3ce166e1fd82
@@ -5,6 +5,7 @@ AllCops:
5
5
  - spec/dummy/db/**/*
6
6
  - spec/dummy/config/**/*
7
7
  - spec/spec_helper.rb
8
+ TargetRubyVersion: 2.3.8
8
9
 
9
10
  Documentation:
10
11
  Enabled: false
@@ -4,6 +4,7 @@ rvm:
4
4
  - 2.4.7
5
5
  - 2.5.6
6
6
  - 2.6.4
7
+ - 2.7.0
7
8
  - ruby-head
8
9
 
9
10
  before_install:
@@ -1,5 +1,26 @@
1
1
  ## Change log
2
2
 
3
+ ### V0.3.0
4
+ * [#94](https://github.com/Wolox/wor-paginate/pull/94) Add Panko formatter - [@blacksam07](https://github.com/blacksam07).
5
+ * [#96](https://github.com/Wolox/wor-paginate/pull/96) Dynamic Adapters - [@juanpablo-rojas](https://github.com/juanpablo-rojas).
6
+
7
+ ### V0.2.0
8
+ * [#95](https://github.com/Wolox/wor-paginate/pull/95) Added total_count option to overwrite the count in render_paginated - [@juanpablo-rojas](https://github.com/juanpablo-rojas).
9
+ * [#93](https://github.com/Wolox/wor-paginate/pull/93) Infinite scroll - [@mnmallea](https://github.com/mnmallea).
10
+ * [#92](https://github.com/Wolox/wor-paginate/pull/92) Ruby 2.7.0 support - [@mnmallea](https://github.com/mnmallea).
11
+ * [#91](https://github.com/Wolox/wor-paginate/pull/91) Navigation links - [@mnmallea](https://github.com/mnmallea).
12
+
13
+ ### V0.1.10
14
+ * [#90](https://github.com/Wolox/wor-paginate/pull/90) Verify that default adapter adapts - [@mtejedorwolox](https://github.com/mtejedorwolox).
15
+
16
+ ### V0.1.9
17
+ * [#88](https://github.com/Wolox/wor-paginate/pull/88) Default adapter - [@mtejedorwolox](https://github.com/mtejedorwolox).
18
+ * [#87](https://github.com/Wolox/wor-paginate/pull/87) Supporting expect(response).to be_paginated - [@mnmallea](https://github.com/mnmallea).
19
+ * [#86](https://github.com/Wolox/wor-paginate/pull/86) Added previous_page parameter - [@juanpablo-rojas](https://github.com/juanpablo-rojas).
20
+
21
+ ### V0.1.8
22
+ * [#85](https://github.com/Wolox/wor-paginate/pull/85) Delete max Railties dependency in gemspec - [@lucasvoboril](https://github.com/lucasvoboril).
23
+
3
24
  ### V0.1.7
4
25
  * [#61](https://github.com/Wolox/wor-paginate/pull/61) Allows requests on offset pages for enumerables - [@mtejedorwolox](https://github.com/mtejedorwolox).
5
26
  * [#63](https://github.com/Wolox/wor-paginate/pull/63) Add max_limit with optional parameter - [@blacksam07](https://github.com/blacksam07).
data/Gemfile CHANGED
@@ -15,7 +15,7 @@ gemspec
15
15
 
16
16
  group :development, :test do
17
17
  gem 'active_model_serializers', '~> 0.10.0'
18
- gem 'bundler', '~> 2.0.1'
18
+ gem 'bundler', '>= 2.0.1'
19
19
  gem 'byebug', '~> 9.0'
20
20
  gem 'codeclimate-test-reporter', '~> 1.0.0'
21
21
  gem 'database_cleaner', '~> 1.6.0'
@@ -23,6 +23,7 @@ group :development, :test do
23
23
  gem 'faker', '~> 1.7.0'
24
24
  gem 'generator_spec', '~> 0.9.0'
25
25
  gem 'kaminari', '~> 0.17.0'
26
+ gem 'panko_serializer', '~> 0.7.2'
26
27
  gem 'rake', '~> 10.0'
27
28
  gem 'rspec', '~> 3.0'
28
29
  gem 'rspec-rails', '~> 3.6.0'
data/README.md CHANGED
@@ -14,6 +14,7 @@ Wor::Paginate
14
14
  - [Custom serializers](#custom-serializers)
15
15
  - [Custom options](#custom-options)
16
16
  - [Custom formatters](#custom-formatters)
17
+ - [Custom adapters](#custom-adapters)
17
18
  - [Working with Kaminari or will_paginate](#working-with-kaminari-or-will_paginate)
18
19
  - [Test helpers](#test-helpers)
19
20
  - [Contributing](#contributing)
@@ -88,7 +89,10 @@ The response to the index will then be:
88
89
  "total_pages": 2,
89
90
  "total_count": 28,
90
91
  "current_page": 1,
91
- "next_page": 2
92
+ "previous_page": null,
93
+ "next_page": 2,
94
+ "previous_page_url": null,
95
+ "next_page_url": "http://api.example.com/users?page=2
92
96
  }
93
97
  ```
94
98
 
@@ -130,7 +134,44 @@ class CustomSerializer < ActiveModel::Serializer
130
134
  end
131
135
  ```
132
136
 
133
- #### Custom formatters
137
+ ##### total_count
138
+ You can overwrite the `total_count` pagination param by passing it as a single option to the method. This could be used if the whole collection to be paginated is complex and has the risk to broke when counting all the records.
139
+
140
+ ```ruby
141
+ render_paginated DummyModel, total_count: 50
142
+ ```
143
+
144
+ ##### preserve_records
145
+ > WARNING: This option only works with an ActiveRecord collection.
146
+
147
+ Preserve records option can be added to `render_paginated` to mantain current records. This allow to navigate pages like an infinite scroll without adding new records when switching pages.
148
+
149
+ - Timestamp mode (default)
150
+ ```ruby
151
+ def index
152
+ render_paginated SomeModel, preserve_records: true
153
+ end
154
+
155
+ # You can customize the field used to preserve this records (default is 'created_at')
156
+ def index
157
+ render_paginated SomeModel, preserve_records: { by: :timestamp, field: :custom_time_field }
158
+ end
159
+ ```
160
+
161
+ - PK mode
162
+ ```ruby
163
+ def index
164
+ render_paginated SomeModel, preserve_records: { by: :id }
165
+ end
166
+
167
+ # You can customize the field used to preserve this records (default is 'id')
168
+ def index
169
+ render_paginated SomeModel, preserve_records: { by: :id, field: :my_custom_id_field }
170
+ end
171
+ ```
172
+
173
+
174
+ ### Custom formatters
134
175
  A formatter is an object that defines the output of the render_paginated method. In case the application needs a different format for a request, it can be passed to the `render_paginated` method using the `formatter` option:
135
176
  ```ruby
136
177
  render_paginated DummyModel, formatter: CustomFormatter
@@ -140,7 +181,7 @@ or it can also be set as a default in the initializer.
140
181
  A new formatter can be created inheriting from the default one. The `format` method should be redefined returning something that can be converted to json.
141
182
 
142
183
  ```ruby
143
- class CustomFormatter < Wor::Paginate::Formatter
184
+ class CustomFormatter < Wor::Paginate::Formatters::Base
144
185
  def format
145
186
  { page: serialized_content, current: current_page }
146
187
  end
@@ -155,6 +196,74 @@ Available helper methods are:
155
196
  * `paginated_content`: its class depends on the original content passed to render_paginated, it's the paginated but not yet serialized content.
156
197
  * `serialized_content`: array with all the items after going through the serializer (either the default or a supplied one)
157
198
 
199
+
200
+ ### Custom adapters
201
+ An adapter is an object that defines how to show the rendered content, and how to calculate several methods of the pagination, such as 'total_count', 'total_pages', 'paginated_content' among others. In case the application needs a different adapter or a custom one, it can be passed to the `render_paginated` method using the `adapter` option:
202
+ ```ruby
203
+ render_paginated DummyModel, adapter: CustomAdapter
204
+ ```
205
+ or it can also be set as a default in the initializer.
206
+
207
+ A new adapter can be created inheriting from the default Base Adapter. Some methods must be redefined in order to make the adapter "adaptable" to the content that will be rendered.
208
+ Below is an example of a simple posible CustomAdapter that extends from the base Adapter.
209
+
210
+ ```ruby
211
+ class CustomAdapter < Wor::Paginate::Adapters::Base
212
+ def required_methods
213
+ %i[count]
214
+ end
215
+
216
+ def paginated_content
217
+ @paginated_content ||= @content.first(5)
218
+ end
219
+
220
+ def total_pages
221
+ (total_count / @limit.to_f).ceil
222
+ end
223
+
224
+ def total_count
225
+ @content.count
226
+ end
227
+
228
+ delegate :count, to: :paginated_content
229
+ end
230
+ ```
231
+ Here's a brief explanation on every overwritten method in this CustomAdapter example:
232
+
233
+ ##### `#required_methods`
234
+ These will be the methods (as symbols) that the content to be rendered has to support. The next expression will be evaluated for every method added here: `@content.respond_to? method`. All required_methods must answer `true` to the previous expression, in order to make the adapter "adaptable" for the content. For example, if we rendered an ActiveRecord::Relation, this CustomAdapter would be adaptable because an ActiveRecord::Relation responds to the `#count` method. At least one symbol has to be returned in this method, otherwise the adapter won't be able to render content.
235
+
236
+ ##### `#paginated_content`
237
+ This is how the content will be shown. As the content comes in the inherited instance variable `@content`, we can transform the content however we want. In the CustomAdapter example, will always be shown the first 5 records.
238
+
239
+ ##### `#total_pages`
240
+ This could be defined as the number of pages, given the limit requested. As the other values, this can be a custom number of pages, depending on your needs. For this example, this number is just an integer calculation of the total pages, depending on the limit. Also, like `@content`, we are inheriting the `@limit` variable, which allows us to operate with it however we want.
241
+
242
+ ##### `#total_count`
243
+ This will be the number that will tell us 'how many records is returning the request'. Again, we can customize it however we want. For this particular example this will be just the count of `@content`.
244
+
245
+ ##### `#count` method as delegate
246
+ In the end of the CustomAdapter we are delegating the `#count` method to the `paginated_content`. This is because the Base Adapter delegates that method to the inherited adapter, so our custom adapter has to know "how to calculate" that method, that's why we are defining a `#count` method in the delegation (It is always mandatory to define the `#count` method in a custom adapter, whether is a method definition or a delegate).
247
+
248
+ If the content is an ActiveRecord::Relation, for example, this adapter would work, because `paginated_content` would become an ActiveRecord::Relation, which actually knows "how to calculate" the count method. This works as a delegate, because ActiveRecord::Relation has an internal `#count` definition, but we would have to provide the needed method definition if it is a custom method, or we want a custom behaviour of a known method.
249
+
250
+ ##### Other available methods to overwrite
251
+ `Wor::Paginate::Adapters::Base` also has implementations for `#next_page` and `#previous_page` methods (which calculate the number of the next and previous pages, respectively). If you want, you can also overwrite those methods, to calculate custom 'next' and 'previous' page numbers.
252
+
253
+ To understand better the implementation of the Base Adapter and how you could overwrite methods in order to make a functional Custom Adapter, take a look at its definition in: [Base adapter Class](https://github.com/Wolox/wor-paginate/blob/master/lib/wor/paginate/adapters/base.rb).
254
+ Keep in mind that an instance of your Custom Adapter must answer `true` to the `#adapt?` method inherited from the Base Adapter, in order to make it "adaptable" to the content.
255
+
256
+ #### Adapters Operations
257
+ There are also helper methods available to dynamically operate the gem's adapters, so you can configurate them, whether in the initializer or in an internal part of your application. Once you include the gem, you'll be provided with the following methods, inside the Config module:
258
+ * `Wor::Paginate::Config.add_adapter(adapter)`: Add a specific adapter to the array of the gem's adapters. The 'adapter' variable must be a Class reference to an Adapter Class (that class has to have a similar structure as the CustomAdapter example above).
259
+ * `Wor::Paginate::Config.remove_adapter(adapter)`: Remove an specific adapter from the array of the gem's adapters.
260
+ * `Wor::Paginate::Config.clear_adapters`: This method empties the array of the gem's adapters.
261
+ * `Wor::Paginate::Config.adapters`: Returns all the current internal adapters inside the gem.
262
+ * `Wor::Paginate::Config.reset_adapters!`: This helper resets the gem's adapters to its default array of adapters. You can see how the array of default adapters looks like at the beggining of the `Wor::Paginate::Config` module: [Config module](https://github.com/Wolox/wor-paginate/blob/master/lib/wor/paginate/config.rb).
263
+
264
+ When the gem paginates, it tries to adapt the content to the first adapter that is "adaptable" for the content (unless a custom adapter has been passed to render_paginated or a default_adapter has been defined in the initializer). So beware of which adapters (and in which order) are you leaving in the `Wor::Paginate::Config.adapters` array, because depending on those, the gem will try to adapt the content.
265
+
266
+
158
267
  ### Working with Kaminari or will_paginate
159
268
  If either Kaminari or will_paginate are required in the project, Wor::Paginate will use them for pagination with no code or configuration change.
160
269
 
@@ -192,6 +301,24 @@ describe YourController do
192
301
  end
193
302
  ```
194
303
 
304
+ ### Working with panko-serializer
305
+
306
+ The default formatter is [Active Model Serializer](https://github.com/rails-api/active_model_serializers).
307
+ If you want to change it, you should replace the formatter to another one. In this section, we are going to work with `PankoFormatter`
308
+
309
+ #### example
310
+ ```ruby
311
+ Wor::Paginate.configure do |config
312
+ config.formatter = Wor::Paginate::Formatters::PankoFormatter
313
+ end
314
+ ```
315
+ and next pass the specific serializer that you can use in the specific endpoint
316
+
317
+ ```ruby
318
+ def index
319
+ render_paginated DummyModel, each_serializer: DummyModelPankoSerializer
320
+ end
321
+ ```
195
322
  ## Contributing
196
323
 
197
324
  1. Fork it
@@ -208,7 +335,6 @@ end
208
335
  ## About ##
209
336
 
210
337
  The current maintainers of this gem are :
211
- * [Lucas Voboril](https://github.com/lucasVoboril)
212
338
  * [Martín Mallea](https://github.com/mnmallea)
213
339
 
214
340
  This project was developed by:
@@ -6,8 +6,35 @@ Wor::Paginate.configure do |config|
6
6
  config.per_page_param = :limit
7
7
 
8
8
  # In case you want to use other format for your response, you can override our formatter here
9
- # You can extend from Wor::Paginate::Formatter and override the 'format' method
9
+ # You can extend from Wor::Paginate::Formatters::Base and override the 'format' method
10
10
  # For more info about available methods for formatters see:
11
- # https://github.com/Wolox/wor-paginate/blob/master/lib/wor/paginate/formatter.rb
12
- # config.formatter = Wor::Paginate::Formatter
11
+ # https://github.com/Wolox/wor-paginate/blob/master/lib/wor/paginate/formatters/base.rb
12
+ # config.formatter = Wor::Paginate::Formatters::AmsFormatter
13
+
14
+ # Configure a default adapter to use on pagination
15
+ # config.default_adapter = nil
16
+
17
+ # Default: nil
18
+ # Possible values:
19
+ # Wor::Paginate::Adapters::KaminariAlreadyPaginated
20
+ # Wor::Paginate::Adapters::WillPaginateAlreadyPaginated
21
+ # Wor::Paginate::Adapters::WillPaginate
22
+ # Wor::Paginate::Adapters::Kaminari
23
+ # Wor::Paginate::Adapters::ActiveRecord
24
+ # Wor::Paginate::Adapters::Enumerable
25
+
26
+ # Custom adapters
27
+
28
+ # config.adapters = [
29
+ # Adapters::KaminariAlreadyPaginated,
30
+ # Adapters::WillPaginateAlreadyPaginated,
31
+ # Adapters::WillPaginate,
32
+ # Adapters::Kaminari,
33
+ # Adapters::ActiveRecord,
34
+ # Adapters::Enumerable
35
+ # ]
36
+ # config.add_adapter(adapter)
37
+ # config.remove_adapter(adapter)
38
+ # config.clear_adapters
39
+ # config.reset_adapters!
13
40
  end
@@ -5,10 +5,13 @@ require_relative 'paginate/adapters/kaminari'
5
5
  require_relative 'paginate/adapters/will_paginate'
6
6
  require_relative 'paginate/adapters/kaminari_already_paginated'
7
7
  require_relative 'paginate/adapters/will_paginate_already_paginated'
8
+ require_relative 'paginate/exceptions/dependency_error'
8
9
  require_relative 'paginate/exceptions/no_pagination_adapter'
9
10
  require_relative 'paginate/exceptions/invalid_page_number'
10
11
  require_relative 'paginate/exceptions/invalid_limit_number'
11
- require_relative 'paginate/formatter'
12
+ require_relative 'paginate/formatters/base'
13
+ require_relative 'paginate/formatters/panko_formatter'
14
+ require_relative 'paginate/formatters/ams_formatter'
12
15
  require_relative 'paginate/config'
13
16
  require_relative 'paginate/paginate'
14
17
 
@@ -16,6 +19,10 @@ module Wor
16
19
  module Paginate
17
20
  def self.configure
18
21
  yield Config
22
+
23
+ return if Config.adapters.any? || Config.default_adapter.present?
24
+
25
+ raise Exceptions::NoPaginationAdapter
19
26
  end
20
27
  end
21
28
  end
@@ -24,8 +24,15 @@ module Wor
24
24
 
25
25
  def next_page
26
26
  return nil if page >= total_pages
27
+
27
28
  page + 1
28
29
  end
30
+
31
+ def previous_page
32
+ return nil if page == 1
33
+
34
+ page - 1
35
+ end
29
36
  end
30
37
  end
31
38
  end
@@ -4,11 +4,10 @@ module Wor
4
4
  module Helpers
5
5
  module TotalCount
6
6
  def total_count
7
- content = @content.reorder(nil)
8
- content_size = content.size
9
- return content.to_a.size if content_size.is_a? Hash
7
+ @content_size ||= @content.reorder(nil).size
8
+ return @content_size.keys.size if @content_size.is_a? Hash
10
9
 
11
- content_size
10
+ @content_size
12
11
  end
13
12
  end
14
13
  end
@@ -13,6 +13,10 @@ module Wor
13
13
  @paginated_content ||= @content.page(@page).per(@limit)
14
14
  end
15
15
 
16
+ def previous_page
17
+ paginated_content.prev_page
18
+ end
19
+
16
20
  delegate :count, :total_count, :total_pages, :next_page, to: :paginated_content
17
21
  end
18
22
  end
@@ -23,7 +23,7 @@ module Wor
23
23
  paginated_content.to_a.size
24
24
  end
25
25
 
26
- delegate :total_pages, :next_page, to: :paginated_content
26
+ delegate :total_pages, :previous_page, :next_page, to: :paginated_content
27
27
  end
28
28
  end
29
29
  end
@@ -6,10 +6,22 @@ module Wor
6
6
  default_page: 1,
7
7
  page_param: :page,
8
8
  per_page_param: :limit,
9
- formatter: Wor::Paginate::Formatter,
10
- max_limit: 50
9
+ formatter: Wor::Paginate::Formatters::Base,
10
+ max_limit: 50,
11
+ default_adapter: nil
11
12
  }.freeze
12
13
 
14
+ DEFAULT_ADAPTERS = {
15
+ kaminari_paginated: Adapters::KaminariAlreadyPaginated,
16
+ will_paginate_paginated: Adapters::WillPaginateAlreadyPaginated,
17
+ will_paginate: Adapters::WillPaginate,
18
+ kaminari: Adapters::Kaminari,
19
+ active_record: Adapters::ActiveRecord,
20
+ enumerable: Adapters::Enumerable
21
+ }.freeze
22
+
23
+ @adapters = DEFAULT_ADAPTERS.values
24
+
13
25
  module_function
14
26
 
15
27
  DEFAULTS_CONFIGS.each do |key, value|
@@ -22,10 +34,30 @@ module Wor
22
34
  end
23
35
  end
24
36
 
37
+ def add_adapter(adapter)
38
+ @adapters << adapter
39
+ end
40
+
41
+ def remove_adapter(adapter)
42
+ @adapters.delete(adapter)
43
+ end
44
+
45
+ def clear_adapters
46
+ @adapters.clear
47
+ end
48
+
49
+ def adapters
50
+ @adapters
51
+ end
52
+
25
53
  # This is mostly useful for the tests
26
54
  def reset!
27
55
  DEFAULTS_CONFIGS.each { |k, v| send("#{k}=", v) }
28
56
  end
57
+
58
+ def reset_adapters!
59
+ @adapters = DEFAULT_ADAPTERS.values
60
+ end
29
61
  end
30
62
  end
31
63
  end
@@ -0,0 +1,11 @@
1
+ module Wor
2
+ module Paginate
3
+ module Exceptions
4
+ class DependencyError < StandardError
5
+ def initialize(msg = 'Serializer dependency error')
6
+ super(msg)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Wor
2
+ module Paginate
3
+ module Formatters
4
+ class AmsFormatter < Base
5
+ include ActiveSupport::Callbacks
6
+ define_callbacks :raise_dependency_error
7
+ set_callback :raise_dependency_error, :before, :serialized_content, unless: :ams_defined?
8
+
9
+ def serialized_content
10
+ return serializable_resource.new(paginated_content).as_json unless serializer.present?
11
+ raise_dependency_error unless serializer.respond_to?('_attributes_data')
12
+ paginated_content.map { |item| serializer.new(item, options) }
13
+ end
14
+
15
+ private
16
+
17
+ def serializable_resource
18
+ ActiveModelSerializers::SerializableResource
19
+ end
20
+
21
+ def ams_defined?
22
+ defined? serializable_resource
23
+ end
24
+
25
+ def raise_dependency_error
26
+ raise Wor::Paginate::Exceptions::DependencyError
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ require 'wor/paginate/utils/uri_helper'
2
+
3
+ module Wor
4
+ module Paginate
5
+ module Formatters
6
+ class Base
7
+ include Utils::UriHelper
8
+ attr_accessor :adapter, :content, :formatter, :options
9
+
10
+ def initialize(adapter, options = {})
11
+ @adapter = adapter
12
+ @options = options
13
+ end
14
+
15
+ def format # rubocop: disable Metrics/MethodLength
16
+ {
17
+ page: serialized_content,
18
+ count: count,
19
+ total_pages: total_pages,
20
+ total_count: options[:total_count] || total_count,
21
+ current_page: current_page,
22
+ previous_page: previous_page,
23
+ next_page: next_page,
24
+ next_page_url: page_url(next_page),
25
+ previous_page_url: page_url(previous_page)
26
+ }
27
+ end
28
+
29
+ protected
30
+
31
+ delegate :count, :total_count, :total_pages, :previous_page, :next_page, to: :adapter
32
+
33
+ def current_page
34
+ adapter.page.to_i
35
+ end
36
+
37
+ def paginated_content
38
+ @content ||= adapter.paginated_content
39
+ end
40
+
41
+ def serialized_content
42
+ paginated_content.as_json
43
+ end
44
+
45
+ def serializer
46
+ options[:each_serializer]
47
+ end
48
+
49
+ def page_url(page)
50
+ return nil unless page
51
+
52
+ replace_query_params(current_url, page: page)
53
+ end
54
+
55
+ def current_url
56
+ options[:_current_url]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ module Wor
2
+ module Paginate
3
+ module Formatters
4
+ class PankoFormatter < Base
5
+ def serialized_content
6
+ raise Wor::Paginate::Exceptions::DependencyError unless valid_serializer
7
+ ActiveRecord::Base.transaction do
8
+ Panko::ArraySerializer.new(paginated_content, each_serializer: serializer).to_a
9
+ end
10
+ rescue ActiveRecord::StatementInvalid
11
+ retry
12
+ end
13
+
14
+ def valid_serializer
15
+ serializer.respond_to?('_descriptor') && defined?(Panko::Serializer)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,16 +1,7 @@
1
+ require_relative 'utils/preserve_records_helper'
2
+
1
3
  module Wor
2
4
  module Paginate
3
- # The order of this array is important!
4
- # In a future release we'll provide an interface to manipulate it
5
- ADAPTERS = [
6
- Adapters::KaminariAlreadyPaginated,
7
- Adapters::WillPaginateAlreadyPaginated,
8
- Adapters::WillPaginate,
9
- Adapters::Kaminari,
10
- Adapters::ActiveRecord,
11
- Adapters::Enumerable
12
- ].freeze
13
-
14
5
  def render_paginated(content, options = {})
15
6
  return render_paginate_with_include(content, options) if includes?(options)
16
7
 
@@ -18,9 +9,16 @@ module Wor
18
9
  end
19
10
 
20
11
  def paginate(content, options = {})
12
+ current_url = request.original_url
13
+ if (preserve_records = options[:preserve_records])
14
+ content, current_url = Wor::Paginate::Utils::PreserveRecordsHelper
15
+ .new(content, current_url,
16
+ preserve_records.is_a?(Hash) ? preserve_records : {}).call
17
+ end
21
18
  adapter = find_adapter_for_content(content, options)
22
19
  raise Exceptions::NoPaginationAdapter if adapter.blank?
23
- formatter_class(options).new(adapter, options).format
20
+
21
+ formatter_class(options).new(adapter, options.merge(_current_url: current_url)).format
24
22
  end
25
23
 
26
24
  def render_paginate_with_include(content, options)
@@ -28,12 +26,20 @@ module Wor
28
26
  end
29
27
 
30
28
  def formatter_class(options)
31
- options[:formatter].presence || Formatter
29
+ options[:formatter].presence || Config.formatter
32
30
  end
33
31
 
34
32
  def find_adapter_for_content(content, options)
35
- ADAPTERS.map { |adapter| adapter.new(content, page(options), limit(options)) }
36
- .find(&:adapt?)
33
+ return instance_adapter(options[:adapter], content, options) unless options[:adapter].nil?
34
+
35
+ adapters = []
36
+ adapters << Config.default_adapter if Config.default_adapter.present?
37
+ adapters += Config.adapters
38
+ adapters.map { |adapter| instance_adapter(adapter, content, options) }.find(&:adapt?)
39
+ end
40
+
41
+ def instance_adapter(adapter, content, options)
42
+ adapter.new(content, page(options), limit(options))
37
43
  end
38
44
 
39
45
  def page(options)
@@ -41,15 +47,15 @@ module Wor
41
47
  end
42
48
 
43
49
  def option_limit(options)
44
- options[:limit].to_i unless options[:limit].nil?
50
+ options[:limit]&.to_i
45
51
  end
46
52
 
47
53
  def option_max_limit(options)
48
- options[:max_limit].to_i unless options[:max_limit].nil?
54
+ options[:max_limit]&.to_i
49
55
  end
50
56
 
51
57
  def param_limit
52
- params[Config.per_page_param].to_i unless params[Config.per_page_param].nil?
58
+ params[Config.per_page_param]&.to_i
53
59
  end
54
60
 
55
61
  def includes?(options)
@@ -28,9 +28,14 @@ end
28
28
 
29
29
  RSpec::Matchers.define :be_paginated do
30
30
  match do |actual_response|
31
- formatter = @custom_formatter || Wor::Paginate::Formatter
32
- @formatted_keys = formatter.new(MockedAdapter.new).format.as_json.keys
33
- actual_response.keys == @formatted_keys
31
+ response = parse_response(actual_response)
32
+ formatter = @custom_formatter || Wor::Paginate::Formatters::Base
33
+ @formatted_keys = formatter.new(MockedAdapter.new, _current_url: 'http://exaple.com/').format.as_json.keys
34
+ response.keys == @formatted_keys
35
+ end
36
+
37
+ def parse_response(response)
38
+ response.is_a?(Hash) ? response : JSON.parse(response.body)
34
39
  end
35
40
 
36
41
  chain :with do |custom_formatter|
@@ -38,10 +43,11 @@ RSpec::Matchers.define :be_paginated do
38
43
  end
39
44
 
40
45
  failure_message do |actual_response|
41
- "expected that #{actual_response} to be paginated with keys #{@formatted_keys}"
46
+ "expected that #{parse_response(actual_response)} to be paginated with keys #{@formatted_keys}"
42
47
  end
43
48
 
44
49
  failure_message_when_negated do |actual_response|
45
- "expected that #{actual_response} not to be paginated with keys #{@formatted_keys}"
50
+ "expected that #{parse_response(actual_response)} not " \
51
+ "to be paginated with keys #{@formatted_keys}"
46
52
  end
47
53
  end
@@ -0,0 +1,33 @@
1
+ module Wor
2
+ module Paginate
3
+ module Utils
4
+ module PreserveModes
5
+ module Timestamp
6
+ def self.default_field
7
+ :created_at
8
+ end
9
+
10
+ def self.last_value(query_param_value, _content, _field)
11
+ query_param_value ? Time.parse(query_param_value) : now_timestamp
12
+ end
13
+
14
+ private_class_method
15
+
16
+ def self.now_timestamp
17
+ Time.zone.now.iso8601(10)
18
+ end
19
+ end
20
+
21
+ module Id
22
+ def self.default_field
23
+ :id
24
+ end
25
+
26
+ def self.last_value(query_param_value, content, field)
27
+ query_param_value || content.maximum(field)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'uri_helper'
2
+ require_relative 'preserve_modes'
3
+
4
+ module Wor
5
+ module Paginate
6
+ module Utils
7
+ class PreserveRecordsHelper
8
+ def initialize(content, url, options)
9
+ @content = content
10
+ @url = url
11
+ @options = options
12
+ end
13
+
14
+ def call
15
+ [content.where("#{field} <= :last_value", last_value: last_value),
16
+ UriHelper.replace_query_params(url, query_param_name => last_value)]
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :content, :url, :options
22
+
23
+ def by
24
+ @by ||= begin
25
+ by = options[:by]&.to_s || 'timestamp'
26
+ raise ArgumentError, "'by' option should be 'id' or 'timestamp'" unless
27
+ %w[timestamp id].include? by
28
+ "Wor::Paginate::Utils::PreserveModes::#{by.classify}".constantize
29
+ end
30
+ end
31
+
32
+ def field
33
+ @field ||= options[:field] || by.default_field
34
+ end
35
+
36
+ def last_value
37
+ @last_value ||= begin
38
+ query_param_value = UriHelper.query_params(url)[query_param_name]
39
+ by.last_value(query_param_value, content, field)
40
+ end
41
+ end
42
+
43
+ def query_param_name
44
+ @query_param_name ||= "#{field}_let"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ module Wor
2
+ module Paginate
3
+ module Utils
4
+ module UriHelper
5
+ def replace_query_params(uri_string, new_query)
6
+ uri = URI.parse(uri_string)
7
+ query = Rack::Utils.parse_query(uri.query)
8
+ uri.query = Rack::Utils.build_query(query.with_indifferent_access.merge(new_query))
9
+ uri.to_s
10
+ end
11
+
12
+ def query_params(uri_string)
13
+ Rack::Utils.parse_query(URI.parse(uri_string).query).with_indifferent_access
14
+ end
15
+
16
+ module_function :replace_query_params, :query_params
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  module Wor
2
2
  module Paginate
3
- VERSION = '0.1.7'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
File without changes
@@ -19,6 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ['lib']
20
20
 
21
21
 
22
- s.add_dependency 'railties', '>= 4.1.0', "<= 6.0.0"
22
+ s.add_dependency 'railties', '>= 4.1.0'
23
23
  s.add_dependency 'rails', '>= 4.0'
24
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wor-paginate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - icoluccio
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2019-10-31 00:00:00.000000000 Z
14
+ date: 2020-09-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -20,9 +20,6 @@ dependencies:
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
22
  version: 4.1.0
23
- - - "<="
24
- - !ruby/object:Gem::Version
25
- version: 6.0.0
26
23
  type: :runtime
27
24
  prerelease: false
28
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -30,9 +27,6 @@ dependencies:
30
27
  - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: 4.1.0
33
- - - "<="
34
- - !ruby/object:Gem::Version
35
- version: 6.0.0
36
30
  - !ruby/object:Gem::Dependency
37
31
  name: rails
38
32
  requirement: !ruby/object:Gem::Requirement
@@ -80,14 +74,21 @@ files:
80
74
  - lib/wor/paginate/adapters/will_paginate.rb
81
75
  - lib/wor/paginate/adapters/will_paginate_already_paginated.rb
82
76
  - lib/wor/paginate/config.rb
77
+ - lib/wor/paginate/exceptions/dependency_error.rb
83
78
  - lib/wor/paginate/exceptions/invalid_limit_number.rb
84
79
  - lib/wor/paginate/exceptions/invalid_page_number.rb
85
80
  - lib/wor/paginate/exceptions/no_pagination_adapter.rb
86
- - lib/wor/paginate/formatter.rb
81
+ - lib/wor/paginate/formatters/ams_formatter.rb
82
+ - lib/wor/paginate/formatters/base.rb
83
+ - lib/wor/paginate/formatters/panko_formatter.rb
87
84
  - lib/wor/paginate/paginate.rb
88
85
  - lib/wor/paginate/rspec.rb
86
+ - lib/wor/paginate/utils/preserve_modes.rb
87
+ - lib/wor/paginate/utils/preserve_records_helper.rb
88
+ - lib/wor/paginate/utils/uri_helper.rb
89
89
  - lib/wor/paginate/version.rb
90
90
  - pull_request_template.md
91
+ - test.sqlite3
91
92
  - wor-paginate.gemspec
92
93
  homepage: https://github.com/Wolox/wor-paginate
93
94
  licenses:
@@ -108,8 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
109
  - !ruby/object:Gem::Version
109
110
  version: '0'
110
111
  requirements: []
111
- rubyforge_project:
112
- rubygems_version: 2.5.2.1
112
+ rubygems_version: 3.0.3
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Simplified pagination for Rails API controllers
@@ -1,50 +0,0 @@
1
- module Wor
2
- module Paginate
3
- class Formatter
4
- attr_accessor :adapter, :content, :formatter, :options
5
-
6
- def initialize(adapter, options = {})
7
- @adapter = adapter
8
- @options = options
9
- end
10
-
11
- def format
12
- {
13
- page: serialized_content,
14
- count: count,
15
- total_pages: total_pages,
16
- total_count: total_count,
17
- current_page: current_page,
18
- next_page: next_page
19
- }
20
- end
21
-
22
- protected
23
-
24
- delegate :count, :total_count, :total_pages, :next_page, to: :adapter
25
-
26
- def current_page
27
- adapter.page.to_i
28
- end
29
-
30
- def paginated_content
31
- @content ||= adapter.paginated_content
32
- end
33
-
34
- def serialized_content
35
- if serializer.present?
36
- return paginated_content.map { |item| serializer.new(item, options) }
37
- end
38
- if defined? ActiveModelSerializers::SerializableResource
39
- ActiveModelSerializers::SerializableResource.new(paginated_content).as_json
40
- else
41
- paginated_content.as_json
42
- end
43
- end
44
-
45
- def serializer
46
- options[:each_serializer]
47
- end
48
- end
49
- end
50
- end