goodreads 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 32d6d10732050dfe55085f6f38358ca6884d4d84
4
- data.tar.gz: aacf42830b97b3ce07eff81bc77b4a520cf946ea
2
+ SHA256:
3
+ metadata.gz: 7c94efd63197915ee10a6ba023cb810e4fac84c2943f4544e83642f2a002a0ec
4
+ data.tar.gz: ce3afa10705116414371ac7b21788c6e6114e7d7a691a31fdd7f49fd80d6e266
5
5
  SHA512:
6
- metadata.gz: cf4836f8619cab1de139614b155614347f43b60aa6078246392ea6671f83bed456eb0a6139f930e72a4c8574ea3ec5fb3986955a2cc5b8eaa3d00edf67ea3d89
7
- data.tar.gz: fa0b3ae21e4caa744e61479c445d863d73e0e68182af09bc169a461ec89178e70b9478469b88a86c77763742d8d5e534d809b2bdccb5a26bd9fea2a46bc48cc7
6
+ metadata.gz: 58091fa38ec8c80c653e0f50ec46e401e5a5a3eb048354cd11e2b9591a20e2011d401f5816e3dc2ca63107974a084e12af9bf2184103162e61b576a30499afc1
7
+ data.tar.gz: d602443eae15c2cf6123d21242f5ac68c90314eb15e4b9d45603234aaafc2b814eb2c1ecae1ae57daa289db895ab2fb595f0c223755163d85845ae285b1f292a
@@ -5,6 +5,7 @@ rvm:
5
5
  - 2.2
6
6
  - 2.3
7
7
  - 2.4
8
+ - 2.5
8
9
 
9
10
  script:
10
11
  - bundle exec rake test
data/README.md CHANGED
@@ -210,4 +210,4 @@ You're welcome to submit patches and new features.
210
210
 
211
211
  The MIT License (MIT)
212
212
 
213
- Copyright (c) 2011-2015 Dan Sosedoff, <dan.sosedoff@gmail.com>
213
+ Copyright (c) 2011-2018 Dan Sosedoff, <dan.sosedoff@gmail.com>
@@ -31,9 +31,18 @@ module Goodreads
31
31
  def initialize(options = {})
32
32
  fail(ArgumentError, "Options hash required.") unless options.is_a?(Hash)
33
33
 
34
- @api_key = options[:api_key] || Goodreads.configuration[:api_key]
35
- @api_secret = options[:api_secret] || Goodreads.configuration[:api_secret]
34
+ @api_key = options[:api_key] || Goodreads.configuration[:api_key]
35
+ @api_secret = options[:api_secret] || Goodreads.configuration[:api_secret]
36
36
  @oauth_token = options[:oauth_token]
37
37
  end
38
+
39
+ # Return if this client is configured with OAuth credentials
40
+ # for a single user
41
+ #
42
+ # False when client is instantiated with an api_key and secret,
43
+ # true when client is instantiated with an oauth_token
44
+ def oauth_configured?
45
+ !oauth_token.nil?
46
+ end
38
47
  end
39
48
  end
@@ -31,5 +31,61 @@ module Goodreads
31
31
  []
32
32
  end
33
33
  end
34
+
35
+ # Get a user's review for a given book
36
+ def user_review(user_id, book_id, params = {})
37
+ data = request('/review/show_by_user_and_book.xml', params.merge(v: "2", user_id: user_id, book_id: book_id))
38
+ Hashie::Mash.new(data["review"])
39
+ end
40
+
41
+ # Add review for a book
42
+ #
43
+ # Params can include :review, :rating, and :shelf
44
+ #
45
+ # review: text of the review (optional)
46
+ # rating: rating (0-5) (optional, default is 0 (no rating))
47
+ # shelf: Name of shelf to add book to (optional, must exist, see: shelves.list)
48
+ #
49
+ # Note that Goodreads API documentation says that this endpoint accepts
50
+ # the read_at parameter but it does not appear to work as of 2018-06.
51
+ def create_review(book_id, params = {})
52
+ params = params.merge(book_id: book_id, v: "2")
53
+
54
+ params[:read_at] = params[:read_at].strftime('%Y-%m-%d') if params[:read_at].is_a?(Time)
55
+
56
+ params[:'review[review]'] = params.delete(:review) if params[:review]
57
+ params[:'review[rating]'] = params.delete(:rating) if params[:rating]
58
+ params[:'review[read_at]'] = params.delete(:read_at) if params[:read_at]
59
+
60
+ data = oauth_request_method(:post, '/review.xml', params)
61
+
62
+ Hashie::Mash.new(data["review"])
63
+ end
64
+
65
+ # Edit review for a book
66
+ #
67
+ # Params can include :review, :rating, :read_at and :shelf, and :finished
68
+ #
69
+ # review: text of the review (optional)
70
+ # rating: rating (0-5) (optional, default is 0 (no rating))
71
+ # shelf: Name of shelf to add book to (optional, must exist, see: shelves.list)
72
+ # read_at: Time object or String in YYYY-MM-DD format (optional)
73
+ # finished: true to mark finished reading (optional)
74
+ # shelf: Name of shelf to add book to (optional, must exist, see: shelves.list)
75
+ def edit_review(review_id, params = {})
76
+ params = params.merge(v: "2")
77
+
78
+ params[:read_at] = params[:read_at].strftime('%Y-%m-%d') if params[:read_at].is_a?(Time)
79
+
80
+ params[:'review[review]'] = params.delete(:review) if params[:review]
81
+ params[:'review[rating]'] = params.delete(:rating) if params[:rating]
82
+ params[:'review[read_at]'] = params.delete(:read_at) if params[:read_at]
83
+
84
+ # Documentation says that you should use HTTP PUT, but API returns
85
+ # 401 Unauthorized when PUT is used instead of POST
86
+ data = oauth_request_method(:post, "/review/#{review_id}.xml", params)
87
+
88
+ Hashie::Mash.new(data["review"])
89
+ end
34
90
  end
