a2z 0.0.1
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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +270 -0
- data/Rakefile +6 -0
- data/a2z.gemspec +23 -0
- data/lib/a2z.rb +6 -0
- data/lib/a2z/blank_slate.rb +110 -0
- data/lib/a2z/client.rb +65 -0
- data/lib/a2z/helpers.rb +18 -0
- data/lib/a2z/requests.rb +3 -0
- data/lib/a2z/requests/item_lookup.rb +42 -0
- data/lib/a2z/requests/item_search.rb +45 -0
- data/lib/a2z/requests/response_group.rb +20 -0
- data/lib/a2z/responses.rb +5 -0
- data/lib/a2z/responses/item.rb +35 -0
- data/lib/a2z/responses/item_link.rb +15 -0
- data/lib/a2z/responses/item_lookup.rb +30 -0
- data/lib/a2z/responses/item_search.rb +44 -0
- data/lib/a2z/responses/operation_request.rb +35 -0
- data/lib/a2z/version.rb +3 -0
- data/spec/a2z/client_spec.rb +49 -0
- data/spec/a2z/requests/item_lookup_spec.rb +13 -0
- data/spec/a2z/requests/item_search_spec.rb +13 -0
- data/spec/a2z/requests/response_group_spec.rb +14 -0
- data/spec/a2z/responses/item_link_spec.rb +13 -0
- data/spec/a2z/responses/item_lookup_spec.rb +13 -0
- data/spec/a2z/responses/item_search_spec.rb +13 -0
- data/spec/a2z/responses/item_spec.rb +25 -0
- data/spec/a2z/version_spec.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- data/tasks/debug.rake +4 -0
- data/tasks/rspec.rake +3 -0
- metadata +163 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Matt Huggins
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
# A2z
|
2
|
+
|
3
|
+
A2z provides a simple Ruby DSL to retrieve product information from the
|
4
|
+
[Amazon Product Advertising API](https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'a2z'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install a2z
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
The basis of the A2z gem is the `A2z::Client`. A client can be used to search
|
23
|
+
for items or lookup a specific item available on [Amazon](http://www.amazon.com).
|
24
|
+
Instantiating a client requires an API `key`, `secret`, and `tag`. To obtain
|
25
|
+
these, you'll need to [create an Amazon Product Advertising API account](https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html).
|
26
|
+
|
27
|
+
Once you have these, create the client as follows:
|
28
|
+
|
29
|
+
client = A2z::Client.new(key: 'YOUR_KEY', secret: 'YOUR_SECRET', tag: 'YOUR_TAG')
|
30
|
+
|
31
|
+
By default, a client will search within Amazon US (i.e.: amazon.com). This can
|
32
|
+
be customized by passing a `country` option when initializing your client.
|
33
|
+
Valid country options are `:ca`, `:cn`, `:de`, `:es`, `:fr`, `:it`, `:jp`,
|
34
|
+
`:uk`, and `:us`. For example:
|
35
|
+
|
36
|
+
client = A2z::Client.new(country: :us, ...)
|
37
|
+
|
38
|
+
Alternative, the country can be changed after the client has been initialized.
|
39
|
+
|
40
|
+
client.country = :us
|
41
|
+
|
42
|
+
Within a Rails application, you'll probably want to keep your key, secret, and
|
43
|
+
tag information out of source control. One way to do this is to store this
|
44
|
+
information in a YAML file such as `config/amazon.yml`. For example:
|
45
|
+
|
46
|
+
key: YOUR_KEY
|
47
|
+
secret: YOUR_SECRET
|
48
|
+
tag: YOUR_TAG
|
49
|
+
|
50
|
+
This file can then be used in the following manner:
|
51
|
+
|
52
|
+
config = YAML.load_file(Rails.root.join('config', 'amazon.yml'))
|
53
|
+
client = A2z::Client.new(config)
|
54
|
+
|
55
|
+
### Item Search
|
56
|
+
|
57
|
+
Once you have instantiated an `A2z::Client` instance, searching for products
|
58
|
+
can be done through the `item_search` method.
|
59
|
+
|
60
|
+
response = client.item_search do
|
61
|
+
category 'Music'
|
62
|
+
keywords 'Dave Matthews Band'
|
63
|
+
end
|
64
|
+
|
65
|
+
The return value is an `A2z::Responses::ItemSearch` object. The example above
|
66
|
+
would make the following calls possible.
|
67
|
+
|
68
|
+
response.valid?
|
69
|
+
=> true
|
70
|
+
|
71
|
+
response.total_results
|
72
|
+
=> 435
|
73
|
+
|
74
|
+
response.total_pages
|
75
|
+
=> 44
|
76
|
+
|
77
|
+
response.items.size
|
78
|
+
=> 10
|
79
|
+
|
80
|
+
response.items.first
|
81
|
+
# => #<A2z::Responses::Item ...>
|
82
|
+
|
83
|
+
response.more_search_results_url
|
84
|
+
=> "http://www.amazon.com/gp/redirect.html?camp=2025&creative=386001&location=..."
|
85
|
+
|
86
|
+
response.operation_request
|
87
|
+
=> #<A2z::Responses::OperationRequest ...>
|
88
|
+
|
89
|
+
For more information on interacting with `A2z::Responses::Item` and
|
90
|
+
`A2z::Responses::OperationRequest` objects, refer to their respective sections
|
91
|
+
below.
|
92
|
+
|
93
|
+
The following code is likely not a real-world example of how you would use
|
94
|
+
the client to perform a search; it is only intended to demonstrate the full
|
95
|
+
collection of methods provided when performing an item search.
|
96
|
+
|
97
|
+
client.item_search do
|
98
|
+
category 'Books'
|
99
|
+
keywords 'Harry Potter'
|
100
|
+
actor 'Adam Sandler'
|
101
|
+
artist 'Muse'
|
102
|
+
audience_rating 'PG-13'
|
103
|
+
author 'J.K. Rowling'
|
104
|
+
brand 'Timex'
|
105
|
+
browse_node 17
|
106
|
+
composer 'Wolfgang Amadeus Mozart'
|
107
|
+
condition 'Refurbished'
|
108
|
+
conductor 'Leopold Stokowski'
|
109
|
+
director 'Judd Apatow'
|
110
|
+
include_reviews_summary true
|
111
|
+
item_page 1
|
112
|
+
manufacturer 'Sony'
|
113
|
+
merchant_id 'SOME_MERCHANT_ID'
|
114
|
+
maximum_price 49.99
|
115
|
+
minimum_price 10.50
|
116
|
+
min_percentage_off 20
|
117
|
+
music_label 'Universal'
|
118
|
+
orchestra 'Antonio Vivaldi'
|
119
|
+
power 'subject:history and (spain or mexico) and not military and language:spanish'
|
120
|
+
publisher 'EMI'
|
121
|
+
sort 'inverse-pricerank'
|
122
|
+
title 'Harry Potter and the Chamber of Secrets'
|
123
|
+
truncate_reviews_at 0
|
124
|
+
variation_page 2
|
125
|
+
response_group('RelatedItems') do
|
126
|
+
related_item_page 2
|
127
|
+
relationship_type 'Episode'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Required arguments and argument values vary depending upon how you use the API.
|
132
|
+
For a full list of arguments and when they are required vs. optional, refer to
|
133
|
+
Amazon's [ItemSearch documentation](http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/ItemSearch.html).
|
134
|
+
|
135
|
+
### Item Lookup
|
136
|
+
|
137
|
+
Once you have instantiated an `A2z::Client` instance, looking up a product can
|
138
|
+
be done through the `item_lookup` method.
|
139
|
+
|
140
|
+
response = client.item_lookup do
|
141
|
+
id '059035342X'
|
142
|
+
end
|
143
|
+
|
144
|
+
The return value is an `A2z::Responses::ItemLookup` object. The example above
|
145
|
+
would make the following calls possible.
|
146
|
+
|
147
|
+
response.valid?
|
148
|
+
=> true
|
149
|
+
|
150
|
+
response.item
|
151
|
+
# => #<A2z::Responses::Item ...>
|
152
|
+
|
153
|
+
response.operation_request
|
154
|
+
=> #<A2z::Responses::OperationRequest ...>
|
155
|
+
|
156
|
+
For more information on interacting with `A2z::Responses::Item` and
|
157
|
+
`A2z::Responses::OperationRequest` objects, refer to their respective sections
|
158
|
+
below.
|
159
|
+
|
160
|
+
The following code is likely not a real-world example of how you would use
|
161
|
+
the client to perform a lookup; it is only intended to demonstrate the full
|
162
|
+
collection of methods provided when performing an item lookup.
|
163
|
+
|
164
|
+
client.item_lookup do
|
165
|
+
category 'Books'
|
166
|
+
id '059035342X'
|
167
|
+
id_type 'ASIN'
|
168
|
+
merchant_id 'SOME_MERCHANT_ID'
|
169
|
+
truncate_reviews_at 0
|
170
|
+
variation_page 2
|
171
|
+
response_group('RelatedItems') do
|
172
|
+
related_item_page 2
|
173
|
+
relationship_type 'Episode'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
Required arguments and argument values vary depending upon how you use the API.
|
178
|
+
For a full list of arguments and when they are required vs. optional, refer to
|
179
|
+
Amazon's [ItemLookup documentation](http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/ItemLookup.html).
|
180
|
+
|
181
|
+
### Items
|
182
|
+
|
183
|
+
Item lookup and item search response objects generally include one or more
|
184
|
+
`A2z::Responses::Item` objects accessible via the `#item` and `#items`
|
185
|
+
instance methods, respectively.
|
186
|
+
|
187
|
+
For example:
|
188
|
+
|
189
|
+
response = client.item_search { ... }
|
190
|
+
=> #<A2z::Responses::ItemSearch ...>
|
191
|
+
|
192
|
+
item = response.items.first
|
193
|
+
=> #<A2z::Responses::Item ...>
|
194
|
+
|
195
|
+
item.asin
|
196
|
+
=> "B008FERRFO"
|
197
|
+
|
198
|
+
item.title
|
199
|
+
=> "Away From The World (Deluxe Version)"
|
200
|
+
|
201
|
+
item.artist
|
202
|
+
=> "Dave Matthews Band"
|
203
|
+
|
204
|
+
item.detail_page_url
|
205
|
+
=> ""http://www.amazon.com/Away-From-World-Deluxe-Version/dp/B008FERRFO..."
|
206
|
+
|
207
|
+
item.manufacturer
|
208
|
+
=> "RCA"
|
209
|
+
|
210
|
+
item.product_group
|
211
|
+
=> "Music"
|
212
|
+
|
213
|
+
Note that some attributes like `artist` and `manufacturer` are only set
|
214
|
+
depending upon the type of item. There is currently no way to determine which
|
215
|
+
attributes have been set on an item object instance. This issue will be
|
216
|
+
addressed in the next gem release.
|
217
|
+
|
218
|
+
### Operation Requests
|
219
|
+
|
220
|
+
Item lookup and item search response objects include an
|
221
|
+
`A2z::Responses::OperationRequest` accessible via the `#operation_request`
|
222
|
+
instance method.
|
223
|
+
|
224
|
+
For example:
|
225
|
+
|
226
|
+
response = client.item_lookup { ... }
|
227
|
+
=> #<A2z::Responses::ItemLookup ...>
|
228
|
+
|
229
|
+
response.operation_request.request_id
|
230
|
+
=> "fc5d5321-a347-4e65-9483-9655e8d9cf16"
|
231
|
+
|
232
|
+
response.operation_request.request_processing_time
|
233
|
+
=> 0.087729
|
234
|
+
|
235
|
+
response.operation_request.headers.class
|
236
|
+
=> Hash
|
237
|
+
|
238
|
+
response.operation_request.headers['UserAgent']
|
239
|
+
=> "Jeff/0.4.3 (Language=Ruby; localhost)"
|
240
|
+
|
241
|
+
response.operation_request.arguments.class
|
242
|
+
=> Hash
|
243
|
+
|
244
|
+
response.operation_request.arguments['Operation']
|
245
|
+
=> "ItemSearch"
|
246
|
+
|
247
|
+
## Contributing
|
248
|
+
|
249
|
+
This gem is new, and as such is lacking a full feature-set for interacting with
|
250
|
+
the Product Advertising API. For example, many of the available
|
251
|
+
[operations](http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/CHAP_OperationListAlphabetical.html)
|
252
|
+
have not yet been implemented.
|
253
|
+
|
254
|
+
Additionally, not much testing has been done yet, so there are many
|
255
|
+
combinations of arguments that may result in errors or exceptions being
|
256
|
+
thrown by the gem.
|
257
|
+
|
258
|
+
As such, any and all external help is encouraged and much appreciated!
|
259
|
+
|
260
|
+
To contribute:
|
261
|
+
|
262
|
+
1. Fork it
|
263
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
264
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
265
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
266
|
+
5. Create new Pull Request
|
267
|
+
|
268
|
+
## License
|
269
|
+
|
270
|
+
A2z is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/a2z.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/a2z/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.author = 'Matt Huggins'
|
6
|
+
gem.email = 'matt.huggins@gmail.com'
|
7
|
+
gem.description = 'Ruby DSL for Amazon Product Advertising API'
|
8
|
+
gem.summary = 'Ruby DSL for Amazon Product Advertising API'
|
9
|
+
gem.homepage = 'https://github.com/mhuggins/a2z'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'a2z'
|
15
|
+
gem.require_path = 'lib'
|
16
|
+
gem.version = A2z::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'crack'
|
19
|
+
gem.add_dependency 'jeff'
|
20
|
+
|
21
|
+
gem.add_development_dependency 'rake'
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
end
|
data/lib/a2z.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
4
|
+
# All rights reserved.
|
5
|
+
|
6
|
+
# Permission is granted for use, copying, modification, distribution,
|
7
|
+
# and distribution of modified versions of this work as long as the
|
8
|
+
# above copyright notice is included.
|
9
|
+
#++
|
10
|
+
|
11
|
+
######################################################################
|
12
|
+
# BlankSlate provides an abstract base class with no predefined
|
13
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
14
|
+
# BlankSlate is useful as a base class when writing classes that
|
15
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
16
|
+
#
|
17
|
+
class BlankSlate
|
18
|
+
class << self
|
19
|
+
|
20
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
21
|
+
# hide +instance_eval+ or any method beginning with "__".
|
22
|
+
def hide(name)
|
23
|
+
methods = instance_methods.map(&:to_sym)
|
24
|
+
if methods.include?(name.to_sym) and
|
25
|
+
name !~ /^(__|instance_eval|object_id)/
|
26
|
+
@hidden_methods ||= {}
|
27
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
28
|
+
undef_method name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_hidden_method(name)
|
33
|
+
@hidden_methods ||= {}
|
34
|
+
@hidden_methods[name] || superclass.find_hidden_method(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
38
|
+
# slate object.
|
39
|
+
def reveal(name)
|
40
|
+
hidden_method = find_hidden_method(name)
|
41
|
+
fail "Don't know how to reveal method '#{name}'" unless hidden_method
|
42
|
+
define_method(name, hidden_method)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
instance_methods.each { |m| hide(m) }
|
47
|
+
end
|
48
|
+
|
49
|
+
######################################################################
|
50
|
+
# Since Ruby is very dynamic, methods added to the ancestors of
|
51
|
+
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
|
52
|
+
# list of available BlankSlate methods. We handle this by defining a
|
53
|
+
# hook in the Object and Kernel classes that will hide any method
|
54
|
+
# defined after BlankSlate has been loaded.
|
55
|
+
#
|
56
|
+
module Kernel
|
57
|
+
class << self
|
58
|
+
alias_method :blank_slate_method_added, :method_added
|
59
|
+
|
60
|
+
# Detect method additions to Kernel and remove them in the
|
61
|
+
# BlankSlate class.
|
62
|
+
def method_added(name)
|
63
|
+
result = blank_slate_method_added(name)
|
64
|
+
return result if self != Kernel
|
65
|
+
BlankSlate.hide(name)
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
######################################################################
|
72
|
+
# Same as above, except in Object.
|
73
|
+
#
|
74
|
+
class Object
|
75
|
+
class << self
|
76
|
+
alias_method :blank_slate_method_added, :method_added
|
77
|
+
|
78
|
+
# Detect method additions to Object and remove them in the
|
79
|
+
# BlankSlate class.
|
80
|
+
def method_added(name)
|
81
|
+
result = blank_slate_method_added(name)
|
82
|
+
return result if self != Object
|
83
|
+
BlankSlate.hide(name)
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_hidden_method(name)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
######################################################################
|
94
|
+
# Also, modules included into Object need to be scanned and have their
|
95
|
+
# instance methods removed from blank slate. In theory, modules
|
96
|
+
# included into Kernel would have to be removed as well, but a
|
97
|
+
# "feature" of Ruby prevents late includes into modules from being
|
98
|
+
# exposed in the first place.
|
99
|
+
#
|
100
|
+
class Module
|
101
|
+
alias blankslate_original_append_features append_features
|
102
|
+
def append_features(mod)
|
103
|
+
result = blankslate_original_append_features(mod)
|
104
|
+
return result if mod != Object
|
105
|
+
instance_methods.each do |name|
|
106
|
+
BlankSlate.hide(name)
|
107
|
+
end
|
108
|
+
result
|
109
|
+
end
|
110
|
+
end
|
data/lib/a2z/client.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'jeff'
|
2
|
+
require 'crack/xml'
|
3
|
+
|
4
|
+
module A2z
|
5
|
+
class Client
|
6
|
+
ErrorResponse = Class.new(ArgumentError)
|
7
|
+
|
8
|
+
include Jeff
|
9
|
+
|
10
|
+
attr_reader :country, :tag
|
11
|
+
|
12
|
+
HOSTS = {
|
13
|
+
ca: 'ecs.amazonaws.ca',
|
14
|
+
cn: 'webservices.amazon.cn',
|
15
|
+
de: 'ecs.amazonaws.de',
|
16
|
+
es: 'webservices.amazon.es',
|
17
|
+
fr: 'ecs.amazonaws.fr',
|
18
|
+
it: 'webservices.amazon.it',
|
19
|
+
jp: 'ecs.amazonaws.jp',
|
20
|
+
uk: 'ecs.amazonaws.co.uk',
|
21
|
+
us: 'ecs.amazonaws.com',
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
params 'AssociateTag' => -> { tag },
|
25
|
+
'Service' => 'AWSECommerceService',
|
26
|
+
'Version' => '2011-08-01'
|
27
|
+
|
28
|
+
def initialize(opts = {})
|
29
|
+
self.country = opts.fetch(:country, :us)
|
30
|
+
self.key = opts[:key]
|
31
|
+
self.secret = opts[:secret]
|
32
|
+
self.tag = opts[:tag]
|
33
|
+
end
|
34
|
+
|
35
|
+
def country=(code)
|
36
|
+
raise ArgumentError.new("Country code must be one of #{HOSTS.keys.join(', ')}.") if code.nil? || HOSTS[code.to_sym].nil?
|
37
|
+
@country = code.to_sym
|
38
|
+
@endpoint = "http://#{HOSTS[@country]}/onca/xml"
|
39
|
+
end
|
40
|
+
|
41
|
+
def item_search(&block)
|
42
|
+
response = request(Requests::ItemSearch.new(&block))
|
43
|
+
Responses::ItemSearch.from_response(response)
|
44
|
+
end
|
45
|
+
|
46
|
+
def item_lookup(&block)
|
47
|
+
response = request(Requests::ItemLookup.new(&block))
|
48
|
+
Responses::ItemLookup.from_response(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def tag=(tag)
|
54
|
+
@tag = tag
|
55
|
+
end
|
56
|
+
|
57
|
+
def request(req)
|
58
|
+
response = get(query: req.params)
|
59
|
+
response = Crack::XML.parse(response.body)
|
60
|
+
|
61
|
+
# strip the root xml node
|
62
|
+
response.values.first
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/a2z/helpers.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module A2z
|
2
|
+
module Helpers
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def underscore(camel_cased_word)
|
10
|
+
camel_cased_word.dup.tap do |word|
|
11
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
12
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
13
|
+
word.tr!('-', '_')
|
14
|
+
word.downcase!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/a2z/requests.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module A2z
|
2
|
+
module Requests
|
3
|
+
class ItemLookup < BlankSlate
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
@params = { 'Operation' => 'ItemLookup' }
|
10
|
+
instance_eval(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
%w(Condition IdType MerchantId TruncateReviewsAt VariationPage).each do |param|
|
14
|
+
method = underscore(param)
|
15
|
+
|
16
|
+
class_eval <<-END, __FILE__, __LINE__
|
17
|
+
def #{method}(value)
|
18
|
+
@params['#{param}'] = value
|
19
|
+
end
|
20
|
+
END
|
21
|
+
end
|
22
|
+
|
23
|
+
def id(value)
|
24
|
+
value = value.join(',') if value.kind_of?(Array)
|
25
|
+
@params['ItemId'] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def category(value)
|
29
|
+
@params['SearchIndex'] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def response_group(value, &block)
|
33
|
+
response_group = ResponseGroup.new(value, &block)
|
34
|
+
@params.merge!(response_group.params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def include_reviews_summary(value)
|
38
|
+
@params['IncludeReviewsSummary'] = value ? 'True' : 'False'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module A2z
|
2
|
+
module Requests
|
3
|
+
class ItemSearch < BlankSlate
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
@params = { 'Operation' => 'ItemSearch' }
|
10
|
+
instance_eval(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
%w(Actor Artist AudienceRating Author Brand BrowseNode Composer Conductor
|
14
|
+
Condition Director ItemPage Manufacturer MaximumPrice MerchantId
|
15
|
+
MinimumPrice MinPercentageOff MusicLabel Orchestra Power Publisher
|
16
|
+
Sort Title TruncateReviewsAt VariationPage).each do |param|
|
17
|
+
method = underscore(param)
|
18
|
+
|
19
|
+
class_eval <<-END, __FILE__, __LINE__
|
20
|
+
def #{method}(value)
|
21
|
+
@params['#{param}'] = value
|
22
|
+
end
|
23
|
+
END
|
24
|
+
end
|
25
|
+
|
26
|
+
def keywords(value)
|
27
|
+
value = value.join(' ') if value.kind_of?(Array)
|
28
|
+
@params['Keywords'] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def category(value)
|
32
|
+
@params['SearchIndex'] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def response_group(value, &block)
|
36
|
+
response_group = ResponseGroup.new(value, &block)
|
37
|
+
@params.merge!(response_group.params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def include_reviews_summary(value)
|
41
|
+
@params['IncludeReviewsSummary'] = value ? 'True' : 'False'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module A2z
|
2
|
+
module Requests
|
3
|
+
class ResponseGroup < BlankSlate
|
4
|
+
attr_reader :params
|
5
|
+
|
6
|
+
def initialize(value, &block)
|
7
|
+
@params = { 'ResponseGroup' => value }
|
8
|
+
instance_eval(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def related_item_page(value)
|
12
|
+
@params['RelatedItemPage'] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def relationship_type(value)
|
16
|
+
@params['RelationshipType'] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module A2z
|
2
|
+
module Responses
|
3
|
+
class Item
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
attr_accessor :asin, :detail_page_url, :links
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@links = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
instance_variable_set("@#{key}".to_sym, value)
|
14
|
+
self.class.class_eval { attr_reader key.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_response(data)
|
18
|
+
new.tap do |item|
|
19
|
+
item.asin = data['ASIN']
|
20
|
+
item.detail_page_url = data['DetailPageURL']
|
21
|
+
|
22
|
+
if data['ItemLinks']
|
23
|
+
item.links = data['ItemLinks']['ItemLink'].collect { |link| ItemLink.from_response(link) }
|
24
|
+
end
|
25
|
+
|
26
|
+
if data['ItemAttributes']
|
27
|
+
data['ItemAttributes'].each { |key, value| item[underscore(key)] = value }
|
28
|
+
end
|
29
|
+
|
30
|
+
item.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module A2z
|
2
|
+
module Responses
|
3
|
+
class ItemLink
|
4
|
+
attr_accessor :description, :url
|
5
|
+
|
6
|
+
def self.from_response(data)
|
7
|
+
new.tap do |item_link|
|
8
|
+
item_link.description = data['Description']
|
9
|
+
item_link.url = data['URL']
|
10
|
+
item_link.freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module A2z
|
2
|
+
module Responses
|
3
|
+
class ItemLookup
|
4
|
+
attr_accessor :operation_request, :item, :valid
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@valid = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?
|
11
|
+
@valid
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid=(value)
|
15
|
+
@valid = !!value
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO capture item_search_response['Items']['Request']['Errors'] into an attr_accessor value
|
19
|
+
# TODO consider capturing item_search_response['Items']['Request'] into an attr_accessor value
|
20
|
+
def self.from_response(data)
|
21
|
+
new.tap do |item_lookup|
|
22
|
+
item_lookup.operation_request = OperationRequest.from_response(data['OperationRequest']) if data['OperationRequest']
|
23
|
+
item_lookup.item = Item.from_response(data['Items']['Item']) if data['Items'] && data['Items']['Item']
|
24
|
+
item_lookup.valid = data['Items']['Request']['IsValid'] == 'True' rescue false
|
25
|
+
item_lookup.freeze
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module A2z
|
2
|
+
module Responses
|
3
|
+
class ItemSearch
|
4
|
+
attr_accessor :operation_request, :items, :total_results, :total_pages, :more_search_results_url, :valid
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@items = []
|
8
|
+
@total_results = 0
|
9
|
+
@total_pages = 0
|
10
|
+
@valid = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
@valid
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid=(value)
|
18
|
+
@valid = !!value
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO capture item_search_response['Items']['Request']['Errors'] into an attr_accessor value
|
22
|
+
# TODO consider capturing item_search_response['Items']['Request'] into an attr_accessor value
|
23
|
+
def self.from_response(data)
|
24
|
+
new.tap do |item_search|
|
25
|
+
item_search.operation_request = OperationRequest.from_response(data['OperationRequest']) if data['OperationRequest']
|
26
|
+
item_search.items = items_from_response(data)
|
27
|
+
item_search.total_results = data['Items']['TotalResults'].to_i rescue 0
|
28
|
+
item_search.total_pages = data['Items']['TotalPages'].to_i rescue 0
|
29
|
+
item_search.more_search_results_url = data['Items']['MoreSearchResultsUrl'] rescue nil
|
30
|
+
item_search.valid = data['Items']['Request']['IsValid'] == 'True' rescue false
|
31
|
+
item_search.freeze
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.items_from_response(data)
|
38
|
+
items = data['Items']['Item'] rescue []
|
39
|
+
items = [items].compact unless items.kind_of?(Array)
|
40
|
+
items.collect { |item| Item.from_response(item) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module A2z
|
2
|
+
module Responses
|
3
|
+
class OperationRequest
|
4
|
+
attr_accessor :request_id, :request_processing_time, :headers, :arguments
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@headers = []
|
8
|
+
@arguments = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_response(data)
|
12
|
+
new.tap do |operation_request|
|
13
|
+
operation_request.request_id = data['RequestId']
|
14
|
+
operation_request.request_processing_time = data['RequestProcessingTime'].to_f
|
15
|
+
|
16
|
+
if data['HTTPHeaders']
|
17
|
+
headers = data['HTTPHeaders']['Header']
|
18
|
+
headers = [headers] unless headers.kind_of?(Array)
|
19
|
+
headers = headers.collect { |h| [ h['Name'], h['Value'] ] }
|
20
|
+
operation_request.headers = Hash[headers]
|
21
|
+
end
|
22
|
+
|
23
|
+
if data['Arguments']
|
24
|
+
arguments = data['Arguments']['Argument']
|
25
|
+
arguments = [arguments] unless arguments.kind_of?(Array)
|
26
|
+
arguments = arguments.collect { |a| [ a['Name'], a['Value'] ] }
|
27
|
+
operation_request.arguments = Hash[arguments]
|
28
|
+
end
|
29
|
+
|
30
|
+
operation_request.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/a2z/version.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Client do
|
4
|
+
describe '#initialize' do
|
5
|
+
subject do
|
6
|
+
A2z::Client
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when country is not specified' do
|
10
|
+
specify { expect { subject.new }.to_not raise_error }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when country is valid' do
|
14
|
+
specify { expect { subject.new(country: :us) }.to_not raise_error }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when country is invalid' do
|
18
|
+
specify { expect { subject.new(country: :fake) }.to raise_error }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#country=' do
|
23
|
+
it 'should succeed'
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#country' do
|
27
|
+
it 'should succeed'
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#tag' do
|
31
|
+
it 'should succeed'
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#item_search' do
|
35
|
+
subject do
|
36
|
+
A2z::Client.new
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should succeed'
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#item_lookup' do
|
43
|
+
subject do
|
44
|
+
A2z::Client.new
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should succeed'
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Requests::ResponseGroup do
|
4
|
+
subject do
|
5
|
+
A2z::Requests::ResponseGroup.new(value, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:value) { nil }
|
9
|
+
let(:block) { Proc.new { } }
|
10
|
+
|
11
|
+
describe '#params' do
|
12
|
+
it 'should return a hash'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Responses::ItemLink do
|
4
|
+
subject do
|
5
|
+
A2z::Responses::ItemLink.from_response(item_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:item_hash) { Hash.new }
|
9
|
+
|
10
|
+
describe '.from_response' do
|
11
|
+
it 'should return an item link object'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Responses::ItemLookup do
|
4
|
+
subject do
|
5
|
+
A2z::Responses::ItemLookup.from_response(item_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:item_hash) { Hash.new }
|
9
|
+
|
10
|
+
describe '.from_response' do
|
11
|
+
it 'should return an item lookup object'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Responses::ItemSearch do
|
4
|
+
subject do
|
5
|
+
A2z::Responses::ItemSearch.from_response(item_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:item_hash) { Hash.new }
|
9
|
+
|
10
|
+
describe '.from_response' do
|
11
|
+
it 'should return an item search object'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe A2z::Responses::Item do
|
4
|
+
subject do
|
5
|
+
A2z::Responses::Item.from_response(item_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:item_hash) { Hash.new }
|
9
|
+
|
10
|
+
describe '.from_response' do
|
11
|
+
it 'should return an item object'
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#asin' do
|
15
|
+
it 'should return string'
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#detail_page_url' do
|
19
|
+
it 'should return string'
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#links' do
|
23
|
+
it 'should return array of item links'
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'a2z'
|
data/tasks/debug.rake
ADDED
data/tasks/rspec.rake
ADDED
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: a2z
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matt Huggins
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-12-27 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: crack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: jeff
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
description: Ruby DSL for Amazon Product Advertising API
|
78
|
+
email: matt.huggins@gmail.com
|
79
|
+
executables: []
|
80
|
+
|
81
|
+
extensions: []
|
82
|
+
|
83
|
+
extra_rdoc_files: []
|
84
|
+
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- a2z.gemspec
|
92
|
+
- lib/a2z.rb
|
93
|
+
- lib/a2z/blank_slate.rb
|
94
|
+
- lib/a2z/client.rb
|
95
|
+
- lib/a2z/helpers.rb
|
96
|
+
- lib/a2z/requests.rb
|
97
|
+
- lib/a2z/requests/item_lookup.rb
|
98
|
+
- lib/a2z/requests/item_search.rb
|
99
|
+
- lib/a2z/requests/response_group.rb
|
100
|
+
- lib/a2z/responses.rb
|
101
|
+
- lib/a2z/responses/item.rb
|
102
|
+
- lib/a2z/responses/item_link.rb
|
103
|
+
- lib/a2z/responses/item_lookup.rb
|
104
|
+
- lib/a2z/responses/item_search.rb
|
105
|
+
- lib/a2z/responses/operation_request.rb
|
106
|
+
- lib/a2z/version.rb
|
107
|
+
- spec/a2z/client_spec.rb
|
108
|
+
- spec/a2z/requests/item_lookup_spec.rb
|
109
|
+
- spec/a2z/requests/item_search_spec.rb
|
110
|
+
- spec/a2z/requests/response_group_spec.rb
|
111
|
+
- spec/a2z/responses/item_link_spec.rb
|
112
|
+
- spec/a2z/responses/item_lookup_spec.rb
|
113
|
+
- spec/a2z/responses/item_search_spec.rb
|
114
|
+
- spec/a2z/responses/item_spec.rb
|
115
|
+
- spec/a2z/version_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- tasks/debug.rake
|
118
|
+
- tasks/rspec.rake
|
119
|
+
has_rdoc: true
|
120
|
+
homepage: https://github.com/mhuggins/a2z
|
121
|
+
licenses: []
|
122
|
+
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
hash: 3
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
version: "0"
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
hash: 3
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
version: "0"
|
146
|
+
requirements: []
|
147
|
+
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 1.3.7
|
150
|
+
signing_key:
|
151
|
+
specification_version: 3
|
152
|
+
summary: Ruby DSL for Amazon Product Advertising API
|
153
|
+
test_files:
|
154
|
+
- spec/a2z/client_spec.rb
|
155
|
+
- spec/a2z/requests/item_lookup_spec.rb
|
156
|
+
- spec/a2z/requests/item_search_spec.rb
|
157
|
+
- spec/a2z/requests/response_group_spec.rb
|
158
|
+
- spec/a2z/responses/item_link_spec.rb
|
159
|
+
- spec/a2z/responses/item_lookup_spec.rb
|
160
|
+
- spec/a2z/responses/item_search_spec.rb
|
161
|
+
- spec/a2z/responses/item_spec.rb
|
162
|
+
- spec/a2z/version_spec.rb
|
163
|
+
- spec/spec_helper.rb
|