cursor_pagination 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|