35
91
  end
@@ -1,5 +1,36 @@
1
1
  module Goodreads
2
2
  module Shelves
3
+ # Lists shelves for a user
4
+ def shelves(user_id, options = {})
5
+ options = options.merge(user_id: user_id, v: 2)
6
+ data = request("/shelf/list.xml", options)
7
+ shelves = data["shelves"]
8
+
9
+ shelves = data["shelves"]["user_shelf"].map do |s|
10
+ Hashie::Mash.new({
11
+ id: s["id"],
12
+ name: s["name"],
13
+ book_count: s["book_count"],
14
+ exclusive: s["exclusive_flag"],
15
+ description: s["description"],
16
+ sort: s["sort"],
17
+ order: s["order"],
18
+ per_page: s["per_page"],
19
+ display_fields: s["display_fields"],
20
+ featured: s["featured"],
21
+ recommend_for: s["recommend_for"],
22
+ sticky: s["sticky"],
23
+ })
24
+ end
25
+
26
+ Hashie::Mash.new(
27
+ start: data["shelves"]["start"].to_i,
28
+ end: data["shelves"]["end"].to_i,
29
+ total: data["shelves"]["total"].to_i,
30
+ shelves: shelves
31
+ )
32
+ end
33
+
3
34
  # Get books from a user's shelf
4
35
  def shelf(user_id, shelf_name, options = {})
5
36
  options = options.merge(shelf: shelf_name, v: 2)
@@ -20,5 +51,42 @@ module Goodreads
20
51
  books: books
21
52
  )
22
53
  end
54
+
55
+ # Add book to a user's shelf
56
+ #
57
+ # Returns the user's review for the book, which includes all its current shelves
58
+ def add_to_shelf(book_id, shelf_name, options = {})
59
+ options = options.merge(book_id: book_id, name: shelf_name, v: 2)
60
+ data = oauth_request_method(:post, "/shelf/add_to_shelf.xml", options)
61
+
62
+ # when a book is on a single shelf it is a single hash
63
+ shelves = data["my_review"]["shelves"]["shelf"]
64
+ shelves = [shelves] unless shelves.instance_of?(Array)
65
+ shelves = shelves.map do |s|
66
+ Hashie::Mash.new({
67
+ id: s["id"].to_i,
68
+ name: s["name"],
69
+ exclusive: s["exclusive"] == "true",
70
+ sortable: s["sortable"] == "true",
71
+ })
72
+ end
73
+
74
+ Hashie::Mash.new(
75
+ id: data["my_review"]["id"].to_i,
76
+ book_id: data["my_review"]["book_id"].to_i,
77
+
78
+ rating: data["my_review"]["rating"].to_i,
79
+ body: data["my_review"]["body"],
80
+ body_raw: data["my_review"]["body_raw"],
81
+ spoiler: data["my_review"]["spoiler_flag"] == "true",
82
+
83
+ shelves: shelves,
84
+
85
+ read_at: data["my_review"]["read_at"],
86
+ started_at: data["my_review"]["started_at"],
87
+ date_added: data["my_review"]["date_added"],
88
+ updated_at: data["my_review"]["updated_at"],
89
+ )
90
+ end
23
91
  end
24
92
  end
@@ -9,12 +9,26 @@ module Goodreads
9
9
 
10
10
  protected
11
11
 
12
- # Perform an API request
12
+ # Perform an API request using API key or OAuth token
13
13
  #
14
14
  # path - Request path
15
15
  # params - Parameters hash
16
16
  #
17
+ # Will make a request with the configured API key (application
18
+ # authentication) or OAuth token (user authentication) if available.
17
19
  def request(path, params = {})
20
+ if oauth_configured?
21
+ oauth_request(path, params)
22
+ else
23
+ http_request(path, params)
24
+ end
25
+ end
26
+
27
+ # Perform an API request using API key
28
+ #
29
+ # path - Request path
30
+ # params - Parameters hash
31
+ def http_request(path, params)
18
32
  token = api_key || Goodreads.configuration[:api_key]
19
33
 
20
34
  fail(Goodreads::ConfigurationError, "API key required.") if token.nil?
@@ -38,18 +52,35 @@ module Goodreads
38
52
  parse(resp)
39
53
  end
40
54
 
55
+ # Perform an OAuth API GET request. Goodreads must have been initialized with a valid OAuth access token.
56
+ #
57
+ # path - Request path
58
+ # params - Parameters hash
59
+ #
60
+ def oauth_request(path, params = {})
61
+ oauth_request_method(:get, path, params)
62
+ end
63
+
41
64
  # Perform an OAuth API request. Goodreads must have been initialized with a valid OAuth access token.
42
65
  #
66
+ # http_method - HTTP verb supported by OAuth gem (one of :get, :post, :delete, etc.)
43
67
  # path - Request path
44
68
  # params - Parameters hash
45
69
  #
46
- def oauth_request(path, params = nil)
70
+ def oauth_request_method(http_method, path, params = {})
47
71
  fail "OAuth access token required!" unless @oauth_token
48
- if params
49
- url_params = params.map { |k, v| "#{k}=#{v}" }.join("&")
50
- path = "#{path}?#{url_params}"
72
+
73
+ headers = { "Accept" => "application/xml" }
74
+
75
+ resp = if http_method == :get || http_method == :delete
76
+ if params
77
+ url_params = params.map { |k, v| "#{k}=#{v}" }.join("&")
78
+ path = "#{path}?#{url_params}"
79
+ end
80
+ @oauth_token.request(http_method, path, headers)
81
+ else
82
+ @oauth_token.request(http_method, path, params || {}, headers)
51
83
  end
52
- resp = @oauth_token.get(path, "Accept" => "application/xml")
53
84
 
54
85
  case resp
55
86
  when Net::HTTPUnauthorized
@@ -1,3 +1,3 @@
1
1
  module Goodreads
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.2"
3
3
  end
