rotulus 0.2.0 → 0.2.2

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: b01279029660a73c745ba2c67c703454f54f44a160b5964523c0fd521ca973c0
4
- data.tar.gz: b1479c0f2a2b8971e4d8977f4b5e4a052829d2d3f472fe1199497266d8d3cafe
3
+ metadata.gz: 2c74625ece0961a25549797386fa66abb146564211b4dc1640abd92ac4c0e2a0
4
+ data.tar.gz: 567562c553bbe914cdf5740e4262bd76b59ef04493658a68c69bd420e3109283
5
5
  SHA512:
6
- metadata.gz: 12120dce0516e9823e891dea5bfe76af1148234e18a556f1d889d1b5ec68031746afbe6e0599955ced295de214d6eedfa3b560ffdce03e0c49e94561a3c5399a
7
- data.tar.gz: 6b10d0bab75dffd5f1c988f09161a56cdc0688e50af403864a9b15c6d6b0b2a1191eddff5606be86e516ba3ba785a96cbbaa39dbb963593a00108fb4aee8bc23
6
+ metadata.gz: 9e1380f553f3a0756b36b21f2a6193ee7031fe29bc8b3478ec349f6c35d2b9292ed5afd295b6b5250b2dc4498e1d86b66d13d639ea5e3d36db78fc8868fb4e9f
7
+ data.tar.gz: 625359b428c792c30a2a2ee2b6b702a2c7270e495111c284c82a1d1433eddf5d0ac485d6d5ae961128006f161c4cb01b8fa76f7ab3bd14d63cb761306a9392f6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
- ## 0.1.0
1
+ ## 0.2.0
2
+ - Initial release.
2
3
 
3
- Initial release.
4
+ ## 0.2.1
5
+ - Drop unnecessary ORDER BY columns following a non-nullable and distinct column.
6
+
7
+ ## 0.2.2
8
+ - Raise error when there is no non-nullable and distinct column in the configured order definition.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rotulus
2
2
 
