sucker 0.5.0 → 0.6.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/README.md +14 -11
- data/lib/sucker/response.rb +23 -1
- data/lib/sucker/stub.rb +6 -1
- data/lib/sucker.rb +1 -1
- data/spec/benchmark/to_hash_implementations.rb +38 -0
- data/spec/integration/item_lookup_spec.rb +2 -2
- data/spec/integration/japan_spec.rb +1 -1
- data/spec/integration/seller_listing_search_spec.rb +1 -1
- data/spec/integration/twenty_items_in_one_request_spec.rb +1 -1
- data/spec/unit/sucker/response_spec.rb +6 -2
- data/spec/unit/sucker/stub_spec.rb +31 -0
- metadata +33 -13
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Sucker
|
2
2
|
======
|
3
3
|
|
4
|
-
Sucker is a thin Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html). It runs on
|
4
|
+
Sucker is a paper-thin Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html). It runs on cURL and Nokogiri and supports __everything__ in the API.
|
5
5
|
|
6
6
|

|
7
7
|
|
@@ -24,7 +24,7 @@ Set up a request.
|
|
24
24
|
worker << {
|
25
25
|
"Operation" => "ItemLookup",
|
26
26
|
"IdType" => "ASIN",
|
27
|
-
"ItemId" =>
|
27
|
+
"ItemId" => asin_batch
|
28
28
|
|
29
29
|
Hit Amazon and do something with the response.
|
30
30
|
|
@@ -33,12 +33,11 @@ Hit Amazon and do something with the response.
|
|
33
33
|
p response.time
|
34
34
|
p response.body
|
35
35
|
|
36
|
-
response.to_h["Items"]["Item"].each { ... }
|
36
|
+
response.to_h["ItemLookupResponse"]["Items"]["Item"].each { ... }
|
37
37
|
|
38
38
|
Hit Amazon again.
|
39
39
|
|
40
|
-
worker << {
|
41
|
-
"ItemId" => 10.more.asins }
|
40
|
+
worker << { "ItemId" => another_asin_batch }
|
42
41
|
response = worker.get
|
43
42
|
|
44
43
|
Check the integration specs for more examples.
|
@@ -46,22 +45,26 @@ Check the integration specs for more examples.
|
|
46
45
|
Testing
|
47
46
|
-------
|
48
47
|
|
49
|
-
To fake web requests,
|
50
|
-
|
51
|
-
In a file such as `spec/support/sucker.rb`, I prep:
|
48
|
+
To fake web requests, create `spec/support/sucker.rb` and:
|
52
49
|
|
53
50
|
require "sucker/stub"
|
54
51
|
Sucker.fixtures_path = File.dirname(__FILE__) + "/../fixtures"
|
55
52
|
|
56
|
-
|
53
|
+
Then, in your spec, stub the worker:
|
57
54
|
|
55
|
+
@worker = Sucker.new(some_hash)
|
58
56
|
Sucker.stub(@worker)
|
59
57
|
|
60
|
-
The first time you run the spec,
|
58
|
+
The first time you run the spec, Sucker will perform the actual web request and cache the response. Then, it will stub subsequent requests with the cached response.
|
61
59
|
|
62
60
|
Notes
|
63
61
|
-----
|
64
62
|
|
65
63
|
* The unit specs should run out of the box after you `bundle install`, but the integration specs require you to create [an amazon.yml file with valid credentials](http://github.com/papercavalier/sucker/blob/master/spec/support/amazon.yml.example) in `spec/support`.
|
66
64
|
|
67
|
-
* Version 0.
|
65
|
+
* Version 0.6.0 now has Active Support's Nokogiri-based `to_hash` under the hood. After some meddling, it does what it's supposed to do and is blazing fast. Fix up your code accordingly.
|
66
|
+
|
67
|
+
user system total real
|
68
|
+
Crack 0.830000 0.010000 0.840000 ( 0.871951)
|
69
|
+
SimpleXml 2.470000 0.050000 2.520000 ( 2.560045)
|
70
|
+
AS + Nokogiri 0.440000 0.010000 0.450000 ( 0.450201)
|
data/lib/sucker/response.rb
CHANGED
@@ -11,7 +11,29 @@ module Sucker
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def to_h
|
14
|
-
|
14
|
+
doc = Nokogiri::XML(body)
|
15
|
+
content_to_string(doc.to_hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :to_hash, :to_h
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def content_to_string(node)
|
23
|
+
case node
|
24
|
+
when Array
|
25
|
+
node.map { |el| content_to_string(el) }
|
26
|
+
when Hash
|
27
|
+
if node.keys.size == 1 && node["__content__"]
|
28
|
+
node["__content__"]
|
29
|
+
else
|
30
|
+
node.inject({}) do |el, key_value|
|
31
|
+
el.merge({ key_value.first => content_to_string(key_value.last) })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
node
|
36
|
+
end
|
15
37
|
end
|
16
38
|
end
|
17
39
|
end
|
data/lib/sucker/stub.rb
CHANGED
@@ -14,7 +14,12 @@ module Sucker
|
|
14
14
|
def stub(request)
|
15
15
|
request.instance_eval do
|
16
16
|
self.class.send :define_method, :fixture do
|
17
|
-
filename = parameters.
|
17
|
+
filename = parameters.
|
18
|
+
values.
|
19
|
+
flatten.
|
20
|
+
join.
|
21
|
+
gsub(/(?:[aeiou13579]|[^\w])+/i, '')[0, 251]
|
22
|
+
|
18
23
|
"#{Sucker.fixtures_path}/#{filename}.xml"
|
19
24
|
end
|
20
25
|
|
data/lib/sucker.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
require "crack/xml"
|
5
|
+
require "xmlsimple"
|
6
|
+
|
7
|
+
@worker = Sucker.new(
|
8
|
+
:locale => "us",
|
9
|
+
:key => amazon["key"],
|
10
|
+
:secret => amazon["secret"])
|
11
|
+
|
12
|
+
# Prep worker
|
13
|
+
@worker << {
|
14
|
+
"Operation" => "ItemLookup",
|
15
|
+
"ItemLookup.Shared.IdType" => "ASIN",
|
16
|
+
"ItemLookup.Shared.Condition" => "All",
|
17
|
+
"ItemLookup.Shared.MerchantId" => "All",
|
18
|
+
"ItemLookup.Shared.ResponseGroup" => "OfferFull" }
|
19
|
+
|
20
|
+
# Push twenty ASINs to worker
|
21
|
+
@asins = %w{
|
22
|
+
0816614024 0143105825 0485113600 0816616779 0942299078
|
23
|
+
0816614008 144006654X 0486400360 0486417670 087220474X
|
24
|
+
0486454398 0268018359 1604246014 184467598X 0312427182
|
25
|
+
1844674282 0745640974 0745646441 0826489540 1844672972 }
|
26
|
+
@worker << {
|
27
|
+
"ItemLookup.1.ItemId" => @asins[0, 10],
|
28
|
+
"ItemLookup.2.ItemId" => @asins[10, 10] }
|
29
|
+
|
30
|
+
Sucker.stub(@worker)
|
31
|
+
|
32
|
+
response = @worker.get
|
33
|
+
|
34
|
+
Benchmark.bm(20) do |x|
|
35
|
+
x.report("Crack") { Crack::XML.parse(response.body) }
|
36
|
+
x.report("SimpleXml") { XmlSimple.xml_in(response.body, { "ForceArray" => false }) }
|
37
|
+
x.report("AS + Nokogiri") { response.to_h }
|
38
|
+
end
|
@@ -23,7 +23,7 @@ module Sucker
|
|
23
23
|
context "single item" do
|
24
24
|
before do
|
25
25
|
@worker << { "ItemId" => "0816614024" }
|
26
|
-
@item = @worker.get.to_h["Items"]["Item"]
|
26
|
+
@item = @worker.get.to_h["ItemLookupResponse"]["Items"]["Item"]
|
27
27
|
end
|
28
28
|
|
29
29
|
it "returns an item" do
|
@@ -43,7 +43,7 @@ module Sucker
|
|
43
43
|
context "multiple items" do
|
44
44
|
before do
|
45
45
|
@worker << { "ItemId" => ["0816614024", "0143105825"] }
|
46
|
-
@items = @worker.get.to_h["Items"]["Item"]
|
46
|
+
@items = @worker.get.to_h["ItemLookupResponse"]["Items"]["Item"]
|
47
47
|
end
|
48
48
|
|
49
49
|
it "returns two items" do
|
@@ -5,7 +5,7 @@ module Sucker
|
|
5
5
|
before do
|
6
6
|
curl = Sucker.new.curl
|
7
7
|
curl.stub(:get).and_return(nil)
|
8
|
-
curl.stub!(:body_str).and_return("
|
8
|
+
curl.stub!(:body_str).and_return('<?xml version="1.0" ?><books><book><creator role="author">Gilles Deleuze</author><title>A Thousand Plateaus</title></book><book><creator role="author">Gilles Deleuze</author><title>Anti-Oedipus</title></book></books>')
|
9
9
|
curl.stub!(:response_code).and_return(200)
|
10
10
|
curl.stub!(:total_time).and_return(1.0)
|
11
11
|
@response = Response.new(curl)
|
@@ -29,6 +29,10 @@ module Sucker
|
|
29
29
|
it "returns a hash" do
|
30
30
|
@response.to_h.should be_an_instance_of Hash
|
31
31
|
end
|
32
|
+
|
33
|
+
it "converts a content hash to string" do
|
34
|
+
@response.to_h["books"]["book"].first["title"].should be_an_instance_of String
|
35
|
+
end
|
32
36
|
end
|
33
37
|
end
|
34
|
-
end
|
38
|
+
end
|
@@ -39,6 +39,37 @@ module Sucker
|
|
39
39
|
|
40
40
|
@worker.get.body.should eql "bar"
|
41
41
|
end
|
42
|
+
|
43
|
+
context "defines:" do
|
44
|
+
context "#fixture" do
|
45
|
+
it "ignores odd numbers" do
|
46
|
+
@worker << { "Foo" => "13579" }
|
47
|
+
@worker.fixture.should_not match /[^\/]*[13579][^\/]*xml$/
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does not ignore odd numbers" do
|
51
|
+
@worker << { "Foo" => "24680" }
|
52
|
+
(0..8).step(2) do |odd_no|
|
53
|
+
@worker.fixture.should match Regexp.new(odd_no.to_s)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "ignores vowels" do
|
58
|
+
@worker << { "Foo" => "aeiou" }
|
59
|
+
@worker.fixture.should_not match /[^\/]*[aeiou][^\/]*xml$/
|
60
|
+
end
|
61
|
+
|
62
|
+
it "does not ignore consonants" do
|
63
|
+
@worker << { "Foo" => ("a".."z").to_a.join }
|
64
|
+
@worker.fixture.should include(("a".."z").to_a.join.gsub(/[aeiou]/, ""))
|
65
|
+
end
|
66
|
+
|
67
|
+
it "ignores non-alphanumeric characters" do
|
68
|
+
@worker << { "Foo" => ";+*&!~" }
|
69
|
+
@worker.fixture.should_not match /[;+*&!~]/
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
42
73
|
end
|
43
74
|
end
|
44
75
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sucker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 6
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.6.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Hakan Ensari
|
@@ -16,30 +16,48 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-08-02 00:00:00 +01:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
23
23
|
prerelease: false
|
24
24
|
type: :runtime
|
25
|
-
name:
|
25
|
+
name: activesupport
|
26
26
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
27
27
|
none: false
|
28
28
|
requirements:
|
29
29
|
- - ">="
|
30
30
|
- !ruby/object:Gem::Version
|
31
|
-
hash:
|
31
|
+
hash: 7712042
|
32
32
|
segments:
|
33
|
-
-
|
33
|
+
- 3
|
34
|
+
- 0
|
34
35
|
- 0
|
35
|
-
-
|
36
|
-
version:
|
36
|
+
- rc
|
37
|
+
version: 3.0.0.rc
|
37
38
|
requirement: *id001
|
38
39
|
- !ruby/object:Gem::Dependency
|
39
40
|
prerelease: false
|
40
41
|
type: :runtime
|
41
|
-
name:
|
42
|
+
name: nokogiri
|
42
43
|
version_requirements: &id002 !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
hash: 113
|
49
|
+
segments:
|
50
|
+
- 1
|
51
|
+
- 4
|
52
|
+
- 3
|
53
|
+
- 1
|
54
|
+
version: 1.4.3.1
|
55
|
+
requirement: *id002
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
prerelease: false
|
58
|
+
type: :runtime
|
59
|
+
name: curb
|
60
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
43
61
|
none: false
|
44
62
|
requirements:
|
45
63
|
- - ">="
|
@@ -51,8 +69,8 @@ dependencies:
|
|
51
69
|
- 7
|
52
70
|
- 1
|
53
71
|
version: 0.7.7.1
|
54
|
-
requirement: *
|
55
|
-
description:
|
72
|
+
requirement: *id003
|
73
|
+
description: A paper-thin Ruby wrapper to the Amazon Product Advertising API
|
56
74
|
email: code@papercavalier.com
|
57
75
|
executables: []
|
58
76
|
|
@@ -68,6 +86,7 @@ files:
|
|
68
86
|
- lib/sucker/response.rb
|
69
87
|
- lib/sucker/stub.rb
|
70
88
|
- README.md
|
89
|
+
- spec/benchmark/to_hash_implementations.rb
|
71
90
|
- spec/integration/item_lookup_spec.rb
|
72
91
|
- spec/integration/japan_spec.rb
|
73
92
|
- spec/integration/seller_listing_search_spec.rb
|
@@ -112,8 +131,9 @@ rubyforge_project:
|
|
112
131
|
rubygems_version: 1.3.7
|
113
132
|
signing_key:
|
114
133
|
specification_version: 3
|
115
|
-
summary: A thin Ruby wrapper to the Amazon Product Advertising API
|
134
|
+
summary: A paper-thin Ruby wrapper to the Amazon Product Advertising API
|
116
135
|
test_files:
|
136
|
+
- spec/benchmark/to_hash_implementations.rb
|
117
137
|
- spec/integration/item_lookup_spec.rb
|
118
138
|
- spec/integration/japan_spec.rb
|
119
139
|
- spec/integration/seller_listing_search_spec.rb
|