cursor_pagination 0.0.2 → 0.0.3
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 +4 -4
- data/README.md +6 -0
- data/cursor_pagination.gemspec +3 -0
- data/lib/cursor_pagination.rb +60 -3
- data/lib/cursor_pagination/active_record_model_extension.rb +29 -2
- data/lib/cursor_pagination/page_scope_methods.rb +26 -15
- data/lib/cursor_pagination/version.rb +1 -1
- data/spec/fake_app/active_record/config.rb +1 -0
- data/spec/fake_app/active_record/models.rb +1 -0
- data/spec/fake_app/rails.rb +28 -0
- data/spec/features/entities_feature_spec.rb +95 -0
- data/spec/helpers/cursor_pagination_helper_spec.rb +2 -2
- data/spec/models/entity_spec.rb +86 -20
- data/spec/spec_helper.rb +1 -0
- data/spec/support/spec_shared.rb +8 -4
- metadata +46 -3
- data/app/helpers/cursor_pagination_helper.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e801f9543b56fe4801268a2bf320334b9170af5c
|
4
|
+
data.tar.gz: 32b9c82901596fee3cfe428aa048bffb79ec148c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 924930a39b2eb74ea406572a533db6522d2f9adaf6568bb5872dd96bc260e982ebc9264fd2530c00bde1cd763e5daf4466c43a585e8d7b9c8de4303765814f22
|
7
|
+
data.tar.gz: 8d5ee3dbb3abc1faa7382de0d0da077ec3f700ac494921b8b0bb6bf7e69811832ab4a3735e778d110b631a1d7f637cedfba80f3b11be662e258af88c86cf36bc
|
data/README.md
CHANGED
@@ -34,6 +34,10 @@ You can pass options as second argument for `cursor` scope
|
|
34
34
|
|
35
35
|
User.order('id DESC').cursor(params[:cursor], reverse: true).per(20)
|
36
36
|
|
37
|
+
You can also use multi columns for your entities
|
38
|
+
|
39
|
+
News.order('published_at DESC, id DESC').cursor(params[:cursor], columns: { published_at: { reverse: true }, id: { reverse: true } }).per(20)
|
40
|
+
|
37
41
|
## How it works?
|
38
42
|
|
39
43
|
Actually, cursor is column value of specific entity, it uses to get all entities, later than specific. For example, if you have the Users set with IDs from 1 to 5,
|
@@ -50,6 +54,8 @@ At this point, `cursor` scope accepts these options:
|
|
50
54
|
|
51
55
|
* `reverse`: Set it to true, if your set are ordered descendingly (_DESC_). Default: _false_
|
52
56
|
* `column`: column value of cursor. For example, if you order your data set by *updated_at*, set *updated_at* column for cursor. Default: _id_
|
57
|
+
* `columns`: hash with columns information, where key is the column name and the value is column options. The available options is
|
58
|
+
* reverse: The same as global `reverse` option, but related to current column. Default: _false_
|
53
59
|
|
54
60
|
## Scope methods
|
55
61
|
|
data/cursor_pagination.gemspec
CHANGED
@@ -14,7 +14,10 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.add_dependency "activerecord", ['>= 3.1']
|
17
|
+
spec.add_dependency "actionpack", ['>= 3.1']
|
17
18
|
spec.add_development_dependency "rspec-rails"
|
19
|
+
spec.add_development_dependency "railties"
|
20
|
+
spec.add_development_dependency "capybara"
|
18
21
|
spec.add_development_dependency "sqlite3-ruby"
|
19
22
|
spec.add_development_dependency "database_cleaner", ['< 1.1.0']
|
20
23
|
|
data/lib/cursor_pagination.rb
CHANGED
@@ -1,7 +1,64 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
1
3
|
require "cursor_pagination/version"
|
2
|
-
require "cursor_pagination/active_record_extension"
|
3
|
-
require 'cursor_pagination/action_view_helper'
|
4
4
|
|
5
5
|
module CursorPagination
|
6
|
-
|
6
|
+
|
7
|
+
class Cursor
|
8
|
+
attr_reader :cursor
|
9
|
+
|
10
|
+
def initialize(cursor)
|
11
|
+
@cursor = cursor
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.decode(cursor)
|
15
|
+
unless cursor.nil?
|
16
|
+
new YAML.load(Base64.strict_decode64(cursor))
|
17
|
+
else
|
18
|
+
new nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.value_from_entity(entity, columns)
|
23
|
+
value = []
|
24
|
+
columns.each_key do |column|
|
25
|
+
value << entity.send(column)
|
26
|
+
end
|
27
|
+
value.size == 1 ? value.first : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_entity(entity, columns)
|
31
|
+
new value_from_entity(entity, columns)
|
32
|
+
end
|
33
|
+
|
34
|
+
def encoded
|
35
|
+
Base64.strict_encode64 cursor.to_yaml
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
cursor.nil? || invalid?
|
40
|
+
end
|
41
|
+
|
42
|
+
def invalid?
|
43
|
+
cursor == -1
|
44
|
+
end
|
45
|
+
|
46
|
+
def value
|
47
|
+
@cursor
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
cursor.nil? ? nil : encoded
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#Include ActiveRecord extension
|
57
|
+
require "cursor_pagination/active_record_extension"
|
58
|
+
::ActiveRecord::Base.send :include, CursorPagination::ActiveRecordExtension
|
59
|
+
|
60
|
+
#Include ActionView Helper
|
61
|
+
require 'cursor_pagination/action_view_helper'
|
62
|
+
ActiveSupport.on_load(:action_view) do
|
63
|
+
::ActionView::Base.send :include, ::CursorPagination::ActionViewHelper
|
7
64
|
end
|
@@ -18,18 +18,45 @@ module CursorPagination
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.cursor(cursor, options = {})
|
21
|
-
|
21
|
+
cursor = Cursor.decode(cursor) unless cursor.is_a? Cursor
|
22
|
+
|
23
|
+
options.reverse_merge! column: :id, reverse: false, columns: {}
|
24
|
+
if options[:columns].empty?
|
25
|
+
options[:columns][options[:column]] = { reverse: options[:reverse] }
|
26
|
+
end
|
27
|
+
|
22
28
|
scoped_method = ActiveRecord::VERSION::STRING < '4.0' ? :scoped : :all
|
23
29
|
@current_cursor = cursor
|
24
30
|
@origin_scope = self.send scoped_method
|
25
31
|
@cursor_options = options
|
26
32
|
|
27
33
|
scope = @origin_scope
|
28
|
-
|
34
|
+
unless cursor.empty?
|
35
|
+
cursor_value = [*cursor.value]
|
36
|
+
scope = scope.where _cursor_to_where(options[:columns], cursor_value)
|
37
|
+
end
|
29
38
|
scope = scope.limit(25)
|
30
39
|
|
31
40
|
scope.extending(CursorPagination::PageScopeMethods)
|
32
41
|
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def self._cursor_to_where(columns, cursor, reverse = false)
|
45
|
+
_cursor_to_where_recursion(0, arel_table, columns.to_a, cursor, reverse)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self._cursor_to_where_recursion(i, t, columns, cursor, reverse = false)
|
49
|
+
column = columns[i]
|
50
|
+
method = column.last[:reverse] ? :lt : :gt
|
51
|
+
method = (method == :lt ? :gt : :lt) if reverse
|
52
|
+
if (columns.size - i) == 1 #last column
|
53
|
+
method = (method == :lt ? :lteq : :gteq) if reverse
|
54
|
+
t[column.first].send method, cursor[i]
|
55
|
+
else
|
56
|
+
t[column.first].send(method, cursor[i]).or(t[column.first].eq(cursor[i]).and(_cursor_to_where_recursion(i+1, t, columns, cursor, reverse)))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
33
60
|
end
|
34
61
|
end
|
35
62
|
end
|
@@ -5,32 +5,43 @@ module CursorPagination
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def first_page?
|
8
|
-
previous_cursor == -1
|
8
|
+
previous_cursor.value == -1
|
9
9
|
end
|
10
10
|
|
11
11
|
def last_page?
|
12
|
-
next_cursor == -1
|
12
|
+
next_cursor.value == -1
|
13
13
|
end
|
14
14
|
|
15
15
|
def previous_cursor
|
16
|
-
|
17
|
-
|
18
|
-
case result.size
|
19
|
-
when limit_value+1
|
20
|
-
result.first.send(options[:column])
|
21
|
-
when 0
|
22
|
-
-1 #no previous page
|
16
|
+
Cursor.new(if current_cursor.empty?
|
17
|
+
-1
|
23
18
|
else
|
24
|
-
|
25
|
-
|
19
|
+
scope = _origin_scope.limit(limit_value+1).reverse_order
|
20
|
+
columns = cursor_options[:columns]
|
26
21
|
|
22
|
+
cursor_value = [*current_cursor.value]
|
23
|
+
scope = scope.where _cursor_to_where(columns, cursor_value, true)
|
24
|
+
result = scope.to_a
|
25
|
+
|
26
|
+
case result.size
|
27
|
+
when limit_value+1
|
28
|
+
Cursor.value_from_entity result.last, columns
|
29
|
+
when 0
|
30
|
+
-1 #no previous page
|
31
|
+
else
|
32
|
+
nil #first page, incomplete
|
33
|
+
end
|
34
|
+
end)
|
27
35
|
end
|
28
36
|
|
29
37
|
def next_cursor
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
Cursor.new(if last.nil?
|
39
|
+
-1
|
40
|
+
else
|
41
|
+
# try to get something after last cursor
|
42
|
+
cursor = Cursor.from_entity last, cursor_options[:columns]
|
43
|
+
_origin_scope.cursor(cursor, cursor_options).per(1).count.zero? ? -1 : cursor.value
|
44
|
+
end)
|
34
45
|
end
|
35
46
|
end
|
36
47
|
end
|
data/spec/fake_app/rails.rb
CHANGED
@@ -18,6 +18,10 @@ app.initialize!
|
|
18
18
|
# routes
|
19
19
|
app.routes.draw do
|
20
20
|
resources :entities
|
21
|
+
|
22
|
+
namespace :two_column do
|
23
|
+
resources :entities
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
27
|
#models
|
@@ -25,6 +29,30 @@ require 'fake_app/active_record/models' if defined? ActiveRecord
|
|
25
29
|
|
26
30
|
# controllers
|
27
31
|
class ApplicationController < ActionController::Base; end
|
32
|
+
class EntitiesController < ApplicationController
|
33
|
+
|
34
|
+
def index
|
35
|
+
@entities = Entity.order('custom ASC').cursor(params[:cursor], column: :custom).per(1)
|
36
|
+
render :inline => %q/
|
37
|
+
<%= previous_cursor_link(@entities, "Previous Page") %>
|
38
|
+
<%= @entities.map { |n| "Custom #{n.custom}" }.join("\n") %>
|
39
|
+
<%= next_cursor_link(@entities, "Next Page") %>/
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
module TwoColumn
|
45
|
+
class EntitiesController < ApplicationController
|
46
|
+
def index
|
47
|
+
@entities = Entity.order('custom_time DESC, id DESC').cursor(params[:cursor], columns: { custom_time: { reverse: true }, id: { reverse: true } }).per(1)
|
48
|
+
render :inline => %q/
|
49
|
+
<%= previous_cursor_link(@entities, "Previous Page") %>
|
50
|
+
<%= @entities.map { |n| "Custom #{n.custom}" }.join("\n") %>
|
51
|
+
<%= next_cursor_link(@entities, "Next Page") %>/
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
28
56
|
|
29
57
|
# helpers
|
30
58
|
Object.const_set(:ApplicationHelper, Module.new)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EntitiesController do
|
4
|
+
|
5
|
+
subject { page }
|
6
|
+
|
7
|
+
shared_examples_for "first page" do
|
8
|
+
it { should have_content "Custom 1" }
|
9
|
+
it { should_not have_link "Previous Page" }
|
10
|
+
it { should have_link "Next Page" }
|
11
|
+
end
|
12
|
+
shared_examples_for "second page" do
|
13
|
+
it { should have_content "Custom 2" }
|
14
|
+
it { should have_link "Previous Page" }
|
15
|
+
it { should have_link "Next Page" }
|
16
|
+
end
|
17
|
+
shared_examples_for "third page" do
|
18
|
+
it { should have_content "Custom 3" }
|
19
|
+
it { should have_link "Previous Page" }
|
20
|
+
it { should have_link "Next Page" }
|
21
|
+
end
|
22
|
+
shared_examples_for "last page" do
|
23
|
+
it { should have_content "Custom 4" }
|
24
|
+
it { should have_link "Previous Page" }
|
25
|
+
it { should_not have_link "Next Page" }
|
26
|
+
end
|
27
|
+
shared_examples_for "previous first page" do
|
28
|
+
describe "previous page" do
|
29
|
+
before { click_link "Previous Page" }
|
30
|
+
it_should_behave_like "first page"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
shared_examples_for "previous second page" do
|
34
|
+
describe "previous page" do
|
35
|
+
before { click_link "Previous Page" }
|
36
|
+
it_should_behave_like "second page"
|
37
|
+
|
38
|
+
it_should_behave_like "previous first page"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
shared_examples_for "cursor pagination" do
|
44
|
+
it_should_behave_like "first page"
|
45
|
+
|
46
|
+
describe "second page" do
|
47
|
+
before { click_link "Next Page" }
|
48
|
+
it_should_behave_like "second page"
|
49
|
+
|
50
|
+
describe "third page" do
|
51
|
+
before { click_link "Next Page" }
|
52
|
+
it_should_behave_like "third page"
|
53
|
+
|
54
|
+
describe "last page" do
|
55
|
+
before { click_link "Next Page" }
|
56
|
+
it_should_behave_like "last page"
|
57
|
+
|
58
|
+
describe "previous page" do
|
59
|
+
before { click_link "Previous Page" }
|
60
|
+
it_should_behave_like "third page"
|
61
|
+
|
62
|
+
it_should_behave_like "previous second page"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it_should_behave_like "previous second page"
|
67
|
+
end
|
68
|
+
|
69
|
+
it_should_behave_like "previous first page"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "by custom" do
|
74
|
+
include_context "entities"
|
75
|
+
|
76
|
+
before { visit entities_path }
|
77
|
+
|
78
|
+
it_should_behave_like "cursor pagination"
|
79
|
+
end
|
80
|
+
|
81
|
+
context "by two-columns" do
|
82
|
+
before do
|
83
|
+
two_minutes_ago = 2.minutes.ago
|
84
|
+
Entity.create! custom: 1, custom_time: 1.minute.ago
|
85
|
+
Entity.create! custom: 3, custom_time: two_minutes_ago
|
86
|
+
Entity.create! custom: 2, custom_time: two_minutes_ago
|
87
|
+
Entity.create! custom: 4, custom_time: 3.minutes.ago
|
88
|
+
|
89
|
+
visit two_column_entities_path
|
90
|
+
end
|
91
|
+
|
92
|
+
it_should_behave_like "cursor pagination"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -16,7 +16,7 @@ describe CursorPagination::ActionViewHelper do
|
|
16
16
|
subject { helper.previous_cursor_link third_page, 'Previous', {:controller => 'entities', :action => 'index'} }
|
17
17
|
it { should be_a String }
|
18
18
|
it { should match(/rel="previous"/) }
|
19
|
-
it { should match(
|
19
|
+
it { should match("cursor=#{CGI.escape(c(first_entity.id).to_s)}") }
|
20
20
|
end
|
21
21
|
context 'overriding rel=' do
|
22
22
|
subject { helper.previous_cursor_link second_page, 'Previous', {:controller => 'entities', :action => 'index'}, {:rel => 'external'} }
|
@@ -35,7 +35,7 @@ describe CursorPagination::ActionViewHelper do
|
|
35
35
|
subject { helper.next_cursor_link first_page, 'More', {:controller => 'entities', :action => 'index'} }
|
36
36
|
it { should be_a String }
|
37
37
|
it { should match(/rel="next"/) }
|
38
|
-
it { should match(
|
38
|
+
it { should match("cursor=#{CGI.escape(c(first_entity.id).to_s)}") }
|
39
39
|
end
|
40
40
|
context 'overriding rel=' do
|
41
41
|
subject { helper.next_cursor_link first_page, 'More', {:controller => 'entities', :action => 'index'}, { :rel => 'external' } }
|
data/spec/models/entity_spec.rb
CHANGED
@@ -9,6 +9,24 @@ describe Entity do
|
|
9
9
|
first_entity.custom.should be > second_entity.custom
|
10
10
|
end
|
11
11
|
|
12
|
+
describe "cursor_options" do
|
13
|
+
it "accepts default values" do
|
14
|
+
options = Entity.cursor(nil).cursor_options
|
15
|
+
options[:columns].should eq id: { reverse: false }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts short column notation" do
|
19
|
+
options = Entity.cursor(nil, column: :custom, reverse: true).cursor_options
|
20
|
+
options[:columns].should eq custom: { reverse: true }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "prefer full columns notation" do
|
24
|
+
full_options = { custom: { reverse: false } }
|
25
|
+
options = Entity.cursor(nil, column: :custom_time, reverse: true, columns: full_options).cursor_options
|
26
|
+
options[:columns].should eq full_options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
12
30
|
describe "#cursor method" do
|
13
31
|
it "returns first entity only" do
|
14
32
|
result = first_page.to_a
|
@@ -23,17 +41,37 @@ describe Entity do
|
|
23
41
|
end
|
24
42
|
|
25
43
|
it "support different orders" do
|
26
|
-
result = Entity.order('id DESC').cursor(second_entity.id, reverse: true).per(1).to_a
|
44
|
+
result = Entity.order('id DESC').cursor(c(second_entity.id), reverse: true).per(1).to_a
|
27
45
|
result.size.should eq 1
|
28
46
|
result.first.should eq first_entity
|
29
47
|
end
|
30
48
|
|
31
49
|
it "support different columns" do
|
32
|
-
result = Entity.cursor(second_entity.id, column: :custom).per(1).to_a
|
50
|
+
result = Entity.cursor(c(second_entity.id), column: :custom).per(1).to_a
|
33
51
|
result.size.should eq 1
|
34
52
|
result.first.should eq first_entity
|
35
53
|
end
|
36
54
|
|
55
|
+
describe "time columns" do
|
56
|
+
let(:columns) do
|
57
|
+
{ custom_time: { reverse: false }, id: { reverse: false } }
|
58
|
+
end
|
59
|
+
let(:scope) { Entity.order('custom_time ASC, id ASC') }
|
60
|
+
let(:first_page) { scope.cursor(nil, columns: columns ).per(1) }
|
61
|
+
specify { first_page.next_cursor.value.should eq [last_entity.custom_time, last_entity.id] }
|
62
|
+
let(:second_page) { scope.cursor(first_page.next_cursor, columns: columns).per(1) }
|
63
|
+
specify { second_page.next_cursor.value.should eq [third_entity.custom_time, third_entity.id] }
|
64
|
+
let(:previous_page) { scope.cursor(second_page.previous_cursor, columns: columns).per(1) }
|
65
|
+
specify { previous_page.next_cursor.value.should eq [last_entity.custom_time, last_entity.id] }
|
66
|
+
let(:third_page) { scope.cursor(second_page.next_cursor, columns: columns).per(1) }
|
67
|
+
specify { third_page.next_cursor.value.should eq [second_entity.custom_time, second_entity.id] }
|
68
|
+
|
69
|
+
specify { first_page.first.should eq last_entity }
|
70
|
+
specify { second_page.first.should eq third_entity }
|
71
|
+
specify { third_page.first.should eq second_entity }
|
72
|
+
specify { previous_page.first.should eq last_entity }
|
73
|
+
end
|
74
|
+
|
37
75
|
context "without #per method" do
|
38
76
|
before do
|
39
77
|
25.times { Entity.create! }
|
@@ -51,43 +89,71 @@ describe Entity do
|
|
51
89
|
# -1 means unavailable cursor (last or first page)
|
52
90
|
describe "#next_cursor" do
|
53
91
|
##Default settings
|
54
|
-
specify { Entity.cursor(nil).per(10).next_cursor.should eq -1 }
|
92
|
+
specify { Entity.cursor(nil).per(10).next_cursor.value.should eq -1 }
|
55
93
|
specify { Entity.cursor(nil).per(10).should be_last_page }
|
56
|
-
specify { first_page.next_cursor.should
|
94
|
+
specify { first_page.next_cursor.should be_a CursorPagination::Cursor }
|
95
|
+
specify { first_page.next_cursor.value.should eq first_entity.id}
|
57
96
|
specify { first_page.should_not be_last_page}
|
58
|
-
specify { last_page.next_cursor.should eq -1 }
|
97
|
+
specify { last_page.next_cursor.value.should eq -1 }
|
59
98
|
|
60
99
|
##Reverse order
|
61
|
-
specify { Entity.order('id DESC').cursor(nil, reverse: true).per(1).next_cursor.should eq last_entity.id }
|
62
|
-
specify { Entity.order('id DESC').cursor(second_entity.id, reverse: true).per(1).next_cursor.should eq -1 }
|
100
|
+
specify { Entity.order('id DESC').cursor(nil, reverse: true).per(1).next_cursor.value.should eq last_entity.id }
|
101
|
+
specify { Entity.order('id DESC').cursor(c(second_entity.id), reverse: true).per(1).next_cursor.value.should eq -1 }
|
63
102
|
|
64
103
|
##With custom column
|
65
|
-
specify { Entity.order('custom ASC').cursor(second_entity.custom, column: :custom).per(1).next_cursor.should eq -1 }
|
66
|
-
specify { Entity.order('custom ASC').cursor(third_entity.custom, column: :custom).per(1).next_cursor.should eq second_entity.custom }
|
67
|
-
specify { Entity.order('custom ASC').cursor(nil, column: :custom).per(1).next_cursor.should eq last_entity.custom }
|
104
|
+
specify { Entity.order('custom ASC').cursor(c(second_entity.custom), column: :custom).per(1).next_cursor.value.should eq -1 }
|
105
|
+
specify { Entity.order('custom ASC').cursor(c(third_entity.custom), column: :custom).per(1).next_cursor.value.should eq second_entity.custom }
|
106
|
+
specify { Entity.order('custom ASC').cursor(nil, column: :custom).per(1).next_cursor.value.should eq last_entity.custom }
|
68
107
|
end
|
69
108
|
|
70
109
|
describe "#previous_cursor" do
|
71
110
|
##Default settings
|
72
111
|
#no previous page
|
73
|
-
specify { Entity.cursor(nil).previous_cursor.should eq -1 }
|
112
|
+
specify { Entity.cursor(nil).previous_cursor.value.should eq -1 }
|
74
113
|
specify { Entity.cursor(nil).should be_first_page }
|
75
114
|
#not full previous page
|
76
|
-
specify { Entity.cursor(first_entity.id).previous_cursor.should be_nil }
|
77
|
-
specify { Entity.cursor(first_entity.id).should_not be_first_page }
|
115
|
+
specify { Entity.cursor(c(first_entity.id)).previous_cursor.value.should be_nil }
|
116
|
+
specify { Entity.cursor(c(first_entity.id)).should_not be_first_page }
|
78
117
|
#full previous page
|
79
|
-
specify { third_page.previous_cursor.should eq first_entity.id }
|
118
|
+
specify { third_page.previous_cursor.value.should eq first_entity.id }
|
80
119
|
specify { third_page.should_not be_first_page }
|
81
120
|
|
82
121
|
##Reverse order
|
83
|
-
specify { Entity.order('id DESC').cursor(nil, reverse: true).previous_cursor.should eq -1 }
|
84
|
-
specify { Entity.order('id DESC').cursor(last_entity.id, reverse: true).previous_cursor.should be_nil }
|
85
|
-
specify { Entity.order('id DESC').cursor(third_entity.id, reverse: true).per(1).previous_cursor.should eq last_entity.id }
|
122
|
+
specify { Entity.order('id DESC').cursor(nil, reverse: true).previous_cursor.value.should eq -1 }
|
123
|
+
specify { Entity.order('id DESC').cursor(c(last_entity.id), reverse: true).previous_cursor.value.should be_nil }
|
124
|
+
specify { Entity.order('id DESC').cursor(c(third_entity.id), reverse: true).per(1).previous_cursor.value.should eq last_entity.id }
|
86
125
|
|
87
126
|
##With custom column
|
88
|
-
specify { Entity.order('custom ASC').cursor(nil, column: :custom).previous_cursor.should eq -1 }
|
89
|
-
specify { Entity.order('custom ASC').cursor(last_entity.custom, column: :custom).previous_cursor.should be_nil }
|
90
|
-
specify { Entity.order('custom ASC').cursor(third_entity.custom, column: :custom).per(1).previous_cursor.should eq last_entity.custom }
|
127
|
+
specify { Entity.order('custom ASC').cursor(nil, column: :custom).previous_cursor.value.should eq -1 }
|
128
|
+
specify { Entity.order('custom ASC').cursor(c(last_entity.custom), column: :custom).previous_cursor.value.should be_nil }
|
129
|
+
specify { Entity.order('custom ASC').cursor(c(third_entity.custom), column: :custom).per(1).previous_cursor.value.should eq last_entity.custom }
|
130
|
+
specify { Entity.order('custom ASC').cursor(c(second_entity.custom), column: :custom).per(1).previous_cursor.value.should eq third_entity.custom }
|
131
|
+
specify { Entity.order('custom ASC').cursor(c(first_entity.custom), column: :custom).per(1).previous_cursor.value.should eq second_entity.custom }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "._cursor_to_where" do
|
136
|
+
let(:time) { Time.at 1378132362 }
|
137
|
+
let(:columns) do
|
138
|
+
{ id: { reverse: true }, custom: { reverse: false }, custom_time: { reverse: false } }
|
139
|
+
end
|
140
|
+
let(:cursor_value) { [1,2,time] }
|
141
|
+
let(:cursor) { CursorPagination::Cursor.new cursor_value }
|
142
|
+
|
143
|
+
context "with direct sql" do
|
144
|
+
specify do
|
145
|
+
target_sql = "(\"entities\".\"id\" < 1 OR \"entities\".\"id\" = 1 AND (\"entities\".\"custom\" > 2 OR \"entities\".\"custom\" = 2 AND \"entities\".\"custom_time\" > '2013-09-02 14:32:42.000000'))"
|
146
|
+
where = Entity.send(:_cursor_to_where, columns, cursor_value)
|
147
|
+
where.to_sql.should eq target_sql
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "with reversed sql" do
|
152
|
+
specify do
|
153
|
+
target_sql = "(\"entities\".\"id\" > 1 OR \"entities\".\"id\" = 1 AND (\"entities\".\"custom\" < 2 OR \"entities\".\"custom\" = 2 AND \"entities\".\"custom_time\" <= '2013-09-02 14:32:42.000000'))"
|
154
|
+
where = Entity.send(:_cursor_to_where, columns, cursor_value, true)
|
155
|
+
where.to_sql.should eq target_sql
|
156
|
+
end
|
91
157
|
end
|
92
158
|
end
|
93
159
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/spec_shared.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
shared_context "entities" do
|
2
2
|
before do
|
3
|
-
4.times { |n| Entity.create! custom: (4 - n)}
|
3
|
+
4.times { |n| Entity.create! custom: (4 - n), custom_time: n.minutes.ago}
|
4
4
|
end
|
5
5
|
|
6
6
|
let(:entities) { Entity.all.to_a }
|
@@ -10,7 +10,11 @@ shared_context "entities" do
|
|
10
10
|
let(:last_entity) { entities[3] }
|
11
11
|
|
12
12
|
let(:first_page) { Entity.cursor(nil).per(1) }
|
13
|
-
let(:second_page) { Entity.cursor(first_entity.id).per(1) }
|
14
|
-
let(:third_page) { Entity.cursor(second_entity.id).per(1) }
|
15
|
-
let(:last_page) { Entity.cursor(third_entity.id).per(1) }
|
13
|
+
let(:second_page) { Entity.cursor(c(first_entity.id)).per(1) }
|
14
|
+
let(:third_page) { Entity.cursor(c(second_entity.id)).per(1) }
|
15
|
+
let(:last_page) { Entity.cursor(c(third_entity.id)).per(1) }
|
16
|
+
|
17
|
+
def c(cursor)
|
18
|
+
CursorPagination::Cursor.new cursor
|
19
|
+
end
|
16
20
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cursor_pagination
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Kukunin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: actionpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec-rails
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,34 @@ dependencies:
|
|
38
52
|
- - '>='
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: railties
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: capybara
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
41
83
|
- !ruby/object:Gem::Dependency
|
42
84
|
name: sqlite3-ruby
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,7 +153,6 @@ files:
|
|
111
153
|
- LICENSE.txt
|
112
154
|
- README.md
|
113
155
|
- Rakefile
|
114
|
-
- app/helpers/cursor_pagination_helper.rb
|
115
156
|
- cursor_pagination.gemspec
|
116
157
|
- lib/cursor_pagination.rb
|
117
158
|
- lib/cursor_pagination/action_view_helper.rb
|
@@ -122,6 +163,7 @@ files:
|
|
122
163
|
- spec/fake_app/active_record/config.rb
|
123
164
|
- spec/fake_app/active_record/models.rb
|
124
165
|
- spec/fake_app/rails.rb
|
166
|
+
- spec/features/entities_feature_spec.rb
|
125
167
|
- spec/helpers/cursor_pagination_helper_spec.rb
|
126
168
|
- spec/models/entity_spec.rb
|
127
169
|
- spec/spec_helper.rb
|
@@ -154,6 +196,7 @@ test_files:
|
|
154
196
|
- spec/fake_app/active_record/config.rb
|
155
197
|
- spec/fake_app/active_record/models.rb
|
156
198
|
- spec/fake_app/rails.rb
|
199
|
+
- spec/features/entities_feature_spec.rb
|
157
200
|
- spec/helpers/cursor_pagination_helper_spec.rb
|
158
201
|
- spec/models/entity_spec.rb
|
159
202
|
- spec/spec_helper.rb
|