goodreads_17up 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ed19533305d447d2c1378fc490cf2bd7daf0a80
4
+ data.tar.gz: 01b6e36b14bb6720d122a0dfdf01dc42a254ecff
5
+ SHA512:
6
+ metadata.gz: 6e6f8b2e4291aea73fc0fb9d8ec3ec86d30ae13ba8d702a0d64f5095f006bab2aec161cb21baf46279aee254d54f9ef4231f8ea2e34cfa231d92be28f1805c53
7
+ data.tar.gz: 5168872ba353b8ecff0920de10392eee28e7bf38badd5c89691fb6a246eb19a34c4efc30b633b4856e4ee2c358de6bd2f81670dbbf7ab83011f418b3d8449b9f
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format=nested
3
+ --backtrace
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - ree
5
+ - 1.9.3
6
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # Goodreads [![Build Status](https://secure.travis-ci.org/sosedoff/goodreads.png)](http://travis-ci.org/sosedoff/goodreads)
2
+
3
+ Ruby wrapper to communicate with Goodreads API.
4
+
5
+ ## Installation
6
+
7
+ Install gem with rubygems:
8
+
9
+ ```
10
+ gem install goodreads
11
+ ```
12
+
13
+ Or manually:
14
+
15
+ ```
16
+ rake install
17
+ ```
18
+
19
+ ## Getting Started
20
+
21
+ Before using Goodreads API you must create a new application. Visit [signup form](http://www.goodreads.com/api/keys) for details.
22
+
23
+ Setup client:
24
+
25
+ ``` ruby
26
+ client = Goodreads::Client.new(:api_key => 'KEY', :api_secret => 'SECRET')
27
+ client = Goodreads.new(:api_key => 'KEY') # short version
28
+ ```
29
+
30
+ ### Global configuration
31
+
32
+ You can define client credentials on global level. Just create an initializer file (if using rails) under
33
+ `config/initializers`:
34
+
35
+ ``` ruby
36
+ Goodreads.configure(
37
+ :api_key => 'KEY',
38
+ :api_secret => 'SECRET'
39
+ )
40
+ ```
41
+
42
+ Get global configuration:
43
+
44
+ ``` ruby
45
+ Goodreads.configuration # => {:api_key => 'YOUR_KEY'}
46
+ ```
47
+
48
+ In case you need to reset options:
49
+
50
+ ```ruby
51
+ Goodreads.reset_configuration
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ### Lookup books
57
+
58
+ You can lookup a book by ISBN, ID or Title:
59
+
60
+ ```ruby
61
+ client.book('id')
62
+ client.book_by_isbn('ISBN')
63
+ client.book_by_title('Book title')
64
+ ```
65
+
66
+ Search for books (by title, isbn, genre):
67
+
68
+ ```ruby
69
+ search = client.search_books('The Lord Of The Rings')
70
+
71
+ search.results.work.each do |book|
72
+ book.id # => book id
73
+ book.title # => book title
74
+ end
75
+ ```
76
+
77
+ ### Authors
78
+
79
+ Look up an author by their Goodreads Author ID:
80
+
81
+ ```ruby
82
+ author = client.author('id')
83
+
84
+ author.id # => author id
85
+ author.name # => author's name
86
+ author.link # => link to author's Goodreads page
87
+ author.fans_count # => number of fans author has on Goodreads
88
+ author.image_url # => link to image of the author
89
+ author.small_image_url # => link to smaller of the author
90
+ author.about # => description of the author
91
+ author.influences # => list of links to author's influences
92
+ author.works_count # => number of works by the author in Goodreads
93
+ author.gender # => author's gender
94
+ author.hometown # => author's hometown
95
+ author.born_at # => author's birthdate
96
+ author.died_at # => date of author's death
97
+ ```
98
+
99
+ Look up an author by name:
100
+
101
+ ```ruby
102
+ author = client.author_by_name('Author Name')
103
+
104
+ author.id # => author id
105
+ author.name # => author name
106
+ author.link # => link to author's Goodreads page
107
+ ```
108
+
109
+ ### Reviews
110
+
111
+ Pull recent reviews:
112
+
113
+ ```ruby
114
+ client.recent_reviews.each do |r|
115
+ r.id # => review id
116
+ r.book.title # => review book title
117
+ r.body # => review message
118
+ r.user.name # => review user name
119
+ end
120
+ ```
121
+
122
+ Get review details:
123
+
124
+ ```ruby
125
+ review = client.review('id')
126
+
127
+ review.id # => review id
128
+ review.user # => user information
129
+ review.book # => uook information
130
+ review.rating # => user rating
131
+ ```
132
+
133
+ ### Shelves
134
+
135
+ Get the books on a user's shelf:
136
+
137
+ ```ruby
138
+ shelf = client.shelf(user_id, shelf_name)
139
+
140
+ shelf.books # array of books on this shelf
141
+ shelf.start # start index of this page of paginated results
142
+ shelf.end # end index of this page of paginated results
143
+ shelf.total # total number of books on this shelf
144
+ ```
145
+
146
+ ### Groups
147
+
148
+ Get group details:
149
+
150
+ ```ruby
151
+ group = client.group('id')
152
+
153
+ group.id # => group id
154
+ group.title # => group title
155
+ group.access # => group's access settings
156
+ # => (e.g., public or private)
157
+ group.group_users_count # => number of users in the group
158
+ ```
159
+
160
+ List the groups a given user is a member of:
161
+
162
+ ```ruby
163
+ group_list = client.group_list('user_id', 'sort')
164
+
165
+ group_list.total # => total number of groups
166
+ group_list.group.count # => number of groups returned in the request
167
+
168
+ # Loop through the list to get details for each of the groups.
169
+
170
+ group_list.group.each do |g|
171
+ g.id # => group id
172
+ g.access # => access settings (private, public)
173
+ g.users_count # => number of members
174
+ g.title # => title
175
+ g.image_url # => url of the group's image
176
+ g.last_activity_at # => date and time of the group's last activity
177
+ end
178
+ ```
179
+
180
+ The `sort` parameter is optional, and defaults to `my_activity`. For other sorting options, [see here](http://www.goodreads.com/api#group.list).
181
+
182
+ ### OAuth
183
+
184
+ For API calls requiring permission, such as write operations or browsing friends, see our [OAuth tutorial](examples/oauth.md).
185
+
186
+ ## Testing
187
+
188
+ To run the test suite:
189
+
190
+ ```
191
+ rake test
192
+ ```
193
+
194
+ ## Contributions
195
+
196
+ You're welcome to submit patches and new features.
197
+
198
+ - Create a new branch for your feature of bugfix
199
+ - Add tests so it does not break any existing code
200
+ - Open a new pull request
201
+ - Check official [API documentation](http://www.goodreads.com/api)
202
+
203
+ ## License
204
+
205
+ Copyright © 2011-2013 Dan Sosedoff.
206
+
207
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
208
+
209
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
210
+
211
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
data/examples/oauth.md ADDED
@@ -0,0 +1,57 @@
1
+ # Authorizing Goodreads via OAuth
2
+
3
+ For services requiring permission, such as write operations or browsing friends, the client must be authorized through OAuth.
4
+
5
+ ## Request Tokens vs. Access Tokens
6
+
7
+ First, get an OAuth *request* token:
8
+
9
+ ```ruby
10
+ request_token = OAuth::Consumer.new(
11
+ Goodreads.configuration[:api_key],
12
+ Goodreads.configuration[:api_secret],
13
+ :site => 'http://www.goodreads.com'
14
+ ).get_request_token
15
+ ```
16
+
17
+ Next, authorize by opening the authorization URL in a browser:
18
+
19
+ ```ruby
20
+ request_token.authorize_url
21
+ ```
22
+
23
+ Then request an OAuth *access* token:
24
+
25
+ ```ruby
26
+ access_token = request_token.get_access_token
27
+ ```
28
+
29
+ Finally, initialize a Goodreads client with it:
30
+
31
+ ```ruby
32
+ goodreads_client = Goodreads.new :oauth_token => access_token
33
+ ```
34
+
35
+ For more info, see the [Goodreads documentation](http://www.goodreads.com/api/oauth_example).
36
+
37
+ ## User ID
38
+
39
+ Get the ID of the user who authorized via OAuth:
40
+
41
+ ```ruby
42
+ goodreads_client.user_id
43
+ ```
44
+
45
+ ## Friends
46
+
47
+ Get the friend details for a user:
48
+
49
+ ```ruby
50
+ friends_hash = goodreads_client.friends [user_id]
51
+ ```
52
+
53
+ Get a list of their names:
54
+
55
+ ```ruby
56
+ friends_hash.user.map{ |u| u.name }
57
+ ```
data/goodreads.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../lib/goodreads/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "goodreads_17up"
5
+ s.version = Goodreads::VERSION.dup
6
+ s.summary = "Goodreads API wrapper"
7
+ s.description = "Simple wrapper for the Goodreads API"
8
+ s.homepage = "http://github.com/sosedoff/goodreads"
9
+ s.authors = ["Dan Sosedoff"]
10
+ s.email = ["dan.sosedoff@gmail.com"]
11
+
12
+ s.add_development_dependency 'webmock', '~> 1.11'
13
+ s.add_development_dependency 'rake', '~> 0.9'
14
+ s.add_development_dependency 'rspec', '~> 2.12'
15
+ s.add_development_dependency 'simplecov', '~> 0.7'
16
+ s.add_development_dependency 'yard', '~> 0.6'
17
+
18
+ s.add_runtime_dependency 'rest-client', '~> 1.6'
19
+ s.add_runtime_dependency 'hashie', '~> 1.2'
20
+ s.add_runtime_dependency 'activesupport', '~> 3'
21
+ s.add_runtime_dependency 'i18n', '~> 0.5'
22
+ s.add_runtime_dependency 'oauth', '~> 0.4'
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
27
+ s.require_paths = ["lib"]
28
+ end
@@ -0,0 +1,9 @@
1
+ module Goodreads
2
+ module Authorized
3
+
4
+ def user_id
5
+ oauth_request('/api/auth_user')['user']['id']
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Goodreads
2
+ module Authors
3
+ # Get author details
4
+ #
5
+ def author(id, params={})
6
+ params[:id] = id
7
+ data = request('/author/show', params)
8
+ Hashie::Mash.new(data['author'])
9
+ end
10
+
11
+ # Search for an author by name
12
+ #
13
+ def author_by_name(name, params={})
14
+ params[:id] = name
15
+ data = request('/api/author_url', params)
16
+ Hashie::Mash.new(data['author'])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ module Goodreads
2
+ module Books
3
+ # Search for books
4
+ #
5
+ # query - Text to match against book title, author, and ISBN fields.
6
+ # options - Optional search parameters
7
+ #
8
+ # options[:page] - Which page to returns (default: 1)
9
+ # options[:field] - Search field. One of: title, author, or genre (default is all)
10
+ #
11
+ def search_books(query, params={})
12
+ params[:q] = query.to_s.strip
13
+ data = request('/search/index', params)
14
+ Hashie::Mash.new(data['search'])
15
+ end
16
+
17
+ # Get book details by Goodreads book ID
18
+ #
19
+ def book(id)
20
+ Hashie::Mash.new(request('/book/show', :id => id)['book'])
21
+ end
22
+
23
+ # Get book details by book ISBN
24
+ #
25
+ def book_by_isbn(isbn)
26
+ Hashie::Mash.new(request('/book/isbn', :isbn => isbn)['book'])
27
+ end
28
+
29
+ # Get book details by book title
30
+ #
31
+ def book_by_title(title)
32
+ Hashie::Mash.new(request('/book/title', :title => title)['book'])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module Goodreads
2
+ module Friends
3
+ # Get the specified user's friends
4
+ #
5
+ # user_id - integer or string
6
+ #
7
+ def friends(user_id)
8
+ data = oauth_request("/friend/user/#{user_id}")
9
+ Hashie::Mash.new(data['friends'])
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Goodreads
2
+ module Groups
3
+ # Get group details
4
+ def group(group_id)
5
+ data = request('/group/show', :id => group_id)
6
+ Hashie::Mash.new(data['group'])
7
+ end
8
+
9
+ # Get list of groups a given user is a member of
10
+ def group_list(user_id, sort='my_activity')
11
+ data = request('/group/list', :id => user_id, :sort => sort)
12
+ Hashie::Mash.new(data['groups']['list'])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Goodreads
2
+ module Reviews
3
+ # Recent reviews from all members.
4
+ #
5
+ # params[:skip_cropped] - Select only non-cropped reviews
6
+ #
7
+ def recent_reviews(params={})
8
+ skip_cropped = params.delete(:skip_cropped) || false
9
+ data = request('/review/recent_reviews', params)
10
+ if data['reviews'] && data['reviews'].key?('review')
11
+ reviews = data['reviews']['review'].map { |r| Hashie::Mash.new(r) }
12
+ reviews = reviews.select { |r| !r.body.include?(r.url) } if skip_cropped
13
+ reviews
14
+ end
15
+ end
16
+
17
+ # Get review details
18
+ #
19
+ def review(id)
20
+ data = request('/review/show', :id => id)
21
+ Hashie::Mash.new(data['review'])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Goodreads
2
+ module Shelves
3
+
4
+ # Get books from a user's shelf
5
+ def shelf(user_id, shelf_name, options={})
6
+ options = options.merge(:shelf => shelf_name, :v =>2)
7
+ data = request("/review/list/#{user_id}.xml", options)
8
+ reviews = data['reviews']['review']
9
+
10
+ books = []
11
+ unless reviews.nil?
12
+ # one-book results come back as a single hash
13
+ reviews = [reviews] if !reviews.instance_of?(Array)
14
+ books = reviews.map {|e| Hashie::Mash.new(e)}
15
+ end
16
+
17
+ Hashie::Mash.new({
18
+ :start => data['reviews']['start'].to_i,
19
+ :end => data['reviews']['end'].to_i,
20
+ :total => data['reviews']['total'].to_i,
21
+ :books => books
22
+ })
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ module Goodreads
2
+ module Users
3
+ # Get user details
4
+ #
5
+ def user(id)
6
+ data = request('/user/show', :id => id)
7
+ Hashie::Mash.new(data['user'])
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ require 'goodreads/client'
2
+ require 'goodreads/client/books'
3
+ require 'goodreads/client/reviews'
4
+ require 'goodreads/client/authors'
5
+ require 'goodreads/client/users'
6
+ require 'goodreads/client/shelves'
7
+ require 'goodreads/client/authorized'
8
+ require 'goodreads/client/groups'
9
+ require 'goodreads/client/friends'
10
+
11
+ module Goodreads
12
+ class Client
13
+ include Goodreads::Request
14
+ include Goodreads::Books
15
+ include Goodreads::Reviews
16
+ include Goodreads::Authors
17
+ include Goodreads::Users
18
+ include Goodreads::Shelves
19
+ include Goodreads::Authorized
20
+ include Goodreads::Groups
21
+ include Goodreads::Friends
22
+
23
+ attr_reader :api_key, :api_secret, :oauth_token
24
+
25
+ # Initialize a Goodreads::Client instance
26
+ #
27
+ # options[:api_key] - Account API key
28
+ # options[:api_secret] - Account API secret
29
+ # options[:oauth_token] - OAuth access token (optional, required for some calls)
30
+ #
31
+ def initialize(options={})
32
+ unless options.kind_of?(Hash)
33
+ raise ArgumentError, "Options hash required."
34
+ end
35
+
36
+ @api_key = options[:api_key] || Goodreads.configuration[:api_key]
37
+ @api_secret = options[:api_secret] || Goodreads.configuration[:api_secret]
38
+ @oauth_token = options[:oauth_token]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Goodreads
2
+ class Error < StandardError; end
3
+ class ConfigurationError < Error ; end
4
+ class Unauthorized < Error ; end
5
+ class NotFound < Error ; end
6
+ end
@@ -0,0 +1,67 @@
1
+ require 'rest-client'
2
+ require 'active_support/core_ext'
3
+ require 'hashie'
4
+
5
+ module Goodreads
6
+ module Request
7
+ API_URL = 'http://www.goodreads.com'
8
+ API_FORMAT = 'xml'
9
+
10
+ protected
11
+
12
+ # Perform an API request
13
+ #
14
+ # path - Request path
15
+ # params - Parameters hash
16
+ #
17
+ def request(path, params={})
18
+ token = api_key || Goodreads.configuration[:api_key]
19
+
20
+ if token.nil?
21
+ raise Goodreads::ConfigurationError, 'API key required.'
22
+ end
23
+
24
+ params.merge!(:format => API_FORMAT, :key => token)
25
+ url = "#{API_URL}#{path}"
26
+
27
+ resp = RestClient.get(url, :params => params) do |response, request, result, &block|
28
+ case response.code
29
+ when 200
30
+ response.return!(request, result, &block)
31
+ when 401
32
+ raise Goodreads::Unauthorized
33
+ when 404
34
+ raise Goodreads::NotFound
35
+ end
36
+ end
37
+
38
+ parse(resp)
39
+ end
40
+
41
+ # Perform an OAuth API request. Goodreads must have been initialized with a valid OAuth access token.
42
+ #
43
+ # path - Request path
44
+ # params - Parameters hash
45
+ #
46
+ def oauth_request(path, params=nil)
47
+ raise 'OAuth access token required!' unless @oauth_token
48
+ path = "#{path}?#{params.map{|k,v|"#{k}=#{v}"}.join('&')}" if params
49
+ resp = @oauth_token.get(path, {'Accept'=>'application/xml'})
50
+
51
+ case resp
52
+ when Net::HTTPUnauthorized
53
+ raise Goodreads::Unauthorized
54
+ when Net::HTTPNotFound
55
+ raise Goodreads::NotFound
56
+ end
57
+
58
+ parse(resp)
59
+ end
60
+
61
+ def parse(resp)
62
+ hash = Hash.from_xml(resp.body)['GoodreadsResponse']
63
+ hash.delete('Request')
64
+ hash
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ module Goodreads
2
+ unless defined?(Goodreads::VERSION)
3
+ VERSION = '0.2.1'.freeze
4
+ end
5
+ end
data/lib/goodreads.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'goodreads/version'
2
+ require 'goodreads/errors'
3
+ require 'goodreads/request'
4
+ require 'goodreads/client'
5
+
6
+ module Goodreads
7
+ @@options = {}
8
+
9
+ # Create a new Goodreads::Client instance
10
+ #
11
+ def self.new(options={})
12
+ Goodreads::Client.new(options)
13
+ end
14
+
15
+ # Define a global configuration
16
+ #
17
+ # options[:api_key] - Account API key
18
+ # options[:api_secret] - Account API secret
19
+ #
20
+ def self.configure(options={})
21
+ unless options.kind_of?(Hash)
22
+ raise ArgumentError, "Options hash required."
23
+ end
24
+
25
+ @@options[:api_key] = options[:api_key]
26
+ @@options[:api_secret] = options[:api_secret]
27
+ @@options
28
+ end
29
+
30
+ # Returns global configuration hash
31
+ #
32
+ def self.configuration
33
+ @@options
34
+ end
35
+
36
+ # Resets the global configuration
37
+ #
38
+ def self.reset_configuration
39
+ @@options = {}
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Client' do
4
+ before :each do
5
+ Goodreads.reset_configuration
6
+ end
7
+
8
+ it 'raises Goodreads::ConfigurationError if API key was not provided' do
9
+ client = Goodreads::Client.new
10
+
11
+ proc { client.book_by_isbn('0307463745') }.
12
+ should raise_error Goodreads::ConfigurationError, 'API key required.'
13
+ end
14
+
15
+ it 'raises Goodreads::Unauthorized if API key is not valid' do
16
+ client = Goodreads::Client.new(:api_key => 'INVALID_KEY')
17
+
18
+ stub_request(:get, "http://www.goodreads.com/book/isbn?format=xml&isbn=054748250711&key=INVALID_KEY").
19
+ to_return(:status => 401)
20
+
21
+ proc { client.book_by_isbn('054748250711') }.
22
+ should raise_error Goodreads::Unauthorized
23
+ end
24
+ end
@@ -0,0 +1,296 @@
1
+ require 'spec_helper'
2
+ require 'oauth'
3
+
4
+ describe 'Client' do
5
+ let(:client) { Goodreads::Client.new(:api_key => 'SECRET_KEY') }
6
+ before(:each) { Goodreads.reset_configuration }
7
+
8
+ describe '#new' do
9
+ it 'requires an argument' do
10
+ expect { Goodreads::Client.new(nil) }.
11
+ to raise_error ArgumentError, "Options hash required."
12
+ end
13
+
14
+ it 'requires a hash argument' do
15
+ expect { Goodreads::Client.new('foo') }.
16
+ to raise_error ArgumentError, "Options hash required."
17
+ end
18
+ end
19
+
20
+ describe '#book_by_isbn' do
21
+ before { stub_with_key_get('/book/isbn', {:isbn => '0307463745'}, 'book.xml') }
22
+
23
+ it 'returns a book by isbn' do
24
+ book = client.book_by_isbn('0307463745')
25
+
26
+ book.should respond_to :id
27
+ book.should respond_to :title
28
+ end
29
+
30
+ context 'when book does not exist' do
31
+ before do
32
+ stub_request(:get, "http://www.goodreads.com/book/isbn?format=xml&isbn=123456789&key=SECRET_KEY").
33
+ to_return(:status => 404, :body => "", :headers => {})
34
+ end
35
+
36
+ it 'raises Goodreads::NotFound' do
37
+ expect { client.book_by_isbn('123456789') }.to raise_error Goodreads::NotFound
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#search_books' do
43
+ before { stub_with_key_get('/search/index', {:q => 'Rework'}, 'search_books_by_name.xml') }
44
+
45
+ it 'returns book search results' do
46
+ result = client.search_books('Rework')
47
+
48
+ result.should be_a Hashie::Mash
49
+ result.should respond_to :query
50
+ result.should respond_to :total_results
51
+ result.should respond_to :results
52
+ result.results.should respond_to :work
53
+ result.query.should eq 'Rework'
54
+ result.results.work.size.should eq 3
55
+ result.results.work.first.id.should eq 6928276
56
+ end
57
+ end
58
+
59
+ describe '#book' do
60
+ before { stub_with_key_get('/book/show', {:id => '6732019'}, 'book.xml') }
61
+
62
+ it 'returns a book by goodreads id' do
63
+ expect { client.book('6732019') }.not_to raise_error
64
+ end
65
+ end
66
+
67
+ describe '#book_by_title' do
68
+ before { stub_with_key_get('/book/title', {:title => 'Rework'}, 'book.xml') }
69
+
70
+ it 'returns a book by title' do
71
+ expect { client.book_by_title('Rework') }.not_to raise_error
72
+ end
73
+ end
74
+
75
+ describe '#recent_reviews' do
76
+ before { stub_with_key_get('/review/recent_reviews', {}, 'recent_reviews.xml') }
77
+
78
+ it 'returns recent reviews' do
79
+ reviews = client.recent_reviews
80
+
81
+ reviews.should be_an Array
82
+ reviews.should_not be_empty
83
+ reviews.first.should respond_to :id
84
+ end
85
+
86
+ context 'with :skip_cropped => true' do
87
+ before { stub_with_key_get('/review/recent_reviews', {}, 'recent_reviews.xml') }
88
+
89
+ it 'returns only full reviews' do
90
+ reviews = client.recent_reviews(:skip_cropped => true)
91
+ reviews.should be_an Array
92
+ reviews.should_not be_empty
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#review' do
98
+ before { stub_with_key_get('/review/show', {:id => '166204831'}, 'review.xml') }
99
+
100
+ it 'returns review details' do
101
+ review = client.review('166204831')
102
+
103
+ review.should be_a Hashie::Mash
104
+ review.id.should eq '166204831'
105
+ end
106
+
107
+ context 'when review does not exist' do
108
+ before do
109
+ stub_request(:get, "http://www.goodreads.com/review/show?format=xml&id=12345&key=SECRET_KEY").
110
+ to_return(:status => 404, :body => "", :headers => {})
111
+ end
112
+
113
+ it 'raises Goodreads::NotFound' do
114
+ expect { client.review('12345') }.to raise_error Goodreads::NotFound
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#author' do
120
+ before { stub_with_key_get('/author/show', {:id => '18541'}, 'author.xml') }
121
+
122
+ it 'returns author details' do
123
+ author = client.author('18541')
124
+
125
+ author.should be_a Hashie::Mash
126
+ author.id.should eq '18541'
127
+ author.name.should eq "Tim O'Reilly"
128
+ author.link.should eq 'http://www.goodreads.com/author/show/18541.Tim_O_Reilly'
129
+ author.fans_count.should eq 109
130
+ author.image_url.should eq 'http://photo.goodreads.com/authors/1199698411p5/18541.jpg'
131
+ author.small_image_url.should eq 'http://photo.goodreads.com/authors/1199698411p2/18541.jpg'
132
+ author.about.should eq ''
133
+ author.influences.should eq ''
134
+ author.works_count.should eq '34'
135
+ author.gender.should eq 'male'
136
+ author.hometown.should eq 'Cork'
137
+ author.born_at.should eq '1954/06/06'
138
+ author.died_at.should be_nil
139
+ end
140
+
141
+ context 'when author does not exist' do
142
+ before do
143
+ stub_request(:get, "http://www.goodreads.com/author/show?format=xml&id=12345&key=SECRET_KEY").
144
+ to_return(:status => 404, :body => "", :headers => {})
145
+ end
146
+
147
+ it 'raises Goodreads::NotFound' do
148
+ expect { client.author('12345') }.to raise_error Goodreads::NotFound
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#author_by_name' do
154
+ before do
155
+ stub_with_key_get('/api/author_url', {:id => 'Orson Scott Card'}, 'author_by_name.xml')
156
+ end
157
+
158
+ it 'returns author details' do
159
+ author = client.author_by_name('Orson Scott Card')
160
+
161
+ author.should be_a Hashie::Mash
162
+ author.id.should eq '589'
163
+ author.name.should eq 'Orson Scott Card'
164
+ author.link.should eq 'http://www.goodreads.com/author/show/589.Orson_Scott_Card?utm_medium=api&utm_source=author_link'
165
+ end
166
+ end
167
+
168
+ describe '#user' do
169
+ before { stub_with_key_get('/user/show', {:id => '878044'}, 'user.xml') }
170
+
171
+ it 'returns user details' do
172
+ user = client.user('878044')
173
+
174
+ user.should be_a Hashie::Mash
175
+ user.id.should eq '878044'
176
+ user.name.should eq 'Jan'
177
+ user.user_name.should eq 'janmt'
178
+ end
179
+
180
+ context 'when user does not exist' do
181
+ before do
182
+ stub_request(:get, "http://www.goodreads.com/user/show?format=xml&id=12345&key=SECRET_KEY").
183
+ to_return(:status => 404, :body => "", :headers => {})
184
+ end
185
+
186
+ it 'raises Goodreads::NotFound' do
187
+ expect { client.user('12345') }.to raise_error Goodreads::NotFound
188
+ end
189
+ end
190
+ end
191
+
192
+ describe '#friends' do
193
+ before { client.stub(:oauth_request).and_return(Hash.from_xml(fixture('friends.xml'))['GoodreadsResponse']) }
194
+
195
+ it 'returns friend details' do
196
+ friends = client.friends('878044')
197
+
198
+ friends.should be_an_instance_of Hashie::Mash
199
+ friends.should respond_to :user
200
+ friends.user.size.should eq friends.end.to_i
201
+ friends.user.first.should respond_to :name
202
+ end
203
+ end
204
+
205
+ describe '#shelf' do
206
+ it "returns list of books for a user's specified shelf" do
207
+ stub_with_key_get('/review/list/1.xml', {:shelf => 'to-read', :v => '2'}, 'to-read.xml')
208
+
209
+ shelf = client.shelf('1', 'to-read')
210
+
211
+ shelf.should respond_to :start
212
+ shelf.should respond_to :end
213
+ shelf.should respond_to :total
214
+ shelf.should respond_to :books
215
+
216
+ shelf.start.should eq 1
217
+ shelf.end.should eq 20
218
+ shelf.total.should eq 40
219
+ shelf.books.length.should eq 20
220
+ shelf.books.first.id.should eq '45590939'
221
+ shelf.books.first.book.title.strip.should eq 'The Demon-Haunted World: Science as a Candle in the Dark'
222
+ end
223
+
224
+ it "paginates book lists from a user's shelf" do
225
+ stub_with_key_get('/review/list/1.xml', {:shelf => 'to-read', :v => '2', :page => '2'}, 'to-read-p2.xml')
226
+
227
+ shelf = client.shelf('1', 'to-read', :page => 2)
228
+
229
+ shelf.start.should eq 21
230
+ shelf.end.should eq 40
231
+ shelf.total.should eq 40
232
+ shelf.books.length.should eq 20
233
+ shelf.books.first.id.should eq '107804211'
234
+ shelf.books.first.book.title.should match /Your Money or Your Life/
235
+ end
236
+
237
+ it "returns an empty array when shelf is empty" do
238
+ stub_with_key_get('/review/list/1.xml', {:shelf => 'to-read', :v => '2'}, 'empty.xml')
239
+
240
+ shelf = client.shelf('1', 'to-read')
241
+
242
+ shelf.start.should eq 0
243
+ shelf.end.should eq 0
244
+ shelf.total.should eq 0
245
+ shelf.books.length.should eq 0
246
+ end
247
+ end
248
+
249
+ describe '#user_id' do
250
+ let(:consumer) { OAuth::Consumer.new('API_KEY', 'SECRET_KEY', :site => 'http://www.goodreads.com') }
251
+ let(:token) { OAuth::AccessToken.new(consumer, 'ACCESS_TOKEN', 'ACCESS_SECRET') }
252
+
253
+ before do
254
+ stub_request(:get, "http://www.goodreads.com/api/auth_user").
255
+ to_return(:status => 200, :body => fixture('oauth_response.xml'), :headers => {})
256
+ end
257
+
258
+ it 'returns id of the user with oauth authentication' do
259
+ client = Goodreads::Client.new(:api_key => 'SECRET_KEY', :oauth_token => token)
260
+ client.user_id.should eq '2003928'
261
+ end
262
+ end
263
+
264
+ describe '#group' do
265
+ before { stub_with_key_get('/group/show', {:id => '1'}, 'group.xml') }
266
+
267
+ it "returns group details" do
268
+ group = client.group('1')
269
+
270
+ group.should be_a Hashie::Mash
271
+ group.id.should eq '1'
272
+ group.title.should eq 'Goodreads Feedback'
273
+ group.access.should eq 'public'
274
+ group.location.should eq ''
275
+ group.category.should eq 'Business'
276
+ group.subcategory.should eq 'Companies'
277
+ group.group_users_count.should eq '10335'
278
+ end
279
+ end
280
+
281
+ describe '#group_list' do
282
+ before { stub_with_key_get('/group/list', {:id => '1', :sort => 'my_activity'}, 'group_list.xml') }
283
+
284
+ it "returns groups a given user is a member of" do
285
+ group_list = client.group_list('1')
286
+
287
+ group_list.should be_a Hashie::Mash
288
+ group_list.total.should eq '107'
289
+ group_list.group.count.should eq 50
290
+ group_list.group[0].id.should eq '1'
291
+ group_list.group[0].title.should eq 'Goodreads Feedback'
292
+ group_list.group[1].id.should eq '220'
293
+ group_list.group[2].users_count.should eq '530'
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Goodreads' do
4
+ describe '.new' do
5
+ it 'returns a new client instance' do
6
+ Goodreads.new.should be_a Goodreads::Client
7
+ end
8
+ end
9
+
10
+ describe '.configure' do
11
+ it 'sets a global configuration options' do
12
+ r = Goodreads.configure(:api_key => 'FOO', :api_secret => 'BAR')
13
+ r.should be_a Hash
14
+ r.should have_key(:api_key)
15
+ r.should have_key(:api_secret)
16
+ r[:api_key].should eql('FOO')
17
+ r[:api_secret].should eql('BAR')
18
+ end
19
+
20
+ it 'raises ConfigurationError on invalid config parameter' do
21
+ proc { Goodreads.configure(nil) }.
22
+ should raise_error(ArgumentError, "Options hash required.")
23
+
24
+ proc { Goodreads.configure('foo') }.
25
+ should raise_error ArgumentError, "Options hash required."
26
+ end
27
+ end
28
+
29
+ describe '.configuration' do
30
+ before do
31
+ Goodreads.configure(:api_key => 'FOO', :api_secret => 'BAR')
32
+ end
33
+
34
+ it 'returns global configuration options' do
35
+ r = Goodreads.configuration
36
+ r.should be_a Hash
37
+ r.should have_key(:api_key)
38
+ r.should have_key(:api_secret)
39
+ r[:api_key].should eql('FOO')
40
+ r[:api_secret].should eql('BAR')
41
+ end
42
+ end
43
+
44
+ describe '.reset_configuration' do
45
+ before do
46
+ Goodreads.configure(:api_key => 'FOO', :api_secret => 'BAR')
47
+ end
48
+
49
+ it 'resets global configuration options' do
50
+ Goodreads.reset_configuration
51
+ Goodreads.configuration.should eql({})
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,38 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_group 'Goodreads', 'lib/goodreads'
6
+ end
7
+
8
+ require 'goodreads'
9
+ require 'webmock'
10
+ require 'webmock/rspec'
11
+
12
+ def stub_get(path, params, fixture_name)
13
+ params[:format] = 'xml'
14
+ stub_request(:get, api_url(path)).
15
+ with(:query => params).
16
+ to_return(
17
+ :status => 200,
18
+ :body => fixture(fixture_name)
19
+ )
20
+ end
21
+
22
+ def stub_with_key_get(path, params, fixture_name)
23
+ params[:key] = 'SECRET_KEY'
24
+ stub_get(path, params, fixture_name)
25
+ end
26
+
27
+ def fixture_path(file=nil)
28
+ path = File.expand_path("../fixtures", __FILE__)
29
+ file.nil? ? path : File.join(path, file)
30
+ end
31
+
32
+ def fixture(file)
33
+ File.read(fixture_path(file))
34
+ end
35
+
36
+ def api_url(path)
37
+ "#{Goodreads::Request::API_URL}#{path}"
38
+ end
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: goodreads_17up
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Dan Sosedoff
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webmock
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rest-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.6'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashie
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '1.2'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '3'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: i18n
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '0.5'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '0.5'
139
+ - !ruby/object:Gem::Dependency
140
+ name: oauth
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: '0.4'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '0.4'
153
+ description: Simple wrapper for the Goodreads API
154
+ email:
155
+ - dan.sosedoff@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - .gitignore
161
+ - .rspec
162
+ - .travis.yml
163
+ - Gemfile
164
+ - README.md
165
+ - Rakefile
166
+ - examples/oauth.md
167
+ - goodreads.gemspec
168
+ - lib/goodreads.rb
169
+ - lib/goodreads/client.rb
170
+ - lib/goodreads/client/authorized.rb
171
+ - lib/goodreads/client/authors.rb
172
+ - lib/goodreads/client/books.rb
173
+ - lib/goodreads/client/friends.rb
174
+ - lib/goodreads/client/groups.rb
175
+ - lib/goodreads/client/reviews.rb
176
+ - lib/goodreads/client/shelves.rb
177
+ - lib/goodreads/client/users.rb
178
+ - lib/goodreads/errors.rb
179
+ - lib/goodreads/request.rb
180
+ - lib/goodreads/version.rb
181
+ - spec/authentication_spec.rb
182
+ - spec/client_spec.rb
183
+ - spec/goodreads_spec.rb
184
+ - spec/spec_helper.rb
185
+ homepage: http://github.com/sosedoff/goodreads
186
+ licenses: []
187
+ metadata: {}
188
+ post_install_message:
189
+ rdoc_options: []
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - '>='
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ required_rubygems_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ requirements: []
203
+ rubyforge_project:
204
+ rubygems_version: 2.0.0
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Goodreads API wrapper
208
+ test_files:
209
+ - spec/authentication_spec.rb
210
+ - spec/client_spec.rb
211
+ - spec/goodreads_spec.rb
212
+ - spec/spec_helper.rb