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.
@@ -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
+