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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Pageboy
2
+ VERSION = '0.1'
3
+ end
@@ -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: