pageboy 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/pageboy.rb +39 -0
- data/lib/pageboy/collator.rb +19 -0
- data/lib/pageboy/config.rb +18 -0
- data/lib/pageboy/page.rb +28 -0
- data/lib/pageboy/page_turner/array.rb +24 -0
- data/lib/pageboy/page_turner/base.rb +36 -0
- data/lib/pageboy/page_turner/link_header.rb +39 -0
- data/lib/pageboy/version.rb +3 -0
- data/pageboy.gemspec +33 -0
- data/spec/collator_spec.rb +21 -0
- data/spec/link_header_spec.rb +41 -0
- data/spec/pageboy_spec.rb +27 -0
- metadata +173 -0
data/lib/pageboy.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'pageboy/config'
|
2
|
+
require 'pageboy/collator'
|
3
|
+
require 'pageboy/page'
|
4
|
+
require 'pageboy/page_turner/array'
|
5
|
+
require 'pageboy/page_turner/link_header'
|
6
|
+
|
7
|
+
module Pageboy
|
8
|
+
def self.paginate(target, opts={})
|
9
|
+
Pageboy::Config.turners.each_pair do |page_turner_class, matchers|
|
10
|
+
matchers.each do |matcher|
|
11
|
+
return page_turner_class.new(target, opts) if matcher.call(target)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
raise "could not find suitable PageTurner for #{target}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.each(target, opts={}, &block)
|
18
|
+
page_turner = paginate(target, opts)
|
19
|
+
Pageboy::Collator(page_turner).each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.map(target, opts={}, &block)
|
23
|
+
page_turner = paginate(target, opts)
|
24
|
+
Pageboy::Collator(page_turner).map(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.register_page_turner(klass, &block)
|
28
|
+
Pageboy::Config.turners[klass] ||= []
|
29
|
+
Pageboy::Config.turners[klass] << block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Pageboy.register_page_turner(Pageboy::PageTurner::Array) do |object|
|
34
|
+
object.respond_to?(:[]) && object.respond_to?(:size)
|
35
|
+
end
|
36
|
+
|
37
|
+
Pageboy.register_page_turner(Pageboy::PageTurner::LinkHeader) do |object|
|
38
|
+
object.is_a?(String) && object.include?('/api/')
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pageboy
|
2
|
+
class Collator
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(page_turner)
|
6
|
+
@page_turner = page_turner
|
7
|
+
end
|
8
|
+
|
9
|
+
def each(&block)
|
10
|
+
page = @page_turner.first
|
11
|
+
while page
|
12
|
+
page.items.each(&block)
|
13
|
+
page = page.next
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Pageboy::Collator.new(page_turner).each { |item| }
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
|
3
|
+
module Pageboy
|
4
|
+
module Config
|
5
|
+
include ActiveSupport::Configurable
|
6
|
+
config_accessor :per_page
|
7
|
+
config_accessor :turners
|
8
|
+
|
9
|
+
def configure
|
10
|
+
yield self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Pageboy::Config.configure do |config|
|
16
|
+
config.per_page = 25
|
17
|
+
config.turners = {}
|
18
|
+
end
|
data/lib/pageboy/page.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Pageboy
|
2
|
+
class Page
|
3
|
+
attr_reader :items
|
4
|
+
|
5
|
+
def initialize(turner, items, prev_page_id, next_page_id)
|
6
|
+
@turner = turner
|
7
|
+
@items = items
|
8
|
+
@prev_page_id = prev_page_id
|
9
|
+
@next_page_id = next_page_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def prev
|
13
|
+
@turner.page(@prev_page_id) if @prev_page_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def next
|
17
|
+
@turner.page(@next_page_id) if @next_page_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_a
|
21
|
+
@items
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(&block)
|
25
|
+
@items.each(&block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'pageboy/page_turner/base'
|
2
|
+
|
3
|
+
module Pageboy
|
4
|
+
module PageTurner
|
5
|
+
class Array < Base
|
6
|
+
|
7
|
+
def page(page_id, opts={})
|
8
|
+
with_merged_options(opts) do |o|
|
9
|
+
per_page = Integer(o[:per_page])
|
10
|
+
i = Integer(page_id)
|
11
|
+
max_page = (resource.size.to_f / per_page).ceil
|
12
|
+
prev_page_id = i - 1 < 1 ? nil : i - 1
|
13
|
+
next_page_id = i + 1 > max_page ? nil : i + 1
|
14
|
+
|
15
|
+
first_item_index = (i-1) * per_page
|
16
|
+
last_item_index = i * per_page - 1
|
17
|
+
slice = resource[first_item_index..last_item_index]
|
18
|
+
Page.new(self, slice, prev_page_id, next_page_id)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'pageboy/page'
|
2
|
+
require 'pageboy/config'
|
3
|
+
|
4
|
+
module Pageboy
|
5
|
+
module PageTurner
|
6
|
+
class Base
|
7
|
+
attr_accessor :resource
|
8
|
+
|
9
|
+
def initialize(resource, opts={})
|
10
|
+
@resource = resource
|
11
|
+
@opts = opts
|
12
|
+
end
|
13
|
+
|
14
|
+
# Can be overridden in subclasses
|
15
|
+
def first_page_id
|
16
|
+
# Some PageTurners use integers, others use GUIDs etc.,
|
17
|
+
# but page numbers are most common, so default to 1.
|
18
|
+
1
|
19
|
+
end
|
20
|
+
|
21
|
+
# Should return a Pageboy::Page object
|
22
|
+
def page(page_id, opts={})
|
23
|
+
raise NotImplementedError,
|
24
|
+
"'page' should be implemented in subclasses"
|
25
|
+
end
|
26
|
+
|
27
|
+
def first(opts={})
|
28
|
+
page(first_page_id, opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_merged_options(opts={})
|
32
|
+
yield Pageboy::Config.config.merge(@opts).merge(opts)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "link_header"
|
2
|
+
require "pageboy/page_turner/base"
|
3
|
+
|
4
|
+
module Pageboy
|
5
|
+
module PageTurner
|
6
|
+
class LinkHeader < Base
|
7
|
+
|
8
|
+
def initialize(url, opts={})
|
9
|
+
super
|
10
|
+
@url = url
|
11
|
+
@connection = opts.delete(:connection) or
|
12
|
+
fail("LinkHeader page turner requires :connection")
|
13
|
+
@first_page_id = url.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
def first_page_id
|
17
|
+
@first_page_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_link(header, rel)
|
21
|
+
link = ::LinkHeader.parse(header).links.find{ |link| link['rel'] == rel }
|
22
|
+
link.to_a.first if link
|
23
|
+
end
|
24
|
+
|
25
|
+
def page(url, opts={})
|
26
|
+
with_merged_options(opts) do |o|
|
27
|
+
response = @connection.get(url)
|
28
|
+
header = response.headers[:link]
|
29
|
+
# require 'debugger';debugger
|
30
|
+
next_page = find_link(header, 'next')
|
31
|
+
slice = response.body
|
32
|
+
|
33
|
+
Page.new(self, slice, nil, next_page)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/pageboy.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pageboy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.authors = ["Duane Johnson"]
|
8
|
+
gem.email = ["duane@instructure.com"]
|
9
|
+
gem.description = %q{Paginator}
|
10
|
+
gem.summary = %q{Paginator for collections of unknown size}
|
11
|
+
|
12
|
+
gem.files = %w[pageboy.gemspec]
|
13
|
+
gem.files += Dir.glob("lib/**/*.rb")
|
14
|
+
gem.files += Dir.glob("spec/**/*")
|
15
|
+
gem.test_files = Dir.glob("spec/**/*")
|
16
|
+
gem.name = "pageboy"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = Pageboy::VERSION
|
19
|
+
|
20
|
+
gem.add_development_dependency "bundler", ">= 1.0.0"
|
21
|
+
gem.add_development_dependency "rspec", "~> 2.6"
|
22
|
+
gem.add_development_dependency "webmock"
|
23
|
+
gem.add_development_dependency "debugger"
|
24
|
+
|
25
|
+
# Used for configuration on modules and classes
|
26
|
+
gem.add_dependency "activesupport", ">= 3.0.0"
|
27
|
+
|
28
|
+
# HTTP library
|
29
|
+
gem.add_dependency "faraday", "~> 0.8.8"
|
30
|
+
|
31
|
+
# Parses Link headers formatted according to RFC 5988 draft spec
|
32
|
+
gem.add_dependency "link_header", ">= 0.0.7"
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'pageboy/page_turner/array'
|
2
|
+
require 'pageboy/collator'
|
3
|
+
|
4
|
+
describe Pageboy::Collator do
|
5
|
+
let(:array) { %w(some words to paginate) }
|
6
|
+
let(:page_turner) { Pageboy::PageTurner::Array.new(array, :per_page => 3) }
|
7
|
+
let(:collator) { Pageboy::Collator.new(page_turner) }
|
8
|
+
|
9
|
+
it "collates with each" do
|
10
|
+
items = []
|
11
|
+
collator.each do |item|
|
12
|
+
items << item
|
13
|
+
end
|
14
|
+
items.should == %w(some words to paginate)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "collates with map" do
|
18
|
+
collator.map { |item| item + '.' }.
|
19
|
+
should == %w(some. words. to. paginate.)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'faraday'
|
3
|
+
require 'pageboy/page_turner/link_header'
|
4
|
+
|
5
|
+
describe Pageboy do
|
6
|
+
context "for an API with link headers" do
|
7
|
+
let(:connection) { Faraday.new }
|
8
|
+
let(:url) { 'http://canvas.dev/api/v1/thing' }
|
9
|
+
let(:link_header) do
|
10
|
+
Pageboy::PageTurner::LinkHeader.new(url,
|
11
|
+
:connection => connection, :per_page => 3)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "paginates a Link header based API" do
|
15
|
+
next_link = %{<http://canvas.dev/api/v1/thing?page=2>; rel="next"}
|
16
|
+
|
17
|
+
# First page
|
18
|
+
stub_request(:get, "http://canvas.dev/api/v1/thing").
|
19
|
+
to_return(
|
20
|
+
:status => 200,
|
21
|
+
:body => [{"name" => "test1"}],
|
22
|
+
:headers => {"Link" => next_link})
|
23
|
+
page = link_header.first
|
24
|
+
page.items.should == [{"name" => "test1"}]
|
25
|
+
|
26
|
+
# Second page
|
27
|
+
stub_request(:get, "http://canvas.dev/api/v1/thing?page=2").
|
28
|
+
to_return(
|
29
|
+
:status => 200,
|
30
|
+
:body => [{"name" => "test2"}],
|
31
|
+
:headers => {})
|
32
|
+
page = page.next
|
33
|
+
page.items.should == [{"name" => "test2"}]
|
34
|
+
|
35
|
+
# No more pages
|
36
|
+
page = page.next
|
37
|
+
page.should be_nil
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../lib/pageboy'
|
2
|
+
|
3
|
+
describe Pageboy do
|
4
|
+
context "for an array" do
|
5
|
+
let(:array) { %w(this is an array with words in it) }
|
6
|
+
let(:turner) { Pageboy::PageTurner::Array.new(array, :per_page => 3) }
|
7
|
+
|
8
|
+
it "paginates an array" do
|
9
|
+
turner.first.prev.should be_nil
|
10
|
+
turner.first.to_a.should == %w(this is an)
|
11
|
+
turner.first.next.to_a.should == %w(array with words)
|
12
|
+
turner.first.next.next.to_a.should == %w(in it)
|
13
|
+
turner.first.next.next.next.should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can turn to any page" do
|
17
|
+
turner.page(2).to_a.should == %w(array with words)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "matching for page turners" do
|
22
|
+
it "chooses PageTurner::Array for arrays" do
|
23
|
+
turner = Pageboy.paginate([])
|
24
|
+
turner.should be_a(Pageboy::PageTurner::Array)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pageboy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Duane Johnson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.6'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.6'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: webmock
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: debugger
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: activesupport
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 3.0.0
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 3.0.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: faraday
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.8.8
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.8.8
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: link_header
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.0.7
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.0.7
|
126
|
+
description: Paginator
|
127
|
+
email:
|
128
|
+
- duane@instructure.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- pageboy.gemspec
|
134
|
+
- lib/pageboy/collator.rb
|
135
|
+
- lib/pageboy/config.rb
|
136
|
+
- lib/pageboy/page.rb
|
137
|
+
- lib/pageboy/page_turner/array.rb
|
138
|
+
- lib/pageboy/page_turner/base.rb
|
139
|
+
- lib/pageboy/page_turner/link_header.rb
|
140
|
+
- lib/pageboy/version.rb
|
141
|
+
- lib/pageboy.rb
|
142
|
+
- spec/collator_spec.rb
|
143
|
+
- spec/link_header_spec.rb
|
144
|
+
- spec/pageboy_spec.rb
|
145
|
+
homepage:
|
146
|
+
licenses: []
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ! '>='
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 1.8.23
|
166
|
+
signing_key:
|
167
|
+
specification_version: 3
|
168
|
+
summary: Paginator for collections of unknown size
|
169
|
+
test_files:
|
170
|
+
- spec/collator_spec.rb
|
171
|
+
- spec/link_header_spec.rb
|
172
|
+
- spec/pageboy_spec.rb
|
173
|
+
has_rdoc:
|