@@ -1,24 +1,37 @@
1
1
  require "spec_helper"
2
2
 
3
- describe "Client" do
3
+ describe Goodreads::Client do
4
4
  before :each do
5
5
  Goodreads.reset_configuration
6
6
  end
7
7
 
8
- it "raises Goodreads::ConfigurationError if API key was not provided" do
9
- client = Goodreads::Client.new
8
+ let(:client) do
9
+ described_class.new(api_key: api_key)
10
+ end
11
+
12
+ context "API key is missing" do
13
+ let(:api_key) {}
10
14
 
11
- expect { client.book_by_isbn("0307463745") }
12
- .to raise_error(Goodreads::ConfigurationError, "API key required.")
15
+ it "throws an error" do
16
+ expect { client.book_by_isbn("0307463745") }
17
+ .to raise_error(Goodreads::ConfigurationError, "API key required.")
18
+ end
13
19
  end
14
20
 
15
- it "raises Goodreads::Unauthorized if API key is not valid" do
16
- client = Goodreads::Client.new(api_key: "INVALID_KEY")
21
+ context "API key is invalid" do
22
+ let(:api_key) { "INVALID_KEY" }
23
+
24
+ before do
25
+ stub_request(:get, "https://www.goodreads.com/book/isbn?format=xml&isbn=1&key=INVALID_KEY")
26
+ .to_return(status: 401)
17
27
 
18
- stub_request(:get, "https://www.goodreads.com/book/isbn?format=xml&isbn=054748250711&key=INVALID_KEY")
19
- .to_return(status: 401)
28
+ stub_request(:get, "https://www.goodreads.com/book/isbn?format=xml&isbn=2&key=INVALID_KEY")
29
+ .to_return(status: 403)
30
+ end
20
31
 
21
- expect { client.book_by_isbn("054748250711") }
22
- .to raise_error(Goodreads::Unauthorized)
32
+ it "throws errors" do
33
+ expect { client.book_by_isbn("1") }.to raise_error(Goodreads::Unauthorized)
34
+ expect { client.book_by_isbn("2") }.to raise_error(Goodreads::Forbidden)
35
+ end
23
36
  end
24
37
  end
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "oauth"
3
3
 
4
- describe "Client" do
4
+ describe Goodreads::Client do
5
5
  let(:client) { Goodreads::Client.new(api_key: "SECRET_KEY") }
6
6
  before(:each) { Goodreads.reset_configuration }
7
7
 
@@ -17,6 +17,45 @@ describe "Client" do
17
17
  end
18
18
  end
19
19
 
20
+ describe "#oauth_configured?" do
21
+ it "is true when OAuth token provided to constructor" do
22
+ client = Goodreads::Client.new(oauth_token: "a token")
23
+ expect(client.oauth_configured?).to be true
24
+ end
25
+
26
+ it "is true when oauth token is not provided to constructor" do
27
+ client = Goodreads::Client.new(api_key: "SECRET_KEY")
28
+ expect(client.oauth_configured?).to be false
29
+ end
30
+ end
31
+
32
+ describe "#request" do
33
+ context "with oauth token" do
34
+ it "makes an oauth request" do
35
+ oauth_token = double
36
+ response = double
37
+
38
+ allow(oauth_token).to receive(:request) { response }
39
+ allow(response).to receive(:body) { fixture("book.xml") }
40
+
41
+ client = Goodreads::Client.new(oauth_token: oauth_token)
42
+ expect(client.book(123)).to be_a Hash
43
+ end
44
+ end
45
+
46
+ context "without oauth token" do
47
+ before do
48
+ allow(client).to receive(:http_request) {
49
+ Hash.from_xml(fixture("book.xml"))["GoodreadsResponse"]
50
+ }
51
+ end
52
+
53
+ it "makes a request" do
54
+ expect(client.book(123)).to be_a Hash
55
+ end
56
+ end
57
+ end
58
+
20
59
  describe "#book_by_isbn" do
21
60
  before { stub_with_key_get("/book/isbn", { isbn: "0307463745" }, "book.xml") }
22
61
 
@@ -154,6 +193,26 @@ describe "Client" do
154
193
  end
155
194
  end
156
195
 
196
+ describe "#user_review" do
197
+ let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
198
+ let(:token) { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }
199
+
200
+ it "returns a user's existing review" do
201
+ stub_request(:get, "https://www.goodreads.com/review/show_by_user_and_book.xml?book_id=50&user_id=1&v=2")
202
+ .to_return(status: 200, body: fixture("review_show_by_user_and_book.xml"))
203
+
204
+ client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
205
+ review = client.user_review(1, 50)
206
+
207
+ expect(review.id).to eq("21")
208
+ expect(review.book.id).to eq(50)
209
+
210
+ expect(review.rating).to eq("5")
211
+ expect(review.body.strip).to eq("")
212
+ expect(review.date_added).to eq("Tue Aug 29 11:20:01 -0700 2006")
213
+ end
214
+ end
215
+
157
216
  describe "#author" do
158
217
  before { stub_with_key_get("/author/show", { id: "18541" }, "author.xml") }
159
218
 
@@ -287,6 +346,131 @@ describe "Client" do
287
346
  end
288
347
  end
289
348
 