3
- [![CI](https://github.com/jsonb-uy/rotulus/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jsonb-uy/rotulus/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/jsonb-uy/rotulus/branch/main/graph/badge.svg?token=OKGOWP4SH9)](https://codecov.io/gh/jsonb-uy/rotulus)
3
+ [![Gem Version](https://badge.fury.io/rb/rotulus.svg)](https://badge.fury.io/rb/rotulus) [![CI](https://github.com/jsonb-uy/rotulus/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jsonb-uy/rotulus/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/jsonb-uy/rotulus/branch/main/graph/badge.svg?token=OKGOWP4SH9)](https://codecov.io/gh/jsonb-uy/rotulus)
4
4
 
5
5
  ### Cursor-based pagination for apps built on Rails/ActiveRecord
6
6
 
@@ -295,18 +295,19 @@ page = Rotulus::Page.new(items, order: order_by, limit: 2)
295
295
 
296
296
  | Class | Description |
297
297
  | ----------- | ----------- |
298
- | `Rotulus::InvalidCursor` | Cursor token received is invalid e.g., unrecognized token, token data has been tampered/updated, base ActiveRecord relation filter/sorting/limit is no longer consistent to the token/ |
298
+ | `Rotulus::InvalidCursor` | Cursor token received is invalid e.g., unrecognized token, token data has been tampered/updated, base ActiveRecord relation filter/sorting/limit is no longer consistent to the token. |
299
299
  | `Rotulus::Expired` | Cursor token received has expired based on the configured `token_expires_in` |
300
300
  | `Rotulus::InvalidLimit` | Limit set to Rotulus::Page is not valid. e.g., exceeds the configured limit. see `config.page_max_limit` |
301
301
  | `Rotulus::CursorError` | Generic error for cursor related validations |
302
- | `Rotulus:: InvalidColumnError` | Column provided in the :order param is can't be recognized. |
302
+ | `Rotulus::InvalidColumnError` | Column provided in the :order param can't be found. |
303
+ | `Rotulus::MissingTiebreakerError` | There is no non-nullable and distinct column in the configured order definition. |
303
304
  | `Rotulus::ConfigurationError` | Generic error for missing/invalid configurations. |
304
305
 
305
306
  ## How it works
306
307
  Cursor-based pagination uses a reference point/record to fetch the previous or next set of records. This gem takes care of the SQL query and cursor generation needed for the pagination. To ensure that the pagination results are stable, it requires that:
307
308
 
308
309
  * Records are sorted (`ORDER BY`).
309
- * In case multiple records with the same column value(s) exists in the result, a unique column is needed as tie-breaker. Usually, the table PK suffices for this but for complex queries(e.g. with table joins and with nullable columns, etc.), combining and using multiple columns that would uniquely identify the row in the result is needed.
310
+ * In case multiple records with the same column value(s) exists in the result, a unique non-nullable column is needed as tie-breaker. Usually, the table PK suffices for this but for complex queries(e.g. with table joins and with nullable columns, etc.), combining and using multiple columns that would uniquely identify the row in the result is needed.
310
311
  * Columns used in `ORDER BY` would need to be indexed as they will be used in filtering.
311
312
 
312
313
 
@@ -352,13 +353,13 @@ page = Rotulus::Page.new(User.all, order: { first_name: { direction: :asc, nulls
352
353
 
353
354
  ###### SQL:
354
355
  ```sql
355
- -- if first_name value of the last record on current page is not null:
356
+ -- if last_name value of the current page's last record is not null:
356
357
  WHERE ((users.last_name >= ? OR users.last_name IS NULL) AND
357
358
  ((users.last_name > ? OR users.last_name IS NULL)
358
359
  OR (users.last_name = ? AND users.id > ?)))
359
360
  ORDER BY users.last_name asc nulls last, users.id asc LIMIT 3
360
361
 
361
- -- if first_name value of the last record on current page is null:
362
+ -- if last_name value of the current page's last record is null:
362
363
  WHERE users.last_name IS NULL AND users.id > ?
363
364
  ORDER BY users.last_name asc nulls last, users.id asc LIMIT 3
364
365
  ```
data/lib/rotulus/order.rb CHANGED
@@ -11,6 +11,9 @@ module Rotulus
11
11
  @definition = {}
12
12
 
13
13
  build_column_definitions
14
+
15
+ return if has_tiebreaker?
16
+ raise Rotulus::MissingTiebreakerError.new('A non-nullable and distinct column is required.')
14
17
  end
15
18
 
16
19
  # Returns an array of the ordered columns
@@ -62,14 +65,14 @@ module Rotulus
62
65
  #
63
66
  # @return [String] the ORDER BY clause
64
67
  def reversed_sql
65
- Arel.sql(columns.map(&:reversed_order_sql).join(', '))
68
+ Arel.sql(columns_for_order.map(&:reversed_order_sql).join(', '))
66
69
  end
67
70
 
68
71
  # Returns the ORDER BY sort expression(s) to sort the records
69
72
  #
70
73
  # @return [String] the ORDER BY clause
71
74
  def sql
72
- Arel.sql(columns.map(&:order_sql).join(', '))
75
+ Arel.sql(columns_for_order.map(&:order_sql).join(', '))
73
76
  end
74
77
 
75
78
  # Generate a 'state' so we can detect whether the order definition has changed.
@@ -92,6 +95,19 @@ module Rotulus
92
95
 
93
96
  attr_reader :ar_model, :definition, :raw_hash
94
97
 
98
+ def columns_for_order
99
+ return @columns_for_order if instance_variable_defined?(:@columns_for_order)
100
+
101
+ @columns_for_order = []
102
+ columns.each do |col|
103
+ @columns_for_order << col
104
+
105
+ break if col.distinct? && !col.nullable?
106
+ end
107
+
108
+ @columns_for_order
109
+ end
110
+
95
111
  def ar_model_primary_key
96
112
  ar_model.primary_key
97
113
  end
@@ -124,6 +140,12 @@ module Rotulus
124
140
  )
125
141
  end
126
142
 
143
+ def has_tiebreaker?
144
+ last_column = columns_for_order.last
145
+
146
+ last_column.distinct? && !last_column.nullable?
147
+ end
148
+
127
149
  def primary_key_ordered?
128
150
  !definition["#{ar_table}.#{ar_model_primary_key}"].nil?
129
151
  end
@@ -1,3 +1,3 @@
1
1
  module Rotulus
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.2'
3
3
  end
data/lib/rotulus.rb CHANGED
@@ -21,7 +21,8 @@ module Rotulus
21
21
  class InvalidCursorDirection < CursorError; end
22
22
  class InvalidLimit < BaseError; end
23
23
  class ConfigurationError < BaseError; end
24
- class InvalidColumnError < BaseError; end
24
+ class MissingTiebreakerError < ConfigurationError; end
25
+ class InvalidColumnError < ConfigurationError; end
25
26
 
26
27
  def self.db
27
28
  @db ||= case ActiveRecord::Base.connection.adapter_name.downcase
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotulus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uy Jayson B
@@ -73,7 +73,6 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - CHANGELOG.md
76
- - Gemfile
77
76
  - LICENSE
78
77
  - README.md
79
78
  - lib/rotulus.rb
@@ -97,7 +96,7 @@ metadata:
97
96
  homepage_uri: https://github.com/jsonb-uy/rotulus
98
97
  source_code_uri: https://github.com/jsonb-uy/rotulus
99
98
  changelog_uri: https://github.com/jsonb-uy/rotulus/blob/main/CHANGELOG.md
100
- documentation_uri: https://rubydoc.info/github/jsonb-uy/rotulus
99
+ documentation_uri: https://rubydoc.info/github/jsonb-uy/rotulus/main
101
100
  bug_tracker_uri: https://github.com/jsonb-uy/rotulus/issues
102
101
  post_install_message:
103
102
  rdoc_options: []
data/Gemfile DELETED
@@ -1,46 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec
3
-
4
- rails_version = ENV['RAILS_VERSION'] || '7-0-stable'
5
-
6
- gem 'rake'
7
- gem 'rspec'
8
-
9
- if ENV.fetch('COVERAGE', nil) == 'true'
10
- gem 'simplecov'
11
- gem 'simplecov-cobertura'
12
- end
13
-
14
- case rails_version
15
- when '4-2-stable'
16
- # Ruby 2.2 or newer.
17
- gem 'pg', '~> 0.15'
18
- gem 'sqlite3', '~> 1.3.6'
19
- gem 'mysql2', '~> 0.4.4'
20
- when '5-0-stable', '5-1-stable', '5-2-stable'
21
- # Ruby 2.2.2 or newer.
22
- gem 'pg', '~> 0.18'
23
- gem 'sqlite3', '~> 1.3.6'
24
- gem 'mysql2', '~> 0.4.4'
25
- when '6-0-stable'
26
- # Ruby 2.5.0 or newer.
27
- gem 'pg', '~> 0.18'
28
- gem 'sqlite3', '~> 1.4'
29
- gem 'mysql2', '>= 0.4.4'
30
- when '6-1-stable'
31
- # Ruby 2.5.0 or newer.
32
- gem 'pg', '~> 1.1'
33
- gem 'sqlite3', '~> 1.4'
34
- gem 'mysql2', '~> 0.5'
35
- when '7-0-stable'
36
- # Ruby 2.7.0 or newer.
37
- gem 'pg', '~> 1.1'
38
- gem 'sqlite3', '~> 1.4'
39
- gem 'mysql2', '~> 0.5'
40
- else
41
- gem 'pg'
42
- gem 'sqlite3'
43
- gem 'mysql2'
44
- end
45
-
46
- gem 'rails', git: 'https://github.com/rails/rails', branch: rails_version