amazonian 0.1.0 → 0.2.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/.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