pageboy 0.1
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.
- 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:
|