Permalink
Browse files

use HTTPI and add ItemSearch

  • Loading branch information...
1 parent d97b546 commit 51532d328e63bdde9cdd58f52e73cb111a110cd8 @phoet phoet committed Jan 4, 2011
Showing with 148 additions and 63 deletions.
  1. +2 −1 .rvmrc
  2. +2 −2 Gemfile
  3. +5 −1 Gemfile.lock
  4. +13 −2 README.rdoc
  5. +17 −19 asin.gemspec
  6. +58 −30 lib/asin.rb
  7. +24 −0 lib/asin/item.rb
  8. +3 −0 lib/asin/version.rb
  9. +3 −1 rakefile.rb
  10. +21 −7 test/test_asin.rb
View
3 .rvmrc
@@ -1 +1,2 @@
-rvm use 1.9.1@asin
+rvm use 1.9.2@asin --create
+rvm wrapper 1.9.2@asin textmate
View
@@ -1,4 +1,4 @@
-source :gemcutter
+source "http://rubygems.org"
-# dependencies in rails_redis_cache.gemspec
+# Specify your gem's dependencies in asin.gemspec
gemspec
View
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- asin (0.0.8)
+ asin (0.0.9)
crack (~> 0.1.7)
hashie (~> 0.2.1)
httpclient (~> 2.1.5.2)
@@ -12,6 +12,9 @@ GEM
crack (0.1.8)
hashie (0.2.2)
httpclient (2.1.5.2)
+ mocha (0.9.10)
+ rake
+ rake (0.8.7)
PLATFORMS
ruby
@@ -21,3 +24,4 @@ DEPENDENCIES
crack (~> 0.1.7)
hashie (~> 0.2.1)
httpclient (~> 2.1.5.2)
+ mocha (~> 0.9.10)
View
@@ -1,8 +1,7 @@
== Installation
-The gem is tested against 1.9.1 and 1.8.7 (compatibility with Heroku-Bamboo-Stack[http://docs.heroku.com/stack]) and runs smoothly with Rails 3.
+The gem is tested against 1.9.2, 1.9.1 and 1.8.7 (compatibility with Heroku-Bamboo-Stack[http://docs.heroku.com/stack]) and runs smoothly with Rails 3.
- rvm use 1.9.1
gem install asin
== Usage
@@ -20,10 +19,22 @@ The gem is tested against 1.9.1 and 1.8.7 (compatibility with Heroku-Bamboo-Stac
item.title
=> Learn Objective-C on the Mac (Learn Series)
+ # search for any kind of stuff on amazon
+ items = search 'Learn Objective-C'
+ items.first.title
+ => "Learn Objective-C on the Mac (Learn Series)"
+
# access the internal data representation (Hashie::Mash)
item.raw.ItemAttributes.ListPrice.FormattedPrice
=> $39.99
+== HTTPI
+
+ASIN uses HTTPI[https://github.com/rubiii/httpi] as a HTTP-Client adapter.
+You can configure the Client you like via configure:
+
+ configure :client => :curb
+
== Infos
Have a look at the RDOC[http://rdoc.info/projects/phoet/asin] for this project, if you want further information.
View
@@ -1,23 +1,21 @@
-# coding: utf-8
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "asin/version"
-spec = Gem::Specification.new do |s|
- s.name = 'asin'
- s.version = '0.0.8'
-
- s.author = 'Peter Schröder'
+Gem::Specification.new do |s|
+ s.name = "asin"
+ s.version = Asin::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ['Peter Schröder']
+ s.email = ['phoetmail@googlemail.com']
+ s.homepage = 'http://github.com/phoet/asin'
+ s.summary = 'Simple interface to Amazon Item lookup.'
s.description = 'Amazon Simple INterface or whatever you want to call this.'
- s.email = 'phoetmail@googlemail.com'
- s.homepage = 'http://github.com/phoet/asin'
- s.summary = 'Simple interface to Amazon Item lookup.'
- s.has_rdoc = true
- s.rdoc_options = ['-a', '--inline-source', '--charset=UTF-8']
-
- s.files = Dir.glob('lib/*.rb') + %w(README.rdoc)
- s.test_files = Dir.glob('test/test_*.rb')
-
- s.add_dependency('crack', '~> 0.1.7')
- s.add_dependency('hashie', '~> 0.2.1')
- s.add_dependency('httpclient', '~> 2.1.5.2')
-end
+ s.rubyforge_project = "asin"
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+end
View
@@ -1,10 +1,12 @@
-require 'hashie'
-require 'httpclient'
+require 'httpi'
require 'crack/xml'
require 'cgi'
require 'base64'
require 'logger'
+require 'asin/item'
+require 'asin/version'
+
# ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
# It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
#
@@ -31,6 +33,12 @@
# item = lookup '1430218150'
# item.title
# => "Learn Objective-C on the Mac (Learn Series)"
+#
+# OR search with fulltext/ASIN/ISBN
+#
+# items = search 'Learn Objective-C'
+# items.first.title
+# => "Learn Objective-C on the Mac (Learn Series)"
#
# The +Item+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
#
@@ -48,26 +56,6 @@
#
module ASIN
- # =Item
- #
- # The +Item+ class is a wrapper for the Amazon XML-REST-Response.
- #
- # A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
- #
- class Item
-
- attr_reader :raw
-
- def initialize(hash)
- @raw = Hashie::Mash.new(hash).ItemLookupResponse.Items.Item
- end
-
- def title
- @raw.ItemAttributes.Title
- end
-
- end
-
# Configures the basic request parameters for ASIN.
#
# Expects at least +secret+ and +key+ for the API call:
@@ -79,13 +67,15 @@ def title
# [secret] the API secret key
# [key] the API access key
# [host] the host, which defaults to 'webservices.amazon.com'
+ # [client] the client library for http (:httpclient, :curb, :net_http) see HTTPI for more information
# [logger] a different logger than logging to STDERR
#
def configure(options={})
@options = {
:host => 'webservices.amazon.com',
:path => '/onca/xml',
:digest => OpenSSL::Digest::Digest.new('sha256'),
+ :client => :httpclient,
:logger => Logger.new(STDERR),
:key => '',
:secret => '',
@@ -108,22 +98,57 @@ def configure(options={})
# lookup(asin, :ResponseGroup => :Medium)
#
def lookup(asin, params={})
- Item.new(call(params.merge(:Operation => :ItemLookup, :ItemId => asin)))
+ response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
+ Item.new(response['ItemLookupResponse']['Items']['Item'])
+ end
+
+ # Performs an +ItemSearch+ REST call against the Amazon API.
+ #
+ # Expects a search-string which can be an ASIN (Amazon Standard Identification Number) and returns a list of +Items+:
+ #
+ # items = search 'Learn Objective-C'
+ # items.first.title
+ # => "Learn Objective-C on the Mac (Learn Series)"
+ #
+ # ==== Options:
+ #
+ # Additional parameters for the API call like this:
+ #
+ # search(asin, :SearchIndex => :Music)
+ #
+ # Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
+ #
+ def search(search_string, params={:SearchIndex => :Books})
+ response = call(params.merge(:Operation => :ItemSearch, :Keywords => search_string))
+ response['ItemSearchResponse']['Items']['Item'].map {|item| Item.new(item)}
end
private
def call(params)
raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" if @options.nil?
+
log(:debug, "calling with params=#{params}")
signed = create_signed_query_string(params)
+
url = "http://#{@options[:host]}#{@options[:path]}?#{signed}"
- log(:info, "performing rest call to url='#{url}'")
- resp = HTTPClient.new.get_content(url)
- # force utf-8 chars, works only on 1.9 string
- resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
- log(:debug, "got response='#{resp}'")
- Crack::XML.parse(resp)
+ log(:info, "performing rest call to url='#{url}' with client='#{@options[:client]}'")
+
+ HTTPI::Adapter.use = @options[:client]
+ HTTPI.logger = @options[:logger] if @options[:logger]
+ request = HTTPI::Request.new(url)
+ response = HTTPI.get(request)
+
+ if response.code == 200
+ # force utf-8 chars, works only on 1.9 string
+ resp = response.body
+ resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
+ log(:debug, "got response='#{resp}'")
+ Crack::XML.parse(resp)
+ else
+ log(:error, "got response='#{response.body}'")
+ raise "request failed with response-code='#{response.code}'"
+ end
end
def create_signed_query_string(params)
@@ -132,11 +157,14 @@ def create_signed_query_string(params)
params[:AWSAccessKeyId] = @options[:key]
# utc timestamp needed for signing
params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
+
# signing needs to order the query alphabetically
- query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&')
+ query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
+
# yeah, you really need to sign the get-request not the query
request_to_sign = "GET\n#{@options[:host]}\n#{@options[:path]}\n#{query}"
hmac = OpenSSL::HMAC.digest(@options[:digest], @options[:secret], request_to_sign)
+
# don't forget to remove the newline from base64
signature = CGI.escape(Base64.encode64(hmac).chomp)
"#{query}&Signature=#{signature}"
View
@@ -0,0 +1,24 @@
+require 'hashie'
+
+module ASIN
+
+ # =Item
+ #
+ # The +Item+ class is a wrapper for the Amazon XML-REST-Response.
+ #
+ # A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
+ #
+ class Item
+
+ attr_reader :raw
+
+ def initialize(hash)
+ @raw = Hashie::Mash.new(hash)
+ end
+
+ def title
+ @raw.ItemAttributes.Title
+ end
+ end
+
+end
View
@@ -0,0 +1,3 @@
+module Asin
+ VERSION = "0.0.9"
+end
View
@@ -1,14 +1,16 @@
+require "bundler"
require "rake/rdoctask"
require "rake/gempackagetask"
+Bundler::GemHelper.install_tasks
+
spec = eval(File.new("asin.gemspec").readlines.join("\n"))
Rake::GemPackageTask.new(spec) do |pkg|
pkg.need_zip = true
pkg.need_tar = true
end
-
Rake::RDocTask.new(:rdoc_dev) do |rd|
rd.rdoc_files.include("lib/**/*.rb", "README.rdoc")
rd.options + ['-a', '--inline-source', '--charset=UTF-8']
View
@@ -1,9 +1,11 @@
require 'test_helper'
-ANY_ASIN = '1430218150'
-
class TestAsin < Test::Unit::TestCase
+
+ ANY_ASIN = '1430218150'
+ ANY_SEARCH = 'Learn Objective-C'
+
def setup
@helper = Object.new
@helper.extend ASIN
@@ -13,12 +15,24 @@ def test_lookup_with_configured_asin
secret = ENV['ASIN_SECRET']
key = ENV['ASIN_KEY']
puts "configure #{secret} and #{key} for this test"
-
+
+ @helper.configure :secret => secret, :key => key
+ item = @helper.lookup(ANY_ASIN)
+ assert_match(/Learn Objective/, item.title)
+ end
+
+ def test_search_with_configured_string
+ secret = ENV['ASIN_SECRET']
+ key = ENV['ASIN_KEY']
+ puts "configure #{secret} and #{key} for this test"
+
@helper.configure :secret => secret, :key => key
- p item = @helper.lookup(ANY_ASIN)
+ items = @helper.search(ANY_SEARCH)
+ assert_equal(10, items.size)
+ item = items.first
assert_match(/Learn Objective/, item.title)
end
-
+
def test_configure_second_time_wont_get_overridden
config = @helper.configure :something => 'wont get overridden'
assert_not_nil(config[:something])
@@ -27,9 +41,9 @@ def test_configure_second_time_wont_get_overridden
assert_not_nil(config[:something])
assert_not_nil(config[:different])
end
-
+
def test_error_with_not_called_configure
assert_raise(RuntimeError) { @helper.lookup ANY_ASIN }
end
-end
+end

0 comments on commit 51532d3

Please sign in to comment.