paginater 0.0.5
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +127 -0
- data/Gemfile.lock.broken +78 -0
- data/Gemfile.lock.good +127 -0
- data/LICENSE.txt +22 -0
- data/README.md +268 -0
- data/Rakefile +1 -0
- data/lib/paginater.rb +31 -0
- data/lib/paginater/constants.rb +32 -0
- data/lib/paginater/core.rb +54 -0
- data/lib/paginater/formatter.rb +131 -0
- data/lib/paginater/page/base_page.rb +31 -0
- data/lib/paginater/page/page_links.rb +50 -0
- data/lib/paginater/version.rb +3 -0
- data/paginater.gemspec +36 -0
- data/spec/paginater/paginatable_array_spec.rb +122 -0
- data/spec/paginater/paginater_spec.rb +327 -0
- data/spec/paginater/paginater_with_kaminari_spec.rb +165 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/active_record_support.rb +53 -0
- data/test/config.ru +2 -0
- data/test/favicon.ico +0 -0
- data/test/rss_helper.rb +32 -0
- data/test/running_test.rb +32 -0
- metadata +233 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/paginater.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'grape'
|
4
|
+
rescue LoadError
|
5
|
+
#do nothing
|
6
|
+
end
|
7
|
+
|
8
|
+
# load paginater components
|
9
|
+
require 'paginater/constants'
|
10
|
+
require 'paginater/core'
|
11
|
+
require 'paginater/page/base_page'
|
12
|
+
require 'paginater/page/page_links'
|
13
|
+
require 'paginater/formatter'
|
14
|
+
require "paginater/version"
|
15
|
+
|
16
|
+
require 'active_record'
|
17
|
+
|
18
|
+
require 'kaminari'
|
19
|
+
require 'kaminari/grape'
|
20
|
+
|
21
|
+
$stderr.puts <<-EOC if !defined?(Grape)
|
22
|
+
warning: no framework detected.
|
23
|
+
EOC
|
24
|
+
|
25
|
+
module Grape
|
26
|
+
module Formatter
|
27
|
+
module Paginater
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
# lib/paginater/constants.rb
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Formatter
|
6
|
+
module Paginater
|
7
|
+
module Constants
|
8
|
+
class PaginaterError < RuntimeError
|
9
|
+
end
|
10
|
+
OneHour = Time.now + 3600
|
11
|
+
DefaultPageSize = 1
|
12
|
+
DefaultPage = 1
|
13
|
+
WrapperParam = :wrapper
|
14
|
+
SimpleParam = :simple
|
15
|
+
KaminariParam = :kaminari
|
16
|
+
SetCookie = "Set-Cookie"
|
17
|
+
# Link headers
|
18
|
+
HEADER_LINK = "Link".freeze
|
19
|
+
HEADER_NEXT = "X-Next".freeze
|
20
|
+
HEADER_LAST = "X-Last".freeze
|
21
|
+
META_REL = "rel".freeze
|
22
|
+
META_LAST = "last".freeze
|
23
|
+
META_NEXT = "next".freeze
|
24
|
+
META_FIRST = "first".freeze
|
25
|
+
META_PREV = "prev".freeze
|
26
|
+
PARAM_PAGE = "page".freeze
|
27
|
+
PARAM_PER_PAGE = "per_page".freeze
|
28
|
+
PARAM_START_PAGE = "start_page".freeze
|
29
|
+
end #Constants
|
30
|
+
end # Paginater
|
31
|
+
end # Formatter
|
32
|
+
end # Grape
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# lib/paginater/core.rb
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Formatter
|
5
|
+
module Paginater
|
6
|
+
module Core
|
7
|
+
|
8
|
+
def paginate(object, page, size)
|
9
|
+
if object.respond_to?(:to_a)
|
10
|
+
object = object.to_a
|
11
|
+
if page > object.count || page.zero?
|
12
|
+
raise Constants::PaginaterError, "invalid page #{page}"
|
13
|
+
end
|
14
|
+
if size.nil? || size <= 0
|
15
|
+
raise Constants::PaginaterError, "invalid size #{size}"
|
16
|
+
end
|
17
|
+
size = object.count if size > object.count
|
18
|
+
if page > page_count(object.count, size)
|
19
|
+
raise Constants::PaginaterError, "page #{page} out"
|
20
|
+
end
|
21
|
+
object = object[((page - 1) * size)...(page * size)]
|
22
|
+
else
|
23
|
+
puts "Can't paginate #{object.class}"
|
24
|
+
object
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def page_count(object_count, size)
|
29
|
+
page_count = if (object_count%size).zero?
|
30
|
+
object_count/size
|
31
|
+
else
|
32
|
+
object_count/size + 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def total_pages(object_count, size, max_pages = nil)
|
37
|
+
return 1 if size.nil?
|
38
|
+
total_pages_count = (object_count.to_f / size).ceil
|
39
|
+
if max_pages && max_pages < total_pages_count
|
40
|
+
max_pages
|
41
|
+
else
|
42
|
+
total_pages_count
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def simple(object, page = 1, size = 1)
|
47
|
+
object = object.to_a
|
48
|
+
object = object[((page.to_i - 1) * size.to_i)...(page.to_i * size.to_i)]
|
49
|
+
end
|
50
|
+
|
51
|
+
end # Logic
|
52
|
+
end # Paginater
|
53
|
+
end # Formatter
|
54
|
+
end # Grape
|
@@ -0,0 +1,131 @@
|
|
1
|
+
|
2
|
+
# lib/paginater/formatter.rb
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Formatter
|
6
|
+
module Paginater
|
7
|
+
class << self
|
8
|
+
|
9
|
+
include Core
|
10
|
+
include Constants
|
11
|
+
|
12
|
+
attr_reader :env, :endpoint, :original, :format
|
13
|
+
attr_reader :original_count, :page_requested, :total_pages
|
14
|
+
|
15
|
+
def call(object, env)
|
16
|
+
|
17
|
+
@env = env
|
18
|
+
@endpoint = env['api.endpoint']
|
19
|
+
@format = env['api.format']
|
20
|
+
@original = object
|
21
|
+
|
22
|
+
check_params
|
23
|
+
|
24
|
+
case endpoint.options[:route_options][:paginater]
|
25
|
+
|
26
|
+
when KaminariParam
|
27
|
+
Kaminari.configure do |config|
|
28
|
+
config.default_per_page = 25 #=> 25?
|
29
|
+
end
|
30
|
+
if is_paginable?
|
31
|
+
# TODO consider also options as :limit => x and :offset => y
|
32
|
+
@paginated = Kaminari.paginate_array(original.to_a).page(endpoint.params[:page]).per(endpoint.params[:size])
|
33
|
+
else
|
34
|
+
@paginated = original.page(endpoint.params[:page]).per(endpoint.params[:size])
|
35
|
+
end
|
36
|
+
|
37
|
+
when SimpleParam
|
38
|
+
@paginated = paginate(original, endpoint.params[:page], endpoint.params[:size])
|
39
|
+
|
40
|
+
when WrapperParam
|
41
|
+
# deprecating...
|
42
|
+
@paginated = wrap_content(paginate(original, endpoint.params[:page], endpoint.params[:size]))
|
43
|
+
|
44
|
+
else
|
45
|
+
@paginated = original
|
46
|
+
end
|
47
|
+
|
48
|
+
set_metadata
|
49
|
+
|
50
|
+
case format
|
51
|
+
when :txt
|
52
|
+
return @paginated.to_s if @paginated.respond_to?(:to_s)
|
53
|
+
when :json
|
54
|
+
Grape::Formatter::Json.call(@paginated, env)
|
55
|
+
else
|
56
|
+
raise PaginaterError, "format #{format} unknown"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def wrap_content(content)
|
64
|
+
object_paged = BasePage.new do |obj|
|
65
|
+
obj.content = content
|
66
|
+
obj.page = endpoint.params[:page]
|
67
|
+
obj.size = endpoint.params[:size]
|
68
|
+
obj.links = endpoint.header(HEADER_LINK)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_params
|
73
|
+
raise PaginaterError, "missing param" unless endpoint.params[:page]
|
74
|
+
raise PaginaterError, "first page must be 1" if endpoint.params[:page].to_i < 1
|
75
|
+
endpoint.params[:page] = endpoint.params[:page].to_i
|
76
|
+
endpoint.params[:size] = (!endpoint.params[:size] || endpoint.params[:size] == '0') ? DefaultPageSize : endpoint.params[:size].to_i
|
77
|
+
@page_requested = endpoint.params[:page]
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_metadata
|
81
|
+
@total_pages = if @paginated.respond_to?(:total_pages)
|
82
|
+
@paginated.total_pages
|
83
|
+
else
|
84
|
+
(@original.count/@paginated.count).ceil
|
85
|
+
end
|
86
|
+
@original_count = if @original.respond_to?(:count)
|
87
|
+
@original.count
|
88
|
+
else
|
89
|
+
1
|
90
|
+
end
|
91
|
+
set_cookie
|
92
|
+
set_headers
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_headers
|
96
|
+
size_param = endpoint.params[:size] == 1 ? "" : "&size=#{endpoint.params[:size]}"
|
97
|
+
first_link = { url: "#{env['PATH_INFO']}?page=#{1}#{size_param}", rel: META_FIRST}
|
98
|
+
next_link = { url: "#{env['PATH_INFO']}?page=#{@page_requested+1}#{size_param}", rel: META_NEXT} unless last_page?
|
99
|
+
prev_link = { url: "#{env['PATH_INFO']}?page=#{@page_requested-1}#{size_param}", rel: META_PREV} if @page_requested > 1
|
100
|
+
endpoint.header(HEADER_LINK, [first_link.to_s,next_link.to_s,prev_link.to_s].join(';'))
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_cookie
|
104
|
+
endpoint.cookies[:paginater] = {
|
105
|
+
:content => "#{@page_requested}/#{@total_pages}/#{@original_count}",
|
106
|
+
:domain => env['HTTP_HOST'],
|
107
|
+
:path => endpoint.options[:path],
|
108
|
+
:expires => OneHour
|
109
|
+
}
|
110
|
+
endpoint.header(SetCookie, endpoint.cookies[:paginater][:content])
|
111
|
+
end
|
112
|
+
|
113
|
+
def is_paginable?
|
114
|
+
original.is_a?(Enumerable)
|
115
|
+
# original.is_a?(Array) || original.is_a?(Hash)
|
116
|
+
# original.respond_to?(:to_a)
|
117
|
+
end
|
118
|
+
|
119
|
+
def last_page?
|
120
|
+
@page_requested == page_count(@original_count, endpoint.params[:size])
|
121
|
+
end
|
122
|
+
|
123
|
+
def log_me text
|
124
|
+
Grape::API.logger.info "#{Time.now} >> #{text}"
|
125
|
+
end
|
126
|
+
|
127
|
+
end # class
|
128
|
+
end # Paginater
|
129
|
+
end # Formatter
|
130
|
+
end # Grape
|
131
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
# lib/paginater/page/base_page.rb
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Formatter
|
6
|
+
module Paginater
|
7
|
+
class BasePage
|
8
|
+
attr_accessor :content, :page, :next_page, :prev_page, :size
|
9
|
+
attr_accessor :code
|
10
|
+
attr_accessor :links
|
11
|
+
def initialize
|
12
|
+
yield self if block_given?
|
13
|
+
end
|
14
|
+
def code(&block)
|
15
|
+
if block_given?
|
16
|
+
@code = block
|
17
|
+
else
|
18
|
+
@code = proc {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def count
|
22
|
+
@content.count ||= 1
|
23
|
+
end
|
24
|
+
# TODO: implement me...
|
25
|
+
def render
|
26
|
+
@code.call self
|
27
|
+
end
|
28
|
+
end # BasePage
|
29
|
+
end # Paginater
|
30
|
+
end # Formatter
|
31
|
+
end # Grape
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# lib/paginater/page/page_links.rb
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Formatter
|
5
|
+
module Paginater
|
6
|
+
class PageLinks
|
7
|
+
include Constants
|
8
|
+
|
9
|
+
DELIM_LINKS = ",".freeze
|
10
|
+
|
11
|
+
attr_accessor :first, :last, :next, :prev
|
12
|
+
|
13
|
+
# let(:header) { {"Link" =>
|
14
|
+
# "<https://api.com/users?page=4&size=20>;
|
15
|
+
# rel=\"next\", <...>;
|
16
|
+
# rel=\"last\", <...>;
|
17
|
+
# rel=\"first\", <...>;
|
18
|
+
# rel=\"prev\""}
|
19
|
+
|
20
|
+
def initialize(response_headers)
|
21
|
+
link_header = response_headers[HEADER_LINK]
|
22
|
+
if link_header
|
23
|
+
return unless link_header =~ /(next|first|last|prev)/
|
24
|
+
|
25
|
+
link_header.split(DELIM_LINKS).each do |link|
|
26
|
+
if link.strip =~ /<([^>]+)>; rel=\"([^\"]+)\"/
|
27
|
+
url_part, meta_part = $1, $2
|
28
|
+
next if !url_part || !meta_part
|
29
|
+
case meta_part
|
30
|
+
when META_FIRST
|
31
|
+
self.first = url_part
|
32
|
+
when META_LAST
|
33
|
+
self.last = url_part
|
34
|
+
when META_NEXT
|
35
|
+
self.next = url_part
|
36
|
+
when META_PREV
|
37
|
+
self.prev = url_part
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
# When on the first page
|
43
|
+
self.next = response_headers[HEADER_NEXT]
|
44
|
+
self.last = response_headers[HEADER_LAST]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end # PageLinks
|
48
|
+
end # Paginater
|
49
|
+
end # Formatter
|
50
|
+
end # Grape
|
data/paginater.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'paginater/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "paginater"
|
8
|
+
gem.version = Paginater::VERSION
|
9
|
+
gem.platform = Gem::Platform::RUBY
|
10
|
+
gem.authors = ["zeroed"]
|
11
|
+
gem.description = "gem for foo pagination features in Grape"
|
12
|
+
gem.summary = %q{YAPG: yet another pagination gem}
|
13
|
+
gem.homepage = "https://github.com/zeroed/paginater"
|
14
|
+
gem.license = "MIT"
|
15
|
+
gem.rubyforge_project = "paginater"
|
16
|
+
|
17
|
+
gem.add_dependency 'bundler'
|
18
|
+
gem.add_dependency 'rake'
|
19
|
+
gem.add_dependency 'rspec'
|
20
|
+
gem.add_dependency 'rack-test'
|
21
|
+
gem.add_dependency 'rspec-rails'
|
22
|
+
gem.add_dependency 'cookiejar'
|
23
|
+
gem.add_dependency 'activerecord'
|
24
|
+
gem.add_dependency 'sqlite3'
|
25
|
+
|
26
|
+
gem.add_dependency 'grape'
|
27
|
+
|
28
|
+
gem.add_dependency 'kaminari' # , :require => 'kaminari/grape'
|
29
|
+
|
30
|
+
gem.add_development_dependency 'thin'
|
31
|
+
|
32
|
+
gem.files = `git ls-files`.split($/)
|
33
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
34
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
35
|
+
gem.require_paths = ["lib"]
|
36
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kaminari::PaginatableArray do
|
4
|
+
it { should have(0).items }
|
5
|
+
|
6
|
+
context 'specifying limit and offset when initializing' do
|
7
|
+
subject { Kaminari::PaginatableArray.new((1..100).to_a, :limit => 10, :offset => 20) }
|
8
|
+
its(:current_page) { should == 3 }
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:array) { Kaminari::PaginatableArray.new((1..100).to_a) }
|
12
|
+
describe '#page' do
|
13
|
+
shared_examples_for 'the first page of array' do
|
14
|
+
it { should have(25).users }
|
15
|
+
its(:current_page) { should == 1 }
|
16
|
+
its(:first) { should == 1 }
|
17
|
+
end
|
18
|
+
|
19
|
+
shared_examples_for 'blank array page' do
|
20
|
+
it { should have(0).items }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'page 1' do
|
24
|
+
subject { array.page 1 }
|
25
|
+
it_should_behave_like 'the first page of array'
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'page 2' do
|
29
|
+
subject { array.page 2 }
|
30
|
+
it { should have(25).users }
|
31
|
+
its(:current_page) { should == 2 }
|
32
|
+
its(:first) { should == 26 }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'page without an argument' do
|
36
|
+
subject { array.page }
|
37
|
+
it_should_behave_like 'the first page of array'
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'page < 1' do
|
41
|
+
subject { array.page 0 }
|
42
|
+
it_should_behave_like 'the first page of array'
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'page > max page' do
|
46
|
+
subject { array.page 5 }
|
47
|
+
it_should_behave_like 'blank array page'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#per' do
|
52
|
+
context 'page 1 per 5' do
|
53
|
+
subject { array.page(1).per(5) }
|
54
|
+
it { should have(5).users }
|
55
|
+
its(:first) { should == 1 }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#total_pages' do
|
60
|
+
context 'per 25 (default)' do
|
61
|
+
subject { array.page }
|
62
|
+
its(:total_pages) { should == 4 }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'per 7' do
|
66
|
+
subject { array.page(2).per(7) }
|
67
|
+
its(:total_pages) { should == 15 }
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'per 65536' do
|
71
|
+
subject { array.page(50).per(65536) }
|
72
|
+
its(:total_pages) { should == 1 }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'per 0 (using default)' do
|
76
|
+
subject { array.page(50).per(0) }
|
77
|
+
its(:total_pages) { should == 4 }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'per -1 (using default)' do
|
81
|
+
subject { array.page(5).per(-1) }
|
82
|
+
its(:total_pages) { should == 4 }
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'per "String value that can not be converted into Number" (using default)' do
|
86
|
+
subject { array.page(5).per('aho') }
|
87
|
+
its(:total_pages) { should == 4 }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#current_page' do
|
92
|
+
context 'page 1' do
|
93
|
+
subject { array.page }
|
94
|
+
its(:current_page) { should == 1 }
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'page 2' do
|
98
|
+
subject { array.page(2).per 3 }
|
99
|
+
its(:current_page) { should == 2 }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#count' do
|
104
|
+
context 'page 1' do
|
105
|
+
subject { array.page }
|
106
|
+
its(:count) { should == 25 }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'page 2' do
|
110
|
+
subject { array.page 2 }
|
111
|
+
its(:count) { should == 25 }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when setting total count explicitly' do
|
116
|
+
subject { Kaminari::PaginatableArray.new((1..10).to_a, :total_count => 9999).page(5).per(10) }
|
117
|
+
it { should have(10).items }
|
118
|
+
its(:first) { should == 1 }
|
119
|
+
its(:total_count) { should == 9999 }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|