paginater 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Paginater
2
+ VERSION = '0.0.5'
3
+ end
@@ -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
+