amazonian 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ pkg/*
3
3
  .bundle
4
4
  *.swp
5
5
  .*.swp
6
+ .yardoc/*
@@ -1,10 +1,7 @@
1
1
  == Status
2
- This project is in active development as of 2010-11.
3
-
4
- Currently it can be used as a rails plugin, and maybe as a gem (untested).
2
+ This project is in active development as of 2010-12.
5
3
 
6
4
  == Installation
7
-
8
5
  Copy or symlink into your application's /lib folder.
9
6
 
10
7
  In your ApplicationController:
@@ -16,20 +13,31 @@ In your ApplicationController:
16
13
  Then create an initializer in config/initializers:
17
14
  require 'amazonian'
18
15
  Amazonian.setup do |config|
19
- config.secret = 'ZkD9VorhnJjoPxZp8S/bP0K3G4NBQPNQrfU4OOUn'
20
- config.key = 'AKIAJCTUFLKJWEVW6XVQ'
21
- #config.debug = true
16
+ #Put your Amazon Web Services Key and Secret Key in here
17
+ config.secret = '<>'
18
+ config.key = '<>'
19
+
20
+ # prints more debug info to the console. Default: false
21
+ # config.debug = true
22
+
23
+ # the default default_search is :All, other tempting options: :Music, :GourmetFood, etc
24
+ config.default_search = :Music
25
+
26
+ # the gem will attempt to filter out duplicate requests. Default: true
27
+ config.cache_last = true
22
28
  end
23
29
 
30
+
24
31
  == Usage
25
32
  Amazonian.asin "1430218150"
26
33
  #=> #<Amazonian::Item>
27
34
 
28
- #soon:
29
35
  Amazonian.search "The art of Computer Programming" #Title
30
36
  Amazonian.search "978-0201853926" #ISBN-13
31
37
  Amazonian.search "0201853922" #ISBN-10
32
38
  Amazonian.search "<whateveryouwant>" #Anything you might put in the search box on Amazon.com.
39
+ #=> #<Amazonian::Search>
40
+
33
41
 
34
42
  == Info
35
43
 
@@ -2,48 +2,84 @@ require 'patron'
2
2
  require 'crack'
3
3
  require 'hashie'
4
4
 
5
+ #
6
+ # This module is designed to allow easier querying of the Amazon Product Advertising API
7
+ # from within your Ruby or Rails applications.
8
+ #
9
+ # Basic usage requires first calling +Amazonian.setup+ to provide your Amazon AWS Key and
10
+ # Secret key for the module to use in querying the database.
11
+ #
12
+ # Amazons internal product ids can be used to retrieve data from the API with +Amazonian.asin+.
13
+ #
14
+ # Searching for products is done via +Amazonian.search+
15
+ #
16
+ # @author Robert L. Carpenter (Modern codebase, interface, gem-ability)
17
+ # @author phoet (Original ASIN interface and implementation of Request Signing, etc.) https://github.com/phoet/asin
18
+ #
19
+ # @api Amazon Web Services Product Advertising API
20
+ #
21
+ # @version 0.2.0
22
+ #
5
23
  module Amazonian
24
+
6
25
  #worker objects
7
26
  @@digest = OpenSSL::Digest::Digest.new('sha256')
8
27
  @@logger = Logger.new(STDERR)
9
28
  @@patron = Patron::Session.new
10
29
 
11
30
  #hold the most recent request/response
12
- @@request = nil
13
- @@response = nil
14
- @@query = nil
31
+ @request = nil
32
+ @response = nil
33
+ @query = nil
15
34
 
16
35
  #configuration variables
17
- @@host = 'webservices.amazon.com'
18
- @@path = '/onca/xml'
19
- @@debug = true
20
- @@key = ''
21
- @@secret = ''
36
+ @host = 'webservices.amazon.com'
37
+ @path = '/onca/xml'
38
+ @debug = true
39
+ @key = ''
40
+ @secret = ''
41
+ @default_search = :All
42
+ @cache_last = true
22
43
 
23
44
  mattr_reader :host,:path,:response,:request
24
- mattr_accessor :key,:secret,:debug
45
+ mattr_accessor :key,:secret,:debug,:default_search,:cache_last
25
46
 
26
47
  #Configure Patron
27
48
  @@patron.timeout = 10
28
49
 
50
+ #
29
51
  # Configure the basic request parameters for Amazonian.
52
+ #
53
+ # Pass in a block with 1 parameter and modify configuration
54
+ # variables from there.
55
+ #
56
+ # @yield [amazonian] Configuration code block.
57
+ #
58
+ # @example
59
+ # require 'Amazonian'
60
+ # Amazonian.setup do |ama|
61
+ # ama.key = "my awesome key for AWS"
62
+ # ama.secret = "super secret secret key for AWS"
63
+ # ama.debug = true
64
+ # ama.default_search = :Music
65
+ # ama.cache_last = false
66
+ # end
67
+ #
30
68
  def self.setup
31
69
  yield self if block_given?
32
70
  end
33
71
 
34
- # Performs an +ItemLookup+ REST call against the Amazon API.
35
- #
36
- # Expects an ASIN (Amazon Standard Identification Number) and returns an +Item+:
37
72
  #
38
- # item = lookup '1430218150'
39
- # item.title
40
- # => "Learn Objective-C on the Mac (Learn Series)"
73
+ # Perform an ASIN (Amazon Standard Identification Number) lookup.
41
74
  #
42
- # ==== Options:
75
+ # @param [String] The ASIN with which to query the API.
76
+ # @param [Hash] Additional options to be passed to the API.
77
+ # @option params [Symbol] :Operation defaults to :ItemLookup
78
+ # @option params [Symbol] :ItemLookup defaults to the ASIN passed as param 1.
43
79
  #
44
- # Additional parameters for the API call like this:
80
+ # @see For more information on the parameters the API accepts, see http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
45
81
  #
46
- # lookup(asin, :ResponseGroup => :Medium)
82
+ # @return [Amazonian::Item] Representing the response from the API
47
83
  #
48
84
  def self.asin(asin, params={})
49
85
  params = params.merge :Operation => :ItemLookup, :ItemId => asin
@@ -51,61 +87,87 @@ module Amazonian
51
87
  Item.new xml['ItemLookupResponse']['Items']['Item']
52
88
  end
53
89
 
90
+ #
91
+ # Perform a search query to the API. This is basically the same thing as
92
+ # searching with the Amazon website.
93
+ #
94
+ # @param [String] The search query
95
+ # @param [Hash] Additional options to be passed to the API
96
+ # @option params [Symbol] :Operation defaults to :ItemSearch
97
+ # @option params [Symbol] :Keywords defaults to the passed search query
98
+ #
99
+ # @see For more information on the parameters the API accepts, see http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
100
+ #
101
+ # @return [Amazonian::Search] Representing the response from the API. Items returned by the search query are represented as
102
+ # +Amazonian::Item+ inside +Amazonian::Search+
103
+ #
54
104
  def self.search(query, params={})
55
105
  params = params.merge :Operation => :ItemSearch,
56
106
  :Keywords => query
57
107
 
58
- params[:SearchIndex] = :Music if params[:SearchIndex].nil?
108
+ params[:SearchIndex] = @default_search if params[:SearchIndex].nil?
59
109
 
60
110
  xml = self.call params
61
111
  Search.new xml['ItemSearchResponse']
62
112
  end
63
113
 
64
- #private
114
+ private
65
115
 
116
+ #
117
+ # Director function. Builds out the Request, Signs it, Sends it off, and Parses its XML all via small helper functions.
118
+ #
119
+ # @param params [Hash] All of the parameters to be formatted into the API REST call.
120
+ # @return [Crack::XML] The Parsed XML
121
+ #
66
122
  def self.call(params)
67
- raise "Cannot call the Amazon API without key and secret key." if @@key.blank? || @@secret.blank?
123
+ raise "Cannot call the Amazon API without key and secret key." if @key.blank? || @secret.blank?
68
124
 
69
125
  #get the signed query, and assemble the querystring
70
- log :debug, "Started Amazonian request for params: #{params.map {|p| "#{p[0]}=>#{p[1]}" }.join ','}" if @@debug
126
+ log :debug, "Started Amazonian request for params: #{params.map {|p| "#{p[0]}=>#{p[1]}" }.join ','}" if @debug
71
127
 
72
128
  #memoize the last request for faster API querying...
73
129
  query = assemble_querystring params
74
- if query == @@query
130
+ if @cache_last && query == @query && @response.body
75
131
  log :debug, "MEMO'D! Shortcutting API call for dup request."
76
- return Crack::XML.parse @@response.body
132
+ return Crack::XML.parse @response.body
77
133
  end
78
- @@query = query
134
+ @query = query
79
135
 
80
136
  #sign the query
81
137
  signed = sign_query query
82
138
 
83
139
  #assemble the full URL
84
- @@request = "http://#{@@host}#{@@path}?#{signed}"
140
+ @request = "http://#{@host}#{@path}?#{signed}"
85
141
 
86
142
  #make the call
87
- log :info, "performing rest call to '#{@@request}'" if @@debug
88
- @@response = @@patron.get @@request
143
+ log :info, "performing rest call to '#{@request}'" if @debug
144
+ @response = @@patron.get @request
89
145
 
90
- log :debug, "Response Code: #{@@response.status}" if @@debug
146
+ log :debug, "Response Code: #{@response.status}" if @debug
91
147
 
92
148
  #todo, this memo logic is broken....an error code is not always without a body
93
- log :error, "Amazon API Error: #{@@response.status}" if @@response.status >= 400
149
+ log :error, "Amazon API Error: #{@response.status}" if @response.status >= 400
94
150
 
95
151
  #parse the response and return it
96
- Crack::XML.parse @@response.body
152
+ Crack::XML.parse @response.body
97
153
  end
98
154
 
155
+ #
156
+ # Builds out a Query String from a hash of symbols.
157
+ #
99
158
  def self.assemble_querystring(params)
100
159
  # Nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
101
160
  params[:Service] = :AWSECommerceService
102
- params[:AWSAccessKeyId] = @@key
161
+ params[:AWSAccessKeyId] = @key
103
162
 
104
163
  # CGI escape each param
105
164
  # signing needs to order the query alphabetically
106
165
  params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
107
166
  end
108
167
 
168
+ #
169
+ # Signs a query string
170
+ #
109
171
  def self.sign_query(query)
110
172
  #make a copy... fixme fixme I'm awkward
111
173
  q = query.clone
@@ -114,27 +176,30 @@ module Amazonian
114
176
 
115
177
  # Sign the entire get-request (not just the querystring)
116
178
  # possible gotcha if Patron starts using more/different headers.
117
- request_to_sign = "GET\n#{@@host}\n#{@@path}\n#{q}"
179
+ request_to_sign = "GET\n#{@host}\n#{@path}\n#{q}"
118
180
 
119
181
  "#{q}&Signature=#{sign_request request_to_sign}"
120
182
  end
121
183
 
184
+ #
185
+ # Signs an entire request string
186
+ #
122
187
  def self.sign_request(request_to_sign)
123
188
  # Sign it.
124
- hmac = OpenSSL::HMAC.digest(@@digest, @@secret, request_to_sign)
189
+ hmac = OpenSSL::HMAC.digest(@digest, @secret, request_to_sign)
125
190
 
126
191
  # Don't forget to remove the newline from base64
127
192
  CGI.escape(Base64.encode64(hmac).chomp)
128
193
  end
129
194
 
130
195
  def self.log(severity, message)
131
- @@logger.send severity, message if @@logger
196
+ @logger.send severity, message if @logger
132
197
  end
133
198
 
134
199
 
135
200
  # =Item
136
201
  #
137
- # The +Item+ class is a wrapper for the Amazon XML-REST-Response.
202
+ # The +Item+ class is used to neatly box up the Amazon REST API responses into an Object.
138
203
  #
139
204
  # A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
140
205
  #
@@ -145,16 +210,36 @@ module Amazonian
145
210
  end
146
211
 
147
212
  def title
148
- @raw.ItemAttributes.Title
213
+ if @raw.ItemAttributes && @raw.ItemAttributes.Title
214
+ @raw.ItemAttributes.Title
215
+ else
216
+ nil
217
+ end
149
218
  end
150
219
  end
151
220
 
221
+ # =Search
222
+ # The +Search+ class is used to neatly box up the Amazon REST API responses into Objects.
223
+ #
224
+ # When using Amazonian.search you should receive an Search object. Search also attempts
225
+ # to autoboxe all of the Items returned in the search results into an array of +Amazonian::Item+
226
+ # objects.
227
+ #
228
+ # A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
229
+ #
152
230
  class Search
153
231
  attr_reader :items
154
232
  def initialize(hash)
155
233
  @raw = Hashie::Mash.new(hash)
156
234
  @items = []
157
- @raw.Items.Item.each {|i| @items.push Amazonian::Item.new(i) }
235
+
236
+ if @raw.Items && @raw.Items.Item
237
+ if @raw.Items.TotalResults.to_i > 1
238
+ @raw.Items.Item.each {|i| @items.push Amazonian::Item.new(i) }
239
+ else
240
+ @items.push Amazonian::Item.new(@raw.Items.Item)
241
+ end
242
+ end
158
243
  end
159
244
  end
160
245
 
@@ -1,3 +1,3 @@
1
1
  module Amazonian
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amazonian
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Robert L. Carpenter
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-29 00:00:00 -07:00
18
+ date: 2010-12-05 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency