Skip to content

Commit

Permalink
Merge pull request anykeyh#16 from jwoertink/features/pagination
Browse files Browse the repository at this point in the history
WIP: Adding pagination. fixes anykeyh#15
  • Loading branch information
anykeyh authored Jun 24, 2018
2 parents f66386a + d718ef5 commit 64817f4
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 5 deletions.
31 changes: 31 additions & 0 deletions spec/model/model_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -328,5 +328,36 @@ module ModelSpec
end
end
end

context "with pagination" do
it "can pull the next 5 users from page 2" do
temporary do
reinit

15.times do |x|
User.create!({first_name: "user#{x}"})
end

users = User.query.paginate(page: 2, per_page: 5)
users.map(&.first_name).should eq ["user5", "user6", "user7", "user8", "user9"]
users.total_entries.should eq 15
end
end

it "can paginate with where clause" do
temporary do
reinit
last_names = ["smith", "jones"]
15.times do |x|
last_name = last_names[x % 2]?
User.create!({first_name: "user#{x}", last_name: last_name})
end

users = User.query.where { last_name == "smith" }.paginate(page: 1, per_page: 5)
users.map(&.first_name).should eq ["user0", "user2", "user4", "user6", "user8"]
users.total_entries.should eq 8
end
end
end
end
end
75 changes: 75 additions & 0 deletions spec/sql/select_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,81 @@ module SelectSpec
end
end
end

describe "WithPagination" do
context "when there's 1901902 records and limit of 25" do
it "sets the per_page to 25" do
r = select_request.from(:users).offset(0).limit(25)
r.total_entries = 1901902_i64
r.per_page.should eq 25
end

it "returns 1 for current_page with no limit set" do
r = select_request.from(:users)
r.total_entries = 1901902_i64
r.current_page.should eq 1
end

it "returns 5 for current_page when offset is 100" do
r = select_request.from(:users).offset(100).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 5
end

it "returns 1 for total_pages when there's no limit" do
r = select_request.from(:users)
r.total_entries = 1901902_i64
r.total_pages.should eq 1
end

it "returns 76077 total_pages when 25 per_page" do
r = select_request.from(:users).offset(100).limit(25)
r.total_entries = 1901902_i64
r.total_pages.should eq 76077
end

it "returns 4 as previous_page when on page 5" do
r = select_request.from(:users).offset(100).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 5
r.previous_page.should eq 4
end

it "returns nil for previous_page when on page 1" do
r = select_request.from(:users).offset(0).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 1
r.previous_page.should eq nil
end

it "returns 6 as next_page when on page 5" do
r = select_request.from(:users).offset(100).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 5
r.next_page.should eq 6
end

it "returns nil for next_page when on page 76077" do
r = select_request.from(:users).offset(1901900).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 76077
r.next_page.should eq nil
end

it "returns true for out_of_bounds? when current_page is 76078" do
r = select_request.from(:users).offset(1901925).limit(25)
r.total_entries = 1901902_i64
r.current_page.should eq 76078
r.out_of_bounds?.should eq true
end

it "returns false for out_of_bounds? when current_page is in normal range" do
r = select_request.from(:users).offset(925).limit(25)
r.total_entries = 1901902_i64
r.out_of_bounds?.should eq false
end
end
end
end
end
end
15 changes: 10 additions & 5 deletions src/clear/sql/query/aggregate.cr
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module Clear::SQL::Query::Aggregate
# Use SQL `COUNT` over your query, and return this number as a Int64
def count(type : X.class = Int64) forall X
if (@offset || @limit)
X.new(Clear::SQL.select("COUNT(*)").from({query_count: self.clear_select.select("1")}).scalar(Int64))
else
X.new(self.clear_select.select("COUNT(*)").scalar(Int64))
end
# save the `select` column clause to ensure non-mutability of the query
columns = @columns.dup
o = if (@offset || @limit)
X.new(Clear::SQL.select("COUNT(*)").from({query_count: self.clear_select.select("1")}).scalar(Int64))
else
X.new(self.clear_select.select("COUNT(*)").scalar(Int64))
end
@columns = columns

return o
end

# Call an custom aggregation function, like MEDIAN or other
Expand Down
58 changes: 58 additions & 0 deletions src/clear/sql/query/with_pagination.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module Clear::SQL::Query::WithPagination
DEFAULT_LIMIT = 50
DEFAULT_PAGE = 1

macro included
property total_entries : Int64? = nil
end

# Maybe this goes on the Collection?
def paginate(page : Int32 = DEFAULT_PAGE, per_page : Int32 = DEFAULT_LIMIT)
# Need to clear these values to get total count first
clear_limit.clear_offset
@total_entries = count

# Calculate proper offset and set limit
page = page < 1 ? 1 : page
@limit = per_page.to_i64
@offset = (per_page * (page - 1)).to_i64
change!
end

def per_page
limit
end

def current_page
if offset.nil? || limit.nil?
1
else
(offset.as(Int64) / limit.as(Int64)) + 1
end
end

def total_pages
if limit.nil? || total_entries.nil?
1
else
(total_entries.as(Int64) / limit.as(Int64).to_f).ceil.to_i
end
end

# current_page - 1 or nil if there is no previous page
def previous_page
current_page > 1 ? (current_page - 1) : nil
end

# current_page + 1 or nil if there is no next page
def next_page
current_page < total_pages ? (current_page + 1) : nil
end

# Helper method that is true when someone tries to fetch a page with a
# larger number than the last page. Can be used in combination with flashes
# and redirecting.
def out_of_bounds?
current_page > total_pages
end
end
1 change: 1 addition & 0 deletions src/clear/sql/select_builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module Clear::SQL::SelectBuilder
include Query::Fetch
include Query::BeforeQuery
include Query::CTE
include Query::WithPagination
include Query::Aggregate

def dup : self
Expand Down

0 comments on commit 64817f4

Please sign in to comment.