sucker 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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 Curb and XmlSimple and supports __everything__ in the API.
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
  ![Sucker](http://upload.wikimedia.org/wikipedia/en/7/71/Vacuum_cleaner_1910.JPG)
7
7
 
@@ -24,7 +24,7 @@ Set up a request.
24
24
  worker << {
25
25
  "Operation" => "ItemLookup",
26
26
  "IdType" => "ASIN",
27
- "ItemId" => ["0816614024", "0143105825"] }
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, I do the following:
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
- In the spec, I set up a worker and then stub it:
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, the worker will perform the actual web request and cache the response. Subsequent requests are then mocked with the cached response.
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.5.0 replaces Crack with XmlSimple, which results in a slightly different hash output. Fix up your code accordingly.
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)
@@ -11,7 +11,29 @@ module Sucker
11
11
  end
12
12
 
13
13
  def to_h
14
- XmlSimple.xml_in(body, { "ForceArray" => false })
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.values.flatten.sort.join.gsub(/[^\w\-\/]+/, '_')[0, 251]
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
@@ -1,8 +1,8 @@
1
1
  require "cgi"
2
2
  require "curb"
3
+ require "active_support/xml_mini/nokogiri"
3
4
  require "sucker/request"
4
5
  require "sucker/response"
5
- require "xmlsimple"
6
6
 
7
7
  # = Sucker
8
8
  # Sucker is a thin Ruby wrapper to the Amazon Product Advertising API.
@@ -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
@@ -18,7 +18,7 @@ module Sucker
18
18
  context "single item" do
19
19
  before do
20
20
  @worker << { "ItemId" => "0816614024" }
21
- @item = @worker.get.to_h["Items"]["Item"]
21
+ @item = @worker.get.to_h["ItemLookupResponse"]["Items"]["Item"]
22
22
  end
23
23
 
24
24
  it "returns an array of items" do
@@ -16,7 +16,7 @@ module Sucker
16
16
 
17
17
  Sucker.stub(@worker)
18
18
 
19
- @listings = @worker.get.to_h["SellerListings"]
19
+ @listings = @worker.get.to_h["SellerListingSearchResponse"]["SellerListings"]
20
20
  end
21
21
 
22
22
  it "returns page count" do
@@ -28,7 +28,7 @@ module Sucker
28
28
 
29
29
  Sucker.stub(@worker)
30
30
 
31
- @items = @worker.get.to_h["Items"].map { |items| items["Item"] }.flatten!
31
+ @items = @worker.get.to_h["ItemLookupResponse"]["Items"].map { |items| items["Item"] }.flatten!
32
32
  end
33
33
 
34
34
  it "returns 20 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("<foo bar='baz' />")
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: 11
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 5
8
+ - 6
9
9
  - 0
10
- version: 0.5.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-07-31 00:00:00 +01:00
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: xml-simple
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: 15
31
+ hash: 7712042
32
32
  segments:
33
- - 1
33
+ - 3
34
+ - 0
34
35
  - 0
35
- - 12
36
- version: 1.0.12
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: curb
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: *id002
55
- description: Sucker is a thin Ruby wrapper to the Amazon Product Advertising API.
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