simplificator-ruby-kiva 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 pascalbetz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,59 @@
1
+ = simplificator-ruby-kiva
2
+
3
+ == What
4
+ This is a ruby wrapper for the kiva.org api.
5
+ See build.kiva.org for details.
6
+
7
+ == Howtow
8
+
9
+ include Kiva
10
+
11
+ Api.app_id = 'your api id here, not required but recommended, see build.kiva.org'
12
+ ### Loans
13
+ ## finding Loans by ID
14
+
15
+ #puts Loan.find(:id => 180009).terms
16
+ #puts Loan.find(:id => '180009, 180008').size
17
+ #puts Loan.find(:id => [180009, '180008']).size
18
+
19
+ ## finding Loans by lender_id
20
+ #puts Loan.find(:lender_id => 'matt', :page => 1).inspect
21
+
22
+ ## finding Loans
23
+ #puts Loan.find(:query => 'Chinchilla', :page => 1).inspect
24
+ #puts Loan.find(:partner_id => [150, 156, 188, 190, 120], :query => 'Vegetable', :status => :in_repayment, :page => 2).inspect
25
+
26
+ ### Partners
27
+
28
+ #puts Partner.find().first.inspect
29
+ #puts Partner.find(:id => 1).inspect
30
+
31
+
32
+ ### Lending Actions
33
+ #LendingAction.recent.first
34
+
35
+
36
+ ### Journal Entries
37
+ #puts JournalEntry.find(:loan_id => 180009, :include_bulk => true, :page => 10).inspect
38
+ #puts Loan.find(:id => 180009).journal_entries(:include_bulk => true).first.comments
39
+ #puts Comment.find(:journal_entry_id => 28342).inspect
40
+
41
+
42
+ ### Team
43
+ #puts Team.find(:id => 2).team_since
44
+ #puts Team.find(:shortname => 'buildkiva')
45
+ #puts Team.find(:id => 6341).lenders(:page => 1).inspect
46
+
47
+ == Note on Patches/Pull Requests
48
+
49
+ * Fork the project.
50
+ * Make your feature addition or bug fix.
51
+ * Add tests for it. This is important so I don't break it in a
52
+ future version unintentionally.
53
+ * Commit, do not mess with rakefile, version, or history.
54
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
55
+ * Send me a pull request. Bonus points for topic branches.
56
+
57
+ == Copyright
58
+
59
+ Copyright (c) 2010 pascalbetz. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "simplificator-ruby-kiva"
8
+ gem.summary = %Q{a ruby wrapper for the kiva.org api}
9
+ gem.description = %Q{a ruby wrapper for the kiva.org api}
10
+ gem.email = "info@simplificator.com"
11
+ gem.homepage = "http://github.com/simplificator/simplificator-ruby-kiva"
12
+ gem.authors = ["simplificator"]
13
+ gem.add_development_dependency "shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "simplificator-ruby-kiva #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,55 @@
1
+ module Kiva
2
+ module Api
3
+ def self.app_id=(value)
4
+ @@app_id = value
5
+ end
6
+ def self.app_id
7
+ @@app_id
8
+ end
9
+
10
+
11
+ module ClassMethods
12
+ private
13
+ def json_to_paged_array(data, data_key, many)
14
+ if many
15
+ if data[data_key]
16
+ PagedArray.new(data[data_key].map { |item| self.new(item)}, data['paging'] || {})
17
+ else
18
+ PagedArray.new([], {})
19
+ end
20
+ else
21
+ if data[data_key]
22
+ self.new(data[data_key].first)
23
+ else
24
+ nil
25
+ end
26
+ end
27
+ end
28
+
29
+ def base_options(options = {})
30
+ if Api.app_id
31
+ {:app_id => Api.app_id}.merge(pagination_options(options))
32
+ else
33
+ pagination_options(options)
34
+ end
35
+ end
36
+
37
+ def pagination_options(options = {})
38
+ { :page => options[:page] || 1}
39
+ end
40
+
41
+ def sanitize_id_parameter(id)
42
+ Array(id).join(',').gsub(/\s/, '')
43
+ end
44
+ end
45
+
46
+
47
+ def self.included(receiver)
48
+ receiver.instance_eval do
49
+ extend ClassMethods
50
+ include HTTParty
51
+ base_uri 'http://api.kivaws.org/v1'
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ module Kiva
2
+ class Borrower
3
+ include DynamicInitializer
4
+ attr_accessor :gender, :pictured, :first_name, :last_name
5
+
6
+ def male?
7
+ self.gender == 'M'
8
+ end
9
+ def female?
10
+ self.gender == 'F'
11
+ end
12
+
13
+ def pictured?
14
+ self.pictured
15
+ end
16
+
17
+ def to_s
18
+ "<#{self.first_name} #{self.last_name} #{self.gender} #{self.pictured?}>"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Kiva
2
+ class Comment
3
+ include DynamicInitializer
4
+ include Api
5
+ attr_accessor :id, :author, :whereabouts, :body, :date
6
+ typed_attr_accessor :date, Time, :parse
7
+
8
+ def self.find(params)
9
+ if params[:journal_entry_id]
10
+ json_to_paged_array(get("/journal_entries/#{params[:journal_entry_id]}/comments.json", :query => base_options(params)), 'comments', true)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Kiva
2
+ class Country
3
+ include DynamicInitializer
4
+ attr_accessor :country_code, :name, :region, :iso_code
5
+
6
+ typed_attr_accessor :location, Kiva::Location
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ module Kiva
2
+ module DynamicInitializer
3
+ module ClassMethods
4
+ def typed_attr_accessor(name, klass, factory_method = :new, array = false)
5
+ define_method("#{name}=") do |value|
6
+ if array
7
+ typed = value.map() {|item| klass.send(factory_method, item)}
8
+ else
9
+ typed = klass.send(factory_method, value)
10
+ end
11
+ instance_variable_set("@#{name}", typed)
12
+ end
13
+ attr_reader name
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def initialize(options = {})
19
+ options.each do |key, value|
20
+ self.send("#{key}=", value)
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.included(receiver)
26
+ receiver.extend ClassMethods
27
+ receiver.send :include, InstanceMethods
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module Kiva
2
+ class Image
3
+ include DynamicInitializer
4
+ attr_accessor :template_id, :id
5
+
6
+ SIZES = %w(w80h80 w200h200 w325h250 w450h360 fullsize)
7
+
8
+ def to_s
9
+ "<Image #{self.template_id} / #{self.id}>"
10
+ end
11
+
12
+ # Build the URL for this Image.
13
+ # See SIZES for valid sizes, defaults to w80h80
14
+ def url(size = 'w80h80')
15
+ raise 'Must have a template id and an id' unless self.template_id && self.id
16
+ raise "Unknown size #{size}" unless SIZES.include?(size.to_s)
17
+ "http://www.kiva.org/img/#{size}/#{self.id}.jpg"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module Kiva
2
+ class JournalEntry
3
+ include DynamicInitializer
4
+ include Api
5
+ attr_accessor :id, :body, :subject, :author, :bulk, :comment_count, :recommendation_count
6
+
7
+ typed_attr_accessor :date, Time, :parse
8
+
9
+ def bulk?
10
+ self.bulk
11
+ end
12
+
13
+ def self.find(params)
14
+ if params[:loan_id]
15
+ json_to_paged_array(get("/loans/#{params[:loan_id]}/journal_entries.json",
16
+ :query => base_options.merge(find_options(params))), 'journal_entries', true)
17
+ end
18
+ end
19
+
20
+ def comments(params = {})
21
+ @comments ||= Comment.find(params.merge({:journal_entry_id => self.id}))
22
+ end
23
+
24
+ private
25
+
26
+ def self.find_options(options)
27
+ result = {}
28
+ result['include_bulk'] = options[:include_bulk] ? 1 : 0
29
+ result
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,66 @@
1
+ module Kiva
2
+ class Lender
3
+ include Api
4
+ include DynamicInitializer
5
+ attr_accessor :loan_count, :occupation, :country_code, :name, :loan_because,
6
+ :lender_id, :uid, :whereabouts, :invitee_count,
7
+ :occupational_info, :personal_url
8
+
9
+ typed_attr_accessor :member_since, Time, :parse
10
+ typed_attr_accessor :team_join_date, Time, :parse
11
+ typed_attr_accessor :image, Kiva::Image
12
+
13
+ def lender_page_url
14
+ "http://www.kiva.org/lender/#{self.lender_id}"
15
+ end
16
+
17
+ # find the loans for this lender
18
+ # loans are cached to avoid roundtrips, refetch the lender if you need to refresh loans
19
+ def loans(params = {})
20
+ @loans ||= Loan.find(params.merge({:lender_id => self.lender_id}))
21
+ end
22
+
23
+ def to_s()
24
+ "<Lender #{self.lender_id}>"
25
+ end
26
+
27
+ # Find Lenders
28
+ # __either__
29
+ # :id : A single ID or a comma Separated String of IDs or an Array of IDs
30
+ # __or__
31
+ # :loan_id : A single loan ID
32
+ # __or__
33
+ # :team_id : a single team ID
34
+ #
35
+ # If searching for a single ID, then this method can return nil (no item with this ID found).
36
+ # Otherwise this method will always return a PagedArray instance (which might be empty though)
37
+ # If querying for multiple items, then :page is supported
38
+ def self.find(params)
39
+ if params[:id] # find one or many by lender ID
40
+ data = get("/lenders/#{sanitize_id_parameter(params[:id])}.json", :query => base_options(params))
41
+ many = sanitize_id_parameter(params[:id]).include?(',')
42
+ elsif params[:loan_id] # find lenders for a loan
43
+ data = get("/loans/#{params[:loan_id]}/lenders.json", :query => base_options(params))
44
+ many = true
45
+ elsif params[:team_id]
46
+ data = get("/teams/#{params[:team_id]}/lenders.json", :query => base_options(params))
47
+ many = true
48
+ end
49
+ json_to_paged_array(data, 'lenders', many)
50
+ end
51
+ end
52
+ end
53
+
54
+ # {"loan_count"=>93,
55
+ # "occupation"=>"Entrepreneur",
56
+ # "country_code"=>"US",
57
+ # "name"=>"Matt",
58
+ # "loan_because"=>"I love the stories. ",
59
+ # "lender_id"=>"matt",
60
+ # "invitee_count"=>23,
61
+ # "occupational_info"=>"I co-founded a startup nonprofit (this one!) and I work with an amazing group of people dreaming up ways to alleviate poverty through personal lending. ",
62
+ # "personal_url"=>"www.socialedge.org/blogs/kiva-chronicles",
63
+ # "uid"=>"matt",
64
+ # "whereabouts"=>"San Francisco CA",
65
+ # "image"=>{"template_id"=>1, "id"=>12829},
66
+ # "member_since"=>"2006-01-01T09:01:01Z"}
@@ -0,0 +1,25 @@
1
+ module Kiva
2
+ class LendingAction
3
+ include Api
4
+ include DynamicInitializer
5
+
6
+ attr_accessor :id, :sector, :basket_amount
7
+
8
+ typed_attr_accessor :date, Time, :parse
9
+ typed_attr_accessor :lender, Kiva::Lender
10
+ typed_attr_accessor :loan, Kiva::Loan
11
+
12
+
13
+ def self.recent(options = {})
14
+ json_to_paged_array(get('/lending_actions/recent.json'), 'lending_actions', true)
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ #{"date"=>"2010-04-09T22:00:27Z",
21
+ #{}"id"=>33614166,
22
+ #{}"lender"=>{"country_code"=>"US", "name"=>"Eisakouo Partnership", "lender_id"=>"eisakouopartnership3932", "uid"=>"eisakouopartnership3932", "whereabouts"=>"Columbia PA", "image"=>{"template_id"=>1, "id"=>482823}},
23
+ #{}"loan"=>{"borrower_count"=>1, "status"=>"fundraising", "name"=>"Laoy Lok", "posted_date"=>"2010-04-07T06:40:02Z", "activity"=>"Cattle", "id"=>191433, "description"=>{"languages"=>["en"]}, "partner_id"=>109, "use"=>"To buy a cow for breeding", "loan_amount"=>300, "funded_amount"=>0, "image"=>{"template_id"=>1, "id"=>520519}, "location"=>{"country_code"=>"KH", "country"=>"Cambodia", "geo"=>{"type"=>"point", "level"=>"country", "pairs"=>"13 105"}, "town"=>"Rung Village"},
24
+ #{}"sector"=>"Agriculture",
25
+ #{}"basket_amount"=>0}}
@@ -0,0 +1,109 @@
1
+ module Kiva
2
+ class Loan
3
+ include Api
4
+ include DynamicInitializer
5
+
6
+ attr_accessor :name, :activity, :use, :borrower_count,
7
+ :status, :id, :partner_id, :description,
8
+ :loan_amount, :funded_amount, :paid_amount, :basket_amount, :sector,
9
+ :delinquent, :journal_totals, :terms, :funded_date
10
+
11
+ typed_attr_accessor :location, Kiva::Location
12
+ typed_attr_accessor :image, Kiva::Image
13
+ typed_attr_accessor :video, Kiva::Video
14
+ typed_attr_accessor :terms, Kiva::Terms
15
+ typed_attr_accessor :posted_date, Date, :parse
16
+ typed_attr_accessor :borrowers, Kiva::Borrower, :new, true
17
+
18
+ # description consists of available languages and texts in different languages
19
+ # texts are only available when doing a find by id
20
+ def description=(value)
21
+ @description = value
22
+ @description_languages = value['languages']
23
+ @description_texts = value['texts']
24
+ end
25
+
26
+ def description_languages
27
+ @description_languages
28
+ end
29
+
30
+ def description_texts
31
+ @description_texts
32
+ end
33
+
34
+ def description_text(language)
35
+ description_texts[language]
36
+ end
37
+
38
+ # Get the lenders for this loan
39
+ # Caches the lenders, reload loan if you need to refresh the lenders
40
+ def lenders(params = {})
41
+ @lenders ||= Lender.find(params.merge({:loan_id => self.id}))
42
+ end
43
+
44
+
45
+ def journal_entries(params = {})
46
+ @journal_entries ||= JournalEntry.find(params.merge({:loan_id => self.id}))
47
+ end
48
+
49
+
50
+ # Supported options:
51
+ # * :page: The requested page number
52
+ def self.recent(options = {})
53
+ data = get('/loans/newest.json', :query => base_options(options))
54
+ json_to_paged_array(data, 'loans', true)
55
+ end
56
+
57
+ # Search for Loans.
58
+ # Different searches are possible
59
+ # __either:__
60
+ # :id: An ID, a comma delimited String of IDs or an Array of IDs
61
+ # __or__
62
+ # :lender_id: A single ID of a lender
63
+ # __or__
64
+ # :team_id : a single ID of a team
65
+ # __or a combination of__
66
+ # * :partner_id: A (comma delimited) String of Partner IDs or an Array of partner IDs
67
+ # * :status: A String or symbol with the Loan status
68
+ # * :query: A String
69
+ #
70
+ # If searching for a single ID, then this method can return nil (no item with this ID found).
71
+ # Otherwise this method will always return a PagedArray instance (which might be empty though)
72
+ #
73
+ # When searching for multiple Loans, then the :page parameter is supported to specify the desired page.
74
+ def self.find(params)
75
+ if params[:id] # find one or many by ID
76
+ data = get("/loans/#{sanitize_id_parameter(params[:id])}.json", :query => base_options(params))
77
+ many = sanitize_id_parameter(params[:id]).include?(',')
78
+ elsif params[:lender_id] # find all loans for a lender
79
+ data = get("/lenders/#{params[:lender_id]}/loans.json", :query => base_options(params))
80
+ many = true
81
+ elsif params[:team_id] # find all loans for a team
82
+ data = get("/teams/#{params[:team_id]}/loans.json", :query => base_options(params))
83
+ many = true
84
+ else # search
85
+ data = get('/loans/search.json', :query => base_options(params).merge(find_options(params)))
86
+ many = true
87
+ end
88
+ json_to_paged_array(data, 'loans', many)
89
+ end
90
+
91
+ def to_s()
92
+ "<Loan '%s' (%s) in %s with status %s, from %s>" % [self.name, self.id, self.location, self.status, self.borrowers]
93
+ end
94
+
95
+ private
96
+
97
+ def self.find_options(options = {})
98
+ result = {}
99
+ if options[:partner_id]
100
+ result[:partner] = sanitize_id_parameter(options[:partner_id])
101
+ end
102
+ result[:status] = options[:status] if options[:status]
103
+ result[:q] = options[:query] if options[:query]
104
+ result
105
+ end
106
+
107
+
108
+ end
109
+ end
@@ -0,0 +1,67 @@
1
+ module Kiva
2
+ class Location
3
+ include DynamicInitializer
4
+ attr_accessor :country, :geo, :town, :type, :level
5
+ attr_accessor :country_code
6
+
7
+ def geo=(value)
8
+ self.type = value['type']
9
+ self.level = value['level']
10
+ self.pairs = value['pairs']
11
+ end
12
+
13
+ def pairs=(value)
14
+ @pairs = value.split(' ').map() {|item| item.to_f}
15
+ end
16
+
17
+ def pairs
18
+ @pairs.join(' ')
19
+ end
20
+
21
+
22
+ # if this is a point?, then returns lat else nil
23
+ def lat
24
+ @pairs[0] if self.point?
25
+ end
26
+
27
+ # if this is a point?, then returns lat else nil
28
+ def lng
29
+ @pairs[1] if self.point?
30
+ end
31
+
32
+ # true if level is 'town'
33
+ def town?
34
+ 'town' == self.level
35
+ end
36
+ # true if level is 'country'
37
+ def country?
38
+ 'country' == self.level
39
+ end
40
+ # true if level 'exact'
41
+ def exact?
42
+ 'exact' == self.level
43
+ end
44
+
45
+ # true if type 'point'
46
+ def point?
47
+ 'point' == self.type
48
+ end
49
+ # true if type 'line'
50
+ def line?
51
+ 'line' == self.type
52
+ end
53
+ # true if type 'box'
54
+ def box?
55
+ 'box' == self.type
56
+ end
57
+ # true if type 'polygon'
58
+ def polygon?
59
+ 'polygon' == self.type
60
+ end
61
+
62
+ def to_s
63
+ "<Location '%s' '%s' %f/%f>" % [self.country, self.town, self.lat || 0, self.lng || 0]
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,66 @@
1
+ module Kiva
2
+ # Adds pagination information to array
3
+ # Pagination attributes/methods have the same names as the wonderful will_paginate
4
+ class PagedArray < Array
5
+ attr_reader :current_page, :per_page, :total_entries, :total_pages
6
+
7
+ def initialize(data, options = {})
8
+ super data
9
+ @current_page = options['page'] || 1
10
+ @per_page = options['page_size'] || data.size
11
+ @total_pages = options['pages'] || 1
12
+ @total_entries = options['total'] || data.size
13
+ end
14
+
15
+ def offset
16
+ (self.current_page - 1) * self.per_page
17
+ end
18
+
19
+ def next_page?()
20
+ self.current_page < self.total_pages
21
+ end
22
+
23
+ def next_page
24
+ self.current_page + 1 if next_page?
25
+ end
26
+
27
+ def previous_page
28
+ self.current_page - 1 if previous_page?
29
+ end
30
+
31
+ def previous_page?()
32
+ self.current_page > 1
33
+ end
34
+
35
+ def from_entry
36
+ self.offset + 1 unless self.out_of_bounds?
37
+ end
38
+
39
+ def out_of_bounds?
40
+ self.current_page < 1 || self.current_page > self.total_pages
41
+ end
42
+
43
+ def to_entry
44
+ [self.offset + self.per_page, self.total_entries].min unless self.out_of_bounds?
45
+ end
46
+
47
+ def inspect
48
+ "#{pagination_info}\n#{super}"
49
+ end
50
+
51
+ def to_s
52
+ self.inspect
53
+ end
54
+
55
+ private
56
+
57
+
58
+ def pagination_info
59
+ if out_of_bounds?
60
+ "<Page is out of bounds>"
61
+ else
62
+ "<Page #{self.current_page} of #{self.total_pages} (entries #{self.from_entry} - #{self.to_entry} of #{self.total_entries}). Previous? #{self.previous_page?}, Next? #{self.next_page?}>"
63
+ end
64
+ end
65
+ end
66
+ end