ramazon_advertising 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +46 -0
  5. data/Rakefile +84 -0
  6. data/VERSION +1 -0
  7. data/features/friendly_errors.feature +15 -0
  8. data/features/generate_root_browse_nodes.feature +13 -0
  9. data/features/getting_offer_details.feature +30 -0
  10. data/features/getting_search_bins.feature +22 -0
  11. data/features/retrieve_browse_node_information.feature +16 -0
  12. data/features/retrieving_a_product.feature +32 -0
  13. data/features/searching_for_products.feature +47 -0
  14. data/features/step_definitions/auth_steps.rb +8 -0
  15. data/features/step_definitions/browse_node_steps.rb +55 -0
  16. data/features/step_definitions/error_steps.rb +14 -0
  17. data/features/step_definitions/product_collection_steps.rb +20 -0
  18. data/features/step_definitions/product_steps.rb +75 -0
  19. data/features/step_definitions/ramazon_advertising_steps.rb +0 -0
  20. data/features/support/env.rb +7 -0
  21. data/features/support/ramazon_advertising.example.yml +2 -0
  22. data/lib/ramazon/abstract_element.rb +18 -0
  23. data/lib/ramazon/browse_node.rb +69 -0
  24. data/lib/ramazon/configuration.rb +66 -0
  25. data/lib/ramazon/error.rb +12 -0
  26. data/lib/ramazon/image.rb +18 -0
  27. data/lib/ramazon/merchant.rb +21 -0
  28. data/lib/ramazon/offer.rb +27 -0
  29. data/lib/ramazon/price.rb +18 -0
  30. data/lib/ramazon/product.rb +303 -0
  31. data/lib/ramazon/product_collection.rb +30 -0
  32. data/lib/ramazon/rails_additions.rb +99 -0
  33. data/lib/ramazon/request.rb +82 -0
  34. data/lib/ramazon/search_bin.rb +11 -0
  35. data/lib/ramazon/search_bin_parameter.rb +9 -0
  36. data/lib/ramazon/search_bin_set.rb +11 -0
  37. data/lib/ramazon/signatory.rb +10 -0
  38. data/lib/ramazon_advertising.rb +41 -0
  39. data/lib/root_nodes.yml +64 -0
  40. data/lib/tasks/ramazon.rake +9 -0
  41. data/spec/ramazon/configuration_spec.rb +21 -0
  42. data/spec/spec_helper.rb +11 -0
  43. metadata +157 -0