349
+ describe "#add_to_shelf" do
350
+ let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
351
+ let(:token) { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }
352
+
353
+ it "adds a book to a user's shelf" do
354
+ stub_request(:post, "https://www.goodreads.com/shelf/add_to_shelf.xml")
355
+ .with(:body => {"book_id"=>"456", "name"=>"read", "v"=>"2"})
356
+ .to_return(status: 201, body: fixture("shelf_add_to_shelf.xml"))
357
+
358
+ client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
359
+ review = client.add_to_shelf(456, "read")
360
+
361
+ expect(review.id).to eq(2416981504)
362
+ expect(review.book_id).to eq(456)
363
+
364
+ expect(review.rating).to eq(0)
365
+ expect(review.body).to be nil
366
+ expect(review.body_raw).to be nil
367
+ expect(review.spoiler).to be false
368
+
369
+ expect(review.shelves.size).to eq(1)
370
+ expect(review.shelves.first.name).to eq("read")
371
+ expect(review.shelves.first.id).to eq(269274694)
372
+ expect(review.shelves.first.exclusive).to be true
373
+ expect(review.shelves.first.sortable).to be false
374
+
375
+
376
+ expect(review.read_at).to be nil
377
+ expect(review.started_at).to be nil
378
+ expect(review.date_added).to eq("Thu Jun 07 19:58:19 -0700 2018")
379
+ expect(review.updated_at).to eq("Thu Jun 07 19:58:53 -0700 2018")
380
+
381
+ expect(review.body).to be nil
382
+ expect(review.body_raw).to be nil
383
+ expect(review.spoiler).to be false
384
+ end
385
+ end
386
+
387
+ describe "#create_review" do
388
+ let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
389
+ let(:token) { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }
390
+
391
+ it "creates a new review for a book" do
392
+ stub_request(:post, "https://www.goodreads.com/review.xml")
393
+ .with(:body => {
394
+ "book_id"=>"456",
395
+ "review" => {
396
+ "rating" => "3",
397
+ "review" => "Good book.",
398
+ "read_at" => "2018-01-02",
399
+ },
400
+ "shelf" => "read",
401
+ "v"=>"2",
402
+ })
403
+ .to_return(status: 201, body: fixture("review_create.xml"))
404
+
405
+ client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
406
+ review = client.create_review(456, {
407
+ :review => "Good book.",
408
+ :rating => 3,
409
+ :read_at => Time.parse('2018-01-02'),
410
+ :shelf => "read",
411
+ })
412
+
413
+ expect(review.id).to eq("67890")
414
+ expect(review.book.id).to eq(456)
415
+ expect(review.rating).to eq("3")
416
+ expect(review.body).to eq("Good book.")
417
+ end
418
+ end
419
+
420
+ describe "#edit_review" do
421
+ let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
422
+ let(:token) { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }
423
+
424
+ it "creates a new review for a book" do
425
+ stub_request(:post, "https://www.goodreads.com/review/67890.xml")
426
+ .with(:body => {
427
+ "finished" => "true",
428
+ "review" => {
429
+ "rating" => "5",
430
+ "review" => "Fantastic book.",
431
+ "read_at" => "2018-04-15",
432
+ },
433
+ "shelf" => "read",
434
+ "v"=>"2",
435
+ })
436
+ .to_return(status: 201, body: fixture("review_update.xml"))
437
+
438
+ client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
439
+ review = client.edit_review(67890, {
440
+ :finished => true,
441
+ :review => "Fantastic book.",
442
+ :rating => 5,
443
+ :read_at => Time.parse('2018-04-15'),
444
+ :shelf => "read",
445
+ })
446
+
447
+ expect(review.id).to eq("67890")
448
+ expect(review.book.id).to eq(456)
449
+ expect(review.rating).to eq("5")
450
+ expect(review.body).to eq("Fantastic book.")
451
+ end
452
+ end
453
+
454
+ describe "#shelves" do
455
+ it "returns list of user's shelves" do
456
+ stub_with_key_get("/shelf/list.xml", { user_id: 123, v: "2" }, "shelf_list.xml")
457
+
458
+ response = client.shelves(123)
459
+
460
+ expect(response).to respond_to(:start)
461
+ expect(response).to respond_to(:end)
462
+ expect(response).to respond_to(:total)
463
+ expect(response).to respond_to(:shelves)
464
+
465
+ expect(response.start).to eq(1)
466
+ expect(response.end).to eq(3)
467
+ expect(response.total).to eq(3)
468
+ expect(response.shelves.length).to eq(3)
469
+ expect(response.shelves.first.id).to eq(9049904)
470
+ expect(response.shelves.first.name).to eq("read")
471
+ end
472
+ end
473
+
290
474
  describe "#user_id" do
291
475
  let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
292
476
  let(:token) { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }
@@ -0,0 +1,102 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <GoodreadsResponse>
3
+ <Request>
4
+ <authentication>true</authentication>
5
+ <key><![CDATA[SECRET_KEY]]></key>
6
+ <method><![CDATA[review_create]]></method>
7
+ </Request>
8
+
9
+ <review>
10
+ <id>67890</id>
11
+
12
+ <user>
13
+ <id>123456</id>
14
+ <uri>kca://profile:goodreads/A25C3Y1SBGK9NK</uri>
15
+ <name>Test</name>
16
+ <display_name>Test User</display_name>
17
+
18
+ <location>Lafayette, CA</location>
19
+
20
+ <link><![CDATA[https://www.goodreads.com/user/show/123456-test-user]]></link>
21
+
22
+ <image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_111x148-9394ebedbb3c6c218f64be9549657029.png]]></image_url>
23
+ <small_image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_50x66-632230dc9882b4352d753eedf9396530.png]]></small_image_url>
24
+ <has_image>false</has_image>
25
+
26
+ </user>
27
+
28
+ <book>
29
+ <id type="integer">456</id>
30
+ <isbn>0195138570</isbn>
31
+ <isbn13>9780195138573</isbn13>
32
+ <text_reviews_count type="integer">11</text_reviews_count>
33
+ <uri>kca://book/amzn1.gr.book.v1.oHwPZrciugo6gPSINsfCaw</uri>
34
+ <title>Mindware: An Introduction to the Philosophy of Cognitive Science</title>
35
+ <title_without_series>Mindware: An Introduction to the Philosophy of Cognitive Science</title_without_series>
36
+ <image_url>https://s.gr-assets.com/assets/nophoto/book/111x148-bcc042a9c91a29c1d680899eff700a03.png</image_url>
37
+ <small_image_url>https://s.gr-assets.com/assets/nophoto/book/50x75-a91bf249278a81aabab721ef782c4a74.png</small_image_url>
38
+ <large_image_url/>
39
+ <link>https://www.goodreads.com/book/show/456.Mindware</link>
40
+ <num_pages>224</num_pages>
41
+ <format>Paperback</format>
42
+ <edition_information>1st edition</edition_information>
43
+ <publisher>Oxford University Press, USA</publisher>
44
+ <publication_day>21</publication_day>
45
+ <publication_year>2000</publication_year>
46
+ <publication_month>12</publication_month>
47
+ <average_rating>3.78</average_rating>
48
+ <ratings_count>210</ratings_count>
49
+ <description>&lt;p&gt;&lt;i&gt;Mindware&lt;/i&gt; is an introductory text with a difference. In eight short chapters it tells a story and invites the reader to join in some up-to-the-minute conceptual discussion of the key issues, problems, and opportunities in cognitive science. The story is about the search for a cognitive scientific understanding of mind. It is presented as a no-holds-barred journey from early work in Artificial Intelligence, through connectionist (artificial neural network) counter-visions, and onto neuroscience artificial life, dynamics and robotics. The journey ends with some wide-ranging and provacative speculation about the role of technology and the changing nature of the human mind itself.&lt;br /&gt;Each chapter is organized as an initial sketch of a research program or theme, followed by a substantial discussion section in which specific problems and issues (both familiear and cutting-edge) are raised and pursued. Discussion topics include mental causation, the hardware/software distinction, the relations between life and mind, the nature of perception, cognition and action, and the continuity (or otherwise) of high-level human intelligence with other forms of adaptive response. Classic topics are treated alongside the newer ones in an integrated treatment of the various discussions. The sketches and discussions are accompanied by numerous figures and boxed sections, and followed by suggestions for futher reading.&lt;/p&gt;</description>
50
+ <authors>
51
+ <author>
52
+ <id>3445871</id>
53
+ <name>Andy Clark</name>
54
+ <role></role>
55
+ <image_url nophoto='false'>
56
+ <![CDATA[https://images.gr-assets.com/authors/1269856267p5/3445871.jpg]]>
57
+ </image_url>
58
+ <small_image_url nophoto='false'>
59
+ <![CDATA[https://images.gr-assets.com/authors/1269856267p2/3445871.jpg]]>
60
+ </small_image_url>
61
+ <link><![CDATA[https://www.goodreads.com/author/show/3445871.Andy_Clark]]></link>
62
+ <average_rating>3.89</average_rating>
63
+ <ratings_count>992</ratings_count>
64
+ <text_reviews_count>74</text_reviews_count>
65
+ </author>
66
+ </authors>
67
+ <published>2000</published>
68
+ <work> <id>282613</id>
69
+ <uri>kca://work/amzn1.gr.work.v1.xCZQesiWRuaIVPV6wsdFGA</uri>
70
+ </work></book>
71
+
72
+
73
+ <rating>3</rating>
74
+ <votes>0</votes>
75
+ <spoiler_flag>false</spoiler_flag>
76
+ <spoilers_state>none</spoilers_state>
77
+
78
+
79
+ <shelves>
80
+ <shelf name="read" exclusive="true" id="269274694" review_shelf_id= ""/>
81
+
82
+ </shelves>
83
+
84
+ <recommended_for></recommended_for>
85
+ <recommended_by></recommended_by>
86
+ <started_at></started_at>
87
+ <read_at></read_at>
88
+ <date_added>Wed Jun 13 20:52:06 -0700 2018</date_added>
89
+ <date_updated>Wed Jun 13 20:52:07 -0700 2018</date_updated>
90
+ <read_count>1</read_count>
91
+ <body>Good book.</body>
92
+
93
+
94
+
95
+ <comments_count>0</comments_count>
96
+ <url><![CDATA[https://www.goodreads.com/review/show/67890]]></url>
97
+ <link><![CDATA[https://www.goodreads.com/review/show/67890]]></link>
98
+ <owned>0</owned>
99
+ </review>
100
+
101
+
102
+ </GoodreadsResponse>
@@ -0,0 +1,144 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <GoodreadsResponse>
3
+ <Request>
4
+ <authentication>true</authentication>
5
+ <key><![CDATA[SECRET_KEY]]></key>
6
+ <method><![CDATA[review_show_by_user_and_book]]></method>
7
+ </Request>
8
+ <review>
9
+ <id>21</id>
10
+
11
+ <user>
12
+ <id>1</id>
13
+ <uri>kca://profile:goodreads/A2CSQET51X1KEY</uri>
14
+ <name>Otis</name>
15
+ <display_name>Otis Chandler</display_name>
16
+
17
+ <location>San Francisco, CA</location>
18
+
19
+ <link><![CDATA[https://www.goodreads.com/user/show/1-otis-chandler]]></link>
20
+
21
+ <image_url><![CDATA[https://images.gr-assets.com/users/1506617226p3/1.jpg]]></image_url>
22
+ <small_image_url><![CDATA[https://images.gr-assets.com/users/1506617226p2/1.jpg]]></small_image_url>
23
+ <has_image>true</has_image>
24
+
25
+ </user>
26
+
27
+ <book>
28
+ <id type="integer">50</id>
29
+ <isbn>0689840926</isbn>
30
+ <isbn13>9780689840920</isbn13>
31
+ <text_reviews_count type="integer">10396</text_reviews_count>
32
+ <uri>kca://book/amzn1.gr.book.v1.RXVB3nJhipnhI-zLlVeRTg</uri>
33
+ <title>Hatchet</title>
34
+ <title_without_series>Hatchet</title_without_series>
35
+ <image_url>https://s.gr-assets.com/assets/nophoto/book/111x148-bcc042a9c91a29c1d680899eff700a03.png</image_url>
36
+ <small_image_url>https://s.gr-assets.com/assets/nophoto/book/50x75-a91bf249278a81aabab721ef782c4a74.png</small_image_url>
37
+ <large_image_url/>
38
+ <link>https://www.goodreads.com/book/show/50.Hatchet</link>
39
+ <num_pages>208</num_pages>
40
+ <format>Hardcover</format>
41
+ <edition_information/>
42
+ <publisher>Atheneum Books for Young Readers: Richard Jackson Books</publisher>
43
+ <publication_day>1</publication_day>
44
+ <publication_year>2000</publication_year>
45
+ <publication_month>4</publication_month>
46
+ <average_rating>3.69</average_rating>
47
+ <ratings_count>250763</ratings_count>
48
+ <description>Brian is on his way to Canada to visit his estranged father when the pilot of his small prop plane suffers a heart attack. Brian is forced to crash-land the plane in a lake--and finds himself stranded in the remote Canadian wilderness with only his clothing and the hatchet his mother gave him as a present before his departure. &lt;br /&gt;&lt;br /&gt;Brian had been distraught over his parents' impending divorce and the secret he carries about his mother, but now he is truly desolate and alone. Exhausted, terrified, and hungry, Brian struggles to find food and make a shelter for himself. He has no special knowledge of the woods, and he must find a new kind of awareness and patience as he meets each day's challenges. Is the water safe to drink? Are the berries he finds poisonous? &lt;br /&gt;&lt;br /&gt;Slowly, Brian learns to turn adversity to his advantage--an invading porcupine unexpectedly shows him how to make fire, a devastating tornado shows him how to retrieve supplies from the submerged airplane. Most of all, Brian leaves behind the self-pity he has felt about his predicament as he summons the courage to stay alive. &lt;br /&gt;&lt;br /&gt;A story of survival and of transformation, this riveting book has sparked many a reader's interest in venturing into the wild.</description>
49
+ <authors>
50
+ <author>
51
+ <id>18</id>
52
+ <name>Gary Paulsen</name>
53
+ <role></role>
54
+ <image_url nophoto='false'>
55
+ <![CDATA[https://images.gr-assets.com/authors/1309159225p5/18.jpg]]>
56
+ </image_url>
57
+ <small_image_url nophoto='false'>
58
+ <![CDATA[https://images.gr-assets.com/authors/1309159225p2/18.jpg]]>
59
+ </small_image_url>
60
+ <link><![CDATA[https://www.goodreads.com/author/show/18.Gary_Paulsen]]></link>
61
+ <average_rating>3.76</average_rating>
62
+ <ratings_count>399462</ratings_count>
63
+ <text_reviews_count>28012</text_reviews_count>
64
+ </author>
65
+ </authors>
66
+ <published>2000</published>
67
+ <work> <id>1158125</id>
68
+ <uri>kca://work/amzn1.gr.work.v1.r_sm_y7tG-b00YGE5HqhQQ</uri>
69
+ </work></book>
70
+
71
+
72
+ <read_statuses>
73
+ <read_status>
74
+ <id type="integer">1610038200</id>
75
+ <old_status></old_status>
76
+ <status>read</status>
77
+ <updated_at type="datetime">Fri Dec 09 22:55:21 -0800 2016</updated_at>
78
+ <ratings_count>0</ratings_count>
79
+ <comments_count>0</comments_count>
80
+ </read_status>
81
+ </read_statuses>
82
+ <rating>5</rating>
83
+ <votes>1</votes>
84
+ <spoiler_flag>false</spoiler_flag>
85
+ <spoilers_state>none</spoilers_state>
86
+
87
+
88
+ <shelves>
89
+ <shelf name="read" exclusive="true" id="5625761" review_shelf_id= ""/>
90
+
91
+ <shelf exclusive='false' id='343477' name='childrensbooks' review_shelf_id='175' sortable='false'></shelf>
92
+
93
+ <shelf exclusive='false' id='348056' name='fiction' review_shelf_id='687' sortable='false'></shelf>
94
+
95
+ <shelf exclusive='false' id='348035' name='fiction' review_shelf_id='209' sortable='false'></shelf>
96
+
97
+ </shelves>
98
+
99
+ <recommended_for></recommended_for>
100
+ <recommended_by></recommended_by>
101
+ <started_at></started_at>
102
+ <read_at></read_at>
103
+ <date_added>Tue Aug 29 11:20:01 -0700 2006</date_added>
104
+ <date_updated>Wed Mar 22 11:44:07 -0700 2017</date_updated>
105
+ <read_count>1</read_count>
106
+ <body>
107
+ </body>
108
+
109
+
110
+
111
+ <comments_count>1</comments_count>
112
+ <comments start="1" end="1" total="1">
113
+ <comment>
114
+ <id>161860175</id>
115
+ <body><![CDATA[love this book i read it in sixth grade and we had journals about it]]></body>
116
+
117
+ <user>
118
+ <id>64170165</id>
119
+ <uri>kca://profile:goodreads/A2PCDUCFEEK4OB</uri>
120
+ <name>Alise</name>
121
+ <display_name>Alise</display_name>
122
+
123
+ <location>The United States</location>
124
+
125
+ <link><![CDATA[https://www.goodreads.com/user/show/64170165-alise]]></link>
126
+
127
+ <image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_111x148-9394ebedbb3c6c218f64be9549657029.png]]></image_url>
128
+ <small_image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_50x66-632230dc9882b4352d753eedf9396530.png]]></small_image_url>
129
+ <has_image>false</has_image>
130
+
131
+ </user>
132
+
133
+ <created_at>Sun Jan 22 22:49:10 -0800 2017</created_at>
134
+ <updated_at>Sun Jan 22 22:49:10 -0800 2017</updated_at>
135
+ </comment>
136
+
137
+ </comments>
138
+ <url><![CDATA[https://www.goodreads.com/review/show/21]]></url>
139
+ <link><![CDATA[https://www.goodreads.com/review/show/21]]></link>
140
+ <owned>0</owned>
141
+ </review>
142
+
143
+
144
+ </GoodreadsResponse>
@@ -0,0 +1,104 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <GoodreadsResponse>
3
+ <Request>
4
+ <authentication>true</authentication>
5
+ <key><![CDATA[SECRET_KEY]]></key>
6
+ <method><![CDATA[review_update_xml]]></method>
7
+ </Request>
8
+ <review>
9
+ <id>67890</id>
10
+
11
+ <user>
12
+ <id>123456</id>
13
+ <uri>kca://profile:goodreads/A25C3Y1SBGK9NK</uri>
14
+ <name>Test</name>
15
+ <display_name>Test User</display_name>
16
+
17
+ <location>Lafayette, CA</location>
18
+
19
+ <link><![CDATA[https://www.goodreads.com/user/show/123456-test-user]]></link>
20
+
21
+ <image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_111x148-9394ebedbb3c6c218f64be9549657029.png]]></image_url>
22
+ <small_image_url><![CDATA[https://s.gr-assets.com/assets/nophoto/user/u_50x66-632230dc9882b4352d753eedf9396530.png]]></small_image_url>
23
+ <has_image>false</has_image>
24
+
25
+ </user>
26
+
27
+ <book>
28
+ <id type="integer">456</id>
29
+ <isbn>0195138570</isbn>
30
+ <isbn13>9780195138573</isbn13>
31
+ <text_reviews_count type="integer">11</text_reviews_count>
32
+ <uri>kca://book/amzn1.gr.book.v1.oHwPZrciugo6gPSINsfCaw</uri>
33
+ <title>Mindware: An Introduction to the Philosophy of Cognitive Science</title>
34
+ <title_without_series>Mindware: An Introduction to the Philosophy of Cognitive Science</title_without_series>
35
+ <image_url>https://s.gr-assets.com/assets/nophoto/book/111x148-bcc042a9c91a29c1d680899eff700a03.png</image_url>
36
+ <small_image_url>https://s.gr-assets.com/assets/nophoto/book/50x75-a91bf249278a81aabab721ef782c4a74.png</small_image_url>
37
+ <large_image_url/>
38
+ <link>https://www.goodreads.com/book/show/456.Mindware</link>
39
+ <num_pages>224</num_pages>
40
+ <format>Paperback</format>
41
+ <edition_information>1st edition</edition_information>
42
+ <publisher>Oxford University Press, USA</publisher>
43
+ <publication_day>21</publication_day>
44
+ <publication_year>2000</publication_year>
45
+ <publication_month>12</publication_month>
46
+ <average_rating>3.78</average_rating>
47
+ <ratings_count>210</ratings_count>
48
+ <description>&lt;p&gt;&lt;i&gt;Mindware&lt;/i&gt; is an introductory text with a difference. In eight short chapters it tells a story and invites the reader to join in some up-to-the-minute conceptual discussion of the key issues, problems, and opportunities in cognitive science. The story is about the search for a cognitive scientific understanding of mind. It is presented as a no-holds-barred journey from early work in Artificial Intelligence, through connectionist (artificial neural network) counter-visions, and onto neuroscience artificial life, dynamics and robotics. The journey ends with some wide-ranging and provacative speculation about the role of technology and the changing nature of the human mind itself.&lt;br /&gt;Each chapter is organized as an initial sketch of a research program or theme, followed by a substantial discussion section in which specific problems and issues (both familiear and cutting-edge) are raised and pursued. Discussion topics include mental causation, the hardware/software distinction, the relations between life and mind, the nature of perception, cognition and action, and the continuity (or otherwise) of high-level human intelligence with other forms of adaptive response. Classic topics are treated alongside the newer ones in an integrated treatment of the various discussions. The sketches and discussions are accompanied by numerous figures and boxed sections, and followed by suggestions for futher reading.&lt;/p&gt;</description>
49
+ <authors>
50
+ <author>
51
+ <id>3445871</id>
52
+ <name>Andy Clark</name>
53
+ <role></role>
54
+ <image_url nophoto='false'>
55
+ <![CDATA[https://images.gr-assets.com/authors/1269856267p5/3445871.jpg]]>
56
+ </image_url>
57
+ <small_image_url nophoto='false'>
58
+ <![CDATA[https://images.gr-assets.com/authors/1269856267p2/3445871.jpg]]>
59
+ </small_image_url>
60
+ <link><![CDATA[https://www.goodreads.com/author/show/3445871.Andy_Clark]]></link>
61
+ <average_rating>3.89</average_rating>
62
+ <ratings_count>992</ratings_count>
63
+ <text_reviews_count>74</text_reviews_count>
64
+ </author>
65
+ </authors>
66
+ <published>2000</published>
67
+ <work> <id>282613</id>
68
+ <uri>kca://work/amzn1.gr.work.v1.xCZQesiWRuaIVPV6wsdFGA</uri>
69
+ </work></book>
70
+
71
+
72
+ <rating>5</rating>
73
+ <votes>0</votes>
74
+ <spoiler_flag>false</spoiler_flag>
75
+ <spoilers_state>none</spoilers_state>
76
+
77
+
78
+ <shelves>
79
+ <shelf name="read" exclusive="true" id="269274694" review_shelf_id= ""/>
80
+
81
+ </shelves>
82
+
83
+ <recommended_for></recommended_for>
84
+ <recommended_by></recommended_by>
85
+ <started_at></started_at>
86
+ <read_at>Fri Aug 20 00:00:00 -0700 2004</read_at>
87
+ <date_added>Wed Jun 13 20:52:06 -0700 2018</date_added>
88
+ <date_updated>Wed Jun 13 20:52:08 -0700 2018</date_updated>
89
+ <read_count>1</read_count>
90
+ <body>Fantastic book.</body>
91
+ <body_raw>
92
+
93
+ </body_raw>
94
+
95
+
96
+
97
+ <comments_count>0</comments_count>
98
+ <url><![CDATA[https://www.goodreads.com/review/show/67890]]></url>
99
+ <link><![CDATA[https://www.goodreads.com/review/show/67890]]></link>
100
+ <owned>0</owned>
101
+ </review>
102
+
103
+
104
+ </GoodreadsResponse>
@@ -0,0 +1,24 @@
1
+ <GoodreadsResponse>
2
+ <Request>
3
+ <authentication>true</authentication>
4
+ <key><![CDATA[SECRET_KEY]]></key>
5
+ <method><![CDATA[shelf_add_to_shelf]]></method>
6
+ </Request>
7
+ <my_review>
8
+ <id>2416981504</id>
9
+ <book_id>456</book_id>
10
+ <rating>0</rating>
11
+ <updated_at>Thu Jun 07 19:58:53 -0700 2018</updated_at>
12
+
13
+ <shelves>
14
+ <shelf name="read" exclusive="true" id="269274694" review_shelf_id= ""/>
15
+ </shelves>
16
+
17
+ <body></body>
18
+ <body_raw></body_raw>
19
+ <spoiler_flag>false</spoiler_flag>
20
+ <started_at></started_at>
21
+ <read_at></read_at>
22
+ <date_added>Thu Jun 07 19:58:19 -0700 2018</date_added>
23
+ </my_review>
24
+ </GoodreadsResponse>
@@ -0,0 +1,51 @@
1
+ <GoodreadsResponse>
2
+ <Request>
3
+ <authentication>true</authentication>
4
+ <key><![CDATA[SECRET_KEY]]></key>
5
+ <method><![CDATA[ shelf_list ]]></method>
6
+ </Request>
7
+ <shelves start="1" end="3" total="3">
8
+ <user_shelf>
9
+ <id type="integer">9049904</id>
10
+ <name>read</name>
11
+ <book_count type="integer">0</book_count>
12
+ <exclusive_flag type="boolean">true</exclusive_flag>
13
+ <description nil="true"/>
14
+ <sort nil="true"/>
15
+ <order nil="true"/>
16
+ <per_page type="integer" nil="true"/>
17
+ <display_fields/>
18
+ <featured type="boolean">true</featured>
19
+ <recommend_for type="boolean">true</recommend_for>
20
+ <sticky type="boolean" nil="true"/>
21
+ </user_shelf>
22
+ <user_shelf>
23
+ <id type="integer">3701</id>
24
+ <name>currently-reading</name>
25
+ <book_count type="integer">0</book_count>
26
+ <exclusive_flag type="boolean">true</exclusive_flag>
27
+ <description nil="true"/>
28
+ <sort nil="true"/>
29
+ <order nil="true"/>
30
+ <per_page type="integer" nil="true"/>
31
+ <display_fields/>
32
+ <featured type="boolean">false</featured>
33
+ <recommend_for type="boolean">true</recommend_for>
34
+ <sticky type="boolean" nil="true"/>
35
+ </user_shelf>
36
+ <user_shelf>
37
+ <id type="integer">3700</id>
38
+ <name>to-read</name>
39
+ <book_count type="integer">0</book_count>
40
+ <exclusive_flag type="boolean">true</exclusive_flag>
41
+ <description nil="true"/>
42
+ <sort nil="true"/>
43
+ <order>a</order>
44
+ <per_page type="integer" nil="true"/>
45
+ <display_fields/>
46
+ <featured type="boolean">false</featured>
47
+ <recommend_for type="boolean">true</recommend_for>
48
+ <sticky type="boolean" nil="true"/>
49
+ </user_shelf>
50
+ </shelves>
51
+ </GoodreadsResponse>
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
- describe "Goodreads" do
3
+ describe Goodreads do
4
4
  describe ".new" do
5
5
  it "returns a new client instance" do
6
6
  expect(Goodreads.new).to be_a(Goodreads::Client)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goodreads
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Sosedoff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-09 00:00:00.000000000 Z
11
+ date: 2018-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webmock
@@ -192,9 +192,14 @@ files:
192
192
  - spec/fixtures/oauth_response.xml
193
193
  - spec/fixtures/recent_reviews.xml
194
194
  - spec/fixtures/review.xml
195
+ - spec/fixtures/review_create.xml
196
+ - spec/fixtures/review_show_by_user_and_book.xml
197
+ - spec/fixtures/review_update.xml
195
198
  - spec/fixtures/reviews.xml
196
199
  - spec/fixtures/reviews_empty.xml
197
200
  - spec/fixtures/search_books_by_name.xml
201
+ - spec/fixtures/shelf_add_to_shelf.xml
202
+ - spec/fixtures/shelf_list.xml
198
203
  - spec/fixtures/to-read-p2.xml
199
204
  - spec/fixtures/to-read.xml
200
205
  - spec/fixtures/user.xml
@@ -220,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
225
  version: '0'
221
226
  requirements: []
222
227
  rubyforge_project:
223
- rubygems_version: 2.6.13
228
+ rubygems_version: 2.7.7
224
229
  signing_key:
225
230
  specification_version: 4
226
231
  summary: Goodreads API wrapper
@@ -237,9 +242,14 @@ test_files:
237
242
  - spec/fixtures/oauth_response.xml
238
243
  - spec/fixtures/recent_reviews.xml
239
244
  - spec/fixtures/review.xml
245
+ - spec/fixtures/review_create.xml
246
+ - spec/fixtures/review_show_by_user_and_book.xml
247
+ - spec/fixtures/review_update.xml
240
248
  - spec/fixtures/reviews.xml
241
249
  - spec/fixtures/reviews_empty.xml
242
250
  - spec/fixtures/search_books_by_name.xml
251
+ - spec/fixtures/shelf_add_to_shelf.xml
252
+ - spec/fixtures/shelf_list.xml
243
253
  - spec/fixtures/to-read-p2.xml
244
254
  - spec/fixtures/to-read.xml
245
255
  - spec/fixtures/user.xml