@@ -0,0 +1,75 @@
1
+ When /^I try to find the asin "([^\"]*)"$/ do |asin|
2
+ @products = Ramazon::Product.find(:item_id => asin, :response_group => ["Medium", "BrowseNodes", "Offers"])
3
+ @product = @products[0]
4
+ end
5
+
6
+ Given /^I am searching with the "([^\"]*)" of "([^\"]*)"$/ do |attr, value|
7
+ @search_options ||= {}
8
+ @search_options[attr.to_sym] = value
9
+ end
10
+
11
+ When /^I perform the product search$/ do
12
+ begin
13
+ @search_options ||= {}
14
+ @products = Ramazon::Product.find(@search_options)
15
+ rescue Ramazon::Error => e
16
+ @error = e
17
+ end
18
+ end
19
+
20
+ Then /^I should get a list of products$/ do
21
+ raise @error if @error
22
+ @products.should_not be_empty
23
+ end
24
+
25
+ Then /^the list of products should have more than (\d+) product$/ do |count|
26
+ @products.should have_at_least(count.to_i + 1).items
27
+ end
28
+
29
+
30
+ Then /^each product should have the "([^\"]*)" "([^\"]*)"$/ do |attr, value|
31
+ @products.each do |p|
32
+ p.send(attr).should eql(value)
33
+ end
34
+ end
35
+
36
+ Then /^I should get a product$/ do
37
+ raise @error if @error
38
+ @product = @products[0] unless @product
39
+ @product.should_not be_nil
40
+ end
41
+
42
+ Then /^the product should have (the\s)?"([^\"]*)" "([^\"]*)"$/ do |the, attr, value|
43
+ @product.send(attr).should eql(value)
44
+ end
45
+
46
+ Then /^the product should have (a\s)?"([^\"]*)"$/ do |a, attr|
47
+ @product.send(attr).should_not be_nil
48
+ end
49
+
50
+ Then /^each of the product's "([^\"]*)" should have a "([^\"]*)"$/ do |collection_name, attr|
51
+ @product.send(collection_name).should_not be_empty
52
+ @product.send(collection_name).each do |i|
53
+ i.send(attr).should_not be_nil
54
+ end
55
+ end
56
+
57
+ Then /^each of the product's "([^\"]*)" should have a "([^\"]*)" of "([^\"]*)"$/ do |collection_name, attr, value|
58
+ @product.send(collection_name).should_not be_empty
59
+ @product.send(collection_name).each do |i|
60
+ i.send(attr).should_not be_nil
61
+ end
62
+ end
63
+
64
+
65
+ Then /^the product should have a category tree for "([^\"]*)"$/ do |category_name|
66
+ @product.category_tree[category_name].should_not be_nil
67
+ end
68
+
69
+
70
+ Then /^each product should have (a\s)?"([^\"]*)"$/ do |a, attr|
71
+ @products.each do |p|
72
+ p.send(attr).should_not be_nil
73
+ end
74
+ end
75
+
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__) + '..', '..', 'lib'))
2
+ require 'lib/ramazon_advertising'
3
+
4
+ require 'spec/expectations'
5
+
6
+ configatron.ramazon.configure_from_yaml(File.join(File.dirname(__FILE__), "ramazon_advertising.yml"))
7
+
@@ -0,0 +1,2 @@
1
+ access_key: SOMETHING
2
+ secret_key: SOMETHING SECRET
@@ -0,0 +1,18 @@
1
+ module Ramazon
2
+ module AbstractElement
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def abstract_element(name, type, options = {})
9
+ element(name, type, options.merge(:parser => :abstract_parse, :raw => true))
10
+ end
11
+
12
+ def abstract_parse(xml, options = {})
13
+ tag XML::Parser.string(xml).parse.root.name
14
+ parse(xml)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ module Ramazon
2
+ class BrowseNode
3
+ include HTTParty
4
+ include HappyMapper
5
+
6
+ tag "BrowseNode"
7
+
8
+ element :name, String, :tag => "Name"
9
+ element :node_id, String, :tag => "BrowseNodeId"
10
+ element :children, Ramazon::BrowseNode, :tag => "Children/BrowseNode"
11
+ element :is_category_root, Boolean, :tag => "IsCategoryRoot"
12
+
13
+ attr_accessor :child
14
+
15
+ DEFAULT_ROOT_FILE = File.join(File.dirname(__FILE__), '..', 'root_nodes.yml')
16
+
17
+ def self.generate_root_nodes(file_name = DEFAULT_ROOT_FILE)
18
+ if Ramazon::Configuration.locale == :us
19
+ doc = Nokogiri::HTML(get('http://www.amazon.com').body)
20
+ root_nodes = {}
21
+ doc.search(".navSaMenu .navSaChildItem a").each do |element|
22
+ if element["href"] =~ /node=(\d+)\&/
23
+ root_nodes[element.content] = $1
24
+ end
25
+ end
26
+
27
+ unless root_nodes.empty?
28
+ FileUtils.rm_f(file_name)
29
+ File.open(file_name, 'w') do |f|
30
+ f.write(root_nodes.to_yaml)
31
+ end
32
+ end
33
+ else
34
+ #todo correlate ECS locales to actual amazon.* urls
35
+ raise "generating root nodes for locale's other than the US is not supported"
36
+ end
37
+ end
38
+
39
+ def self.root_nodes(file_name = DEFAULT_ROOT_FILE)
40
+ @root_nodes ||= File.open(file_name) { |yf| YAML::load(yf) }
41
+ end
42
+
43
+ # find a browse node based on its id
44
+ # @param node_id [String] the browse node you're looking for
45
+ # @returns [Ramazon::BrowseNode] the node you're looking for
46
+ def self.find(node_id)
47
+ req = Ramazon::Request.new(:operation => "BrowseNodeLookup", :browse_node_id => node_id)
48
+ res = req.submit
49
+ parse(res.to_s)[0]
50
+ end
51
+
52
+ # get a hash of name -> child browse_nodes
53
+ # @returns [Hash] stringified hash of names => Ramazon::BrowseNode objects
54
+ def child_hash
55
+ if !@child_hash
56
+ @child_hash = {}
57
+ self.children.each do |i|
58
+ @child_hash[i.name] = i
59
+ end
60
+ end
61
+
62
+ @child_hash
63
+ end
64
+
65
+ def self.parse(xml, options = {})
66
+ super
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,66 @@
1
+ module Ramazon
2
+ # the class that persists configuration information for Amazon requests
3
+ class Configuration
4
+ class << self
5
+ LOCALES = [:ca, :de, :fr, :jp, :uk, :us]
6
+
7
+ # set the locale for future requests
8
+ # will raise an exception if the locale isn't in the Configuration::LOCALES collection
9
+ def locale=(locale)
10
+ if LOCALES.include?(locale)
11
+ configatron.ramazon.locale = locale
12
+ else
13
+ raise "unknown locale"
14
+ end
15
+ end
16
+
17
+ # get the current locale (defaults to the us)
18
+ def locale
19
+ configatron.ramazon.locale || :us
20
+ end
21
+
22
+ # get the current access key
23
+ def access_key
24
+ configatron.ramazon.access_key
25
+ end
26
+
27
+ # set the current access key
28
+ # @param key [String] access key you're using to access the advertising api
29
+ def access_key=(key)
30
+ configatron.ramazon.access_key = key
31
+ end
32
+
33
+ # get the current secret key that is used for request signing
34
+ def secret_key
35
+ configatron.ramazon.secret_key
36
+ end
37
+
38
+ # set the secret key so that requests can be appropriately signed
39
+ # @param key [String] secret key you're using to sign advertising requests
40
+ def secret_key=(key)
41
+ configatron.ramazon.secret_key = key
42
+ end
43
+
44
+ # get the correct host based on your locale
45
+ def base_uri
46
+ if locale == :us
47
+ "http://ecs.amazonaws.com"
48
+ else
49
+ "http://ecs.amazonaws.#{locale}"
50
+ end
51
+ end
52
+
53
+ # get the full path including locale specific host and /onca/xml path
54
+ def uri
55
+ "#{base_uri}#{path}"
56
+ end
57
+
58
+ # get the path where requests should be dispatched to
59
+ def path
60
+ "/onca/xml"
61
+ end
62
+
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,12 @@
1
+ module Ramazon
2
+ class Error < StandardError
3
+ include HappyMapper
4
+ tag "Error"
5
+ element :code, String, :tag => "Code"
6
+ element :message, String, :tag => "Message"
7
+
8
+ def to_s
9
+ "#{self.code}: #{self.message}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Ramazon
2
+ # An image object returned by Amazon's product data
3
+ # Available accessors include
4
+ # +url+::
5
+ # The url of the image
6
+ # +height+::
7
+ # The height of the image
8
+ # +width+::
9
+ # The width of the image
10
+ class Image
11
+ include HappyMapper
12
+ include Ramazon::AbstractElement
13
+ tag "Image"
14
+ element :url, String, :tag => "URL"
15
+ element :height, Integer, :tag => "Height"
16
+ element :width, Integer, :tag => "Width"
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Ramazon
2
+ # Merchant Details
3
+ # Current supports the following accessors
4
+ # +merchant_id+::
5
+ # The id of the merchant
6
+ # +glance_page_url+::
7
+ # The url of the merchant
8
+ # +average_feedback_rating+::
9
+ # Average Feedback Rating
10
+ # +total_feedback+::
11
+ # Total Feedback
12
+ class Merchant
13
+ include HappyMapper
14
+ tag "Merchant"
15
+
16
+ element :merchant_id, String, :tag => "MerchantId"
17
+ element :glance_page_url, String, :tag => "GlancePage"
18
+ element :average_feedback_rating, Float, :tag => "AverageFeedbackRating"
19
+ element :total_feedback, Integer, :tag => "TotalFeedback"
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Ramazon
2
+ # Offer Details
3
+ # Currently supports the following accessors
4
+ # +merchant+::
5
+ # The merchant that is listing this product (NOTE: Returns Ramazon::Merchant object)
6
+ # +condition+::
7
+ # The condition of the product
8
+ # +sub_condition+::
9
+ # The subcondition of the product
10
+ # +listing_id+::
11
+ # The id of the listing
12
+ # +price+::
13
+ # The asking price of the listing (NOTE: Returns Ramazon::Price object)
14
+ class Offer
15
+ include HappyMapper
16
+ include Ramazon::AbstractElement
17
+
18
+ tag "Offer"
19
+
20
+ has_one :merchant, Ramazon::Merchant, :tag => "Merchant"
21
+ element :condition, String, :tag => "OfferAttributes/Condition"
22
+ element :sub_condition, String, :tag => "OfferAttributes/SubCondition"
23
+ element :listing_id, String, :tag => "OfferListing/OfferListingId"
24
+ abstract_element :price, Ramazon::Price, :tag => "OfferListing/Price"
25
+
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module Ramazon
2
+ # A price object returned by Amazon's product data
3
+ # Available accessors include
4
+ # +amount+::
5
+ # The unformatted ammount of the price
6
+ # +currency_code+::
7
+ # The currency of the offer
8
+ # +formatted_price+::
9
+ # The formatted price of the offer
10
+ class Price
11
+ include HappyMapper
12
+ include Ramazon::AbstractElement
13
+
14
+ element :amount, String, :tag => "Amount"
15
+ element :currency_code, String, :tag => "CurrencyCode"
16
+ element :formatted_price, String, :tag => "FormattedPrice"
17
+ end
18
+ end
@@ -0,0 +1,303 @@
1
+ module Ramazon
2
+ # Find and get product details with this class
3
+ # Currently supports the following accessors
4
+ # (all other elements can be accessed via nokogiri selectors and the get method)
5
+ # +asin+::
6
+ # Amazon Identifier
7
+ # +upc+::
8
+ # UPC ID
9
+ # +title+::
10
+ # Title of the product
11
+ # +product_group+::
12
+ # The category/product_group of the product
13
+ # +manufacturer+::
14
+ # The manufacturer of the product
15
+ # +brand+::
16
+ # The brand of the product
17
+ # +url+::
18
+ # The Amazon URL of the product
19
+ # +small_image+::
20
+ # The small image that Amazon provides (NOTE: Returns Ramazon::Image object)
21
+ # +medium_image+::
22
+ # The medium image that Amazon provides (NOTE: Returns Ramazon::Image object)
23
+ # +large_image+::
24
+ # The large image that Amazon provides (NOTE: Returns Ramazon::Image object)
25
+ # +list_price+::
26
+ # The list price of the item (NOTE: Returns Ramazon::Price object)
27
+ # +lowest_new_price+::
28
+ # The lowest new price from the offer summary (NOTE: Returns Ramazon::Price object)
29
+ # +sales_rank+::
30
+ # The sales rank of the product
31
+ # +new_count+::
32
+ # The quantity of new item offers
33
+ # +used_count+::
34
+ # The quantity of used item offers
35
+ # +collectible_count+::
36
+ # The quantity of collectible item offers
37
+ # +refurbished_count+::
38
+ # The quantity of refurbished item offers
39
+ # +release_date+::
40
+ # The release date of the product
41
+ # +original_release_date+::
42
+ # The original release date of the product
43
+ # +offers+::
44
+ # The collection of offers available for the given product
45
+ # @example find an individual item
46
+ # @products = Ramazon::Product.find(:item_id => "B000NU2CY4", :response_group => "Medium")
47
+ # @products[0].title
48
+ # @products[0].asin
49
+ # @products[0].upc
50
+ # @products[0].large_image.url
51
+ # @products[0].url
52
+ #
53
+
54
+ class Product
55
+ include HappyMapper
56
+ include Ramazon::AbstractElement
57
+ tag 'Item'
58
+
59
+ element :asin, String, :tag => 'ASIN'
60
+ element :upc, String, :tag => 'ItemAttributes/UPC'
61
+ element :title, String, :tag => 'Title', :deep => true
62
+ element :product_group, String, :tag => 'ItemAttributes/ProductGroup'
63
+ element :manufacturer, String, :tag => 'ItemAttributes/Manufacturer'
64
+ element :brand, String, :tag => 'ItemAttributes/Brand'
65
+ element :url, String, :tag => 'DetailPageURL'
66
+ abstract_element :small_image, Ramazon::Image, :tag => "SmallImage"
67
+ abstract_element :large_image, Ramazon::Image, :tag => "LargeImage"
68
+ abstract_element :medium_image, Ramazon::Image, :tag => "MediumImage"
69
+ abstract_element :tiny_image, Ramazon::Image, :tag => "TinyImage"
70
+ abstract_element :thumb_image, Ramazon::Image, :tag => "ThumbImage"
71
+ abstract_element :list_price, Ramazon::Price, :tag => "ItemAttributes/ListPrice"
72
+ abstract_element :lowest_new_price, Ramazon::Price, :tag => "OfferSummary/LowestNewPrice"
73
+ has_many :offers, Ramazon::Offer, :tag => "Offers/Offer"
74
+
75
+ element :sales_rank, Integer, :tag => "SalesRank"
76
+
77
+ element :new_count, Integer, :tag => "OfferSummary/TotalNew"
78
+ element :used_count, Integer, :tag => "OfferSummary/TotalUsed"
79
+ element :collectible_count, Integer, :tag => "OfferSummary/TotalCollectible"
80
+ element :refurbished_count, Integer, :tag => "OfferSummary/TotalRefurbished"
81
+
82
+ element :release_date, Date, :tag => 'ItemAttributes/ReleaseDate'
83
+ element :original_release_date, Date, :tag => 'ItemAttributes/OriginalReleaseDate'
84
+
85
+ attr_accessor :has_first_page_of_full_offers
86
+
87
+ # Creates the worker that performs the delta indexing
88
+ # @param options Amazon request options (you can use an underscore convention)
89
+ # (ie. passing the :response_group option will be converted to "ResponseGroup")
90
+ # <tt>:item_id</tt> - the ASIN or UPC you're looking for
91
+ # @return [Array] array of Ramazon::Product objects
92
+ def self.find(*args)
93
+ options = args.extract_options!
94
+ if options[:item_id]
95
+ item_lookup(options[:item_id], options)
96
+ else
97
+ options[:operation] ||= "ItemSearch"
98
+ options[:search_index] ||= "Blended"
99
+ options[:item_page] ||= 1
100
+ res = Ramazon::Request.new(options).submit
101
+ products = Ramazon::ProductCollection.create_from_results(options[:item_page] || 1, 10, res)
102
+ if find_options_retrieve_all_offers?(options)
103
+ products.each do |p|
104
+ p.has_first_page_of_full_offers = true
105
+ p.offer_pages = offer_pages_for(p)
106
+ end
107
+ end
108
+ products
109
+ end
110
+ end
111
+
112
+ # Performs an item lookup
113
+ # @param item_id the ASIN or UPC you're looking for
114
+ # @options additional Amazon request options (i.e. :response_group)
115
+ def self.item_lookup(item_id, options = {})
116
+ req = Ramazon::Request.new({:item_id => item_id,
117
+ :operation => "ItemLookup"}.merge(options))
118
+ res = req.submit
119
+
120
+ Ramazon::ProductCollection.create_from_results(1,1,res)
121
+ end
122
+
123
+ # assembles the available images for the object
124
+ # @return [Hash] hash of symbolized image_name => Ramazon::Image pairs
125
+ def images
126
+ if !@images
127
+ @images = {}
128
+ @images[:thumb] = self.thumb_image if self.thumb_image
129
+ @images[:tiny_image] = self.tiny_image if self.tiny_image
130
+ @images[:small] = self.small_image if self.small_image
131
+ @images[:medium] = self.medium_image if self.medium_image
132
+ @images[:large] = self.large_image if self.large_image
133
+ end
134
+ end
135
+
136
+ attr_accessor :xml_doc
137
+ def self.parse(xml, options = {})
138
+ node = XML::Parser.string(xml.to_s).parse.root
139
+ node.find("//Item").collect do |n|
140
+ p = super(n.to_s)
141
+ p.xml_doc = Nokogiri::XML.parse(n.to_s)
142
+ p
143
+ end
144
+ end
145
+
146
+ # perform a nokogiri search on the product's XML
147
+ # @param args Passes directly to a Nokogiri::Xml.parse(xml).search method
148
+ # @example find the actor
149
+ # @product = Ramazon::Product.find(:item_id => "B000NU2CY4", :response_group => "Medium")[0]
150
+ # @product.get("ItemAttributes Actor").collect{|a| a.content}
151
+ def get(*args)
152
+ result = @xml_doc.search(args)
153
+ end
154
+
155
+ # returns a hash of category browse nodes from the top down
156
+ def category_tree
157
+ @category_tree = {}
158
+ get_category_browse_nodes.each do |n|
159
+ build_category_tree(n)
160
+ end
161
+ @category_tree
162
+ end
163
+
164
+ # a sorted list of used offers
165
+ def used_offers
166
+ if @used_offers.nil?
167
+ @used_offers = []
168
+ self.offers.each do |o|
169
+ if o.condition.downcase == "used"
170
+ @used_offers << o
171
+ end
172
+ end
173
+ @used_offers.sort!{|a,b| a.price.amount <=> b.price.amount}
174
+ end
175
+ @used_offers
176
+ end
177
+
178
+ # breaks down all the offers in a nested hash of [condition][subcondition]
179
+ # note: this will load ALL offers into memory
180
+ # @return [Hash] a nest hash of offers [condition][subcondition] => Array of Ramazon::Offer objects
181
+ def offers_by_condition
182
+ @offer_hash = {}
183
+ offer_page = 1
184
+
185
+ offers = offer_page(offer_page)
186
+ while offer_page <= offer_pages
187
+ offers.each do |o|
188
+ @offer_hash[o.condition.downcase] ||= {}
189
+ @offer_hash[o.condition.downcase][o.sub_condition] ||= []
190
+ @offer_hash[o.condition.downcase][o.sub_condition] << o
191
+ end
192
+
193
+ offer_page += 1
194
+ offers = offer_page(offer_page)
195
+ end
196
+
197
+ @offer_hash
198
+ end
199
+
200
+ # gets the number of offer pages for the specified product
201
+ # @param [Ramazon::Product] product the we want to get offer pages for
202
+ # @returns [Integer] number of offer pages
203
+ def self.offer_pages_for(product)
204
+ if !@offer_pages
205
+ offer_page_tags = product.get("//Offers/TotalOfferPages")
206
+ if offer_page_tags.size > 0
207
+ offer_pages = offer_page_tags[0].content.to_i
208
+ else
209
+ offer_pages = 1
210
+ end
211
+ end
212
+
213
+ offer_pages
214
+ end
215
+
216
+ # get the lowest offers broken down by subcondition
217
+ # @return [Hash] a nested hash of prices ie ["new"]["mint"] => Ramazon::Offer
218
+ def lowest_offers
219
+ if @lowest_offers.nil?
220
+ @lowest_offers = {}
221
+ offers_by_condition.each do |condition, sub_conditions|
222
+ @lowest_offers[condition] = {}
223
+ sub_conditions.each do |sub_condition, col|
224
+ sorted_offers = col.sort{|a,b| a.price.amount.to_i <=> b.price.amount.to_i}
225
+ @lowest_offers[condition][sub_condition] = sorted_offers.first
226
+ end
227
+ end
228
+ end
229
+
230
+ @lowest_offers
231
+ end
232
+
233
+ def offer_pages=(pages)
234
+ @offer_pages = pages.to_i
235
+ end
236
+
237
+ def offer_pages
238
+ @offer_pages
239
+ end
240
+
241
+ #get offers from a given page
242
+ # @param page [Integer] the page number you want to get
243
+ # @return [Array] Array of Offers returned from the page
244
+ def offer_page(page = 1)
245
+ #get all offers
246
+ if page == 1 && has_first_page_of_full_offers
247
+ self.offers
248
+ else
249
+ products = self.class.find(:item_id => self.asin,
250
+ :response_group => "OfferListings",
251
+ :merchant_id => "All",
252
+ :condition => "All",
253
+ :offer_page => page)
254
+
255
+ if products
256
+ product = products[0]
257
+ self.offer_pages = self.class.offer_pages_for(product)
258
+ product.offers
259
+ else
260
+ []
261
+ end
262
+ end
263
+ end
264
+
265
+ private
266
+ # recursive function used to generate a topdown category tree
267
+ def build_category_tree(n, child = nil)
268
+ amz_node = BrowseNode.parse(n.to_s)
269
+ amz_node.child = child unless child.nil?
270
+
271
+ if n.search("./IsCategoryRoot").size > 0
272
+ @category_tree[amz_node.name] ||= []
273
+ @category_tree[amz_node.name] << amz_node
274
+ else
275
+ parents = n.search("./Ancestors/BrowseNode")
276
+ if parents.size > 0
277
+ build_category_tree(parents[0], amz_node)
278
+ end
279
+ end
280
+
281
+
282
+ end
283
+
284
+ def get_category_browse_nodes
285
+ self.get("BrowseNodes BrowseNode IsCategoryRoot").collect do |n|
286
+ n.ancestors("//BrowseNodes/BrowseNode")
287
+ end
288
+ end
289
+
290
+ def self.find_options_retrieve_all_offers?(options = {})
291
+ if options[:response_group].is_a?(Array)
292
+ groups = options[:response_group].join(" ")
293
+ else
294
+ groups = options[:response_group] || ""
295
+ end
296
+
297
+ groups =~ /OfferListings/ &&
298
+ (options[:offer_page].nil? || options[:offer_page].to_i == 1) &&
299
+ (options[:merchant_id] == "All") &&
300
+ (options[:condition] == "All")
301
+ end
302
+ end
303
+ end