ramazon_advertising 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +46 -0
- data/Rakefile +84 -0
- data/VERSION +1 -0
- data/features/friendly_errors.feature +15 -0
- data/features/generate_root_browse_nodes.feature +13 -0
- data/features/getting_offer_details.feature +30 -0
- data/features/getting_search_bins.feature +22 -0
- data/features/retrieve_browse_node_information.feature +16 -0
- data/features/retrieving_a_product.feature +32 -0
- data/features/searching_for_products.feature +47 -0
- data/features/step_definitions/auth_steps.rb +8 -0
- data/features/step_definitions/browse_node_steps.rb +55 -0
- data/features/step_definitions/error_steps.rb +14 -0
- data/features/step_definitions/product_collection_steps.rb +20 -0
- data/features/step_definitions/product_steps.rb +75 -0
- data/features/step_definitions/ramazon_advertising_steps.rb +0 -0
- data/features/support/env.rb +7 -0
- data/features/support/ramazon_advertising.example.yml +2 -0
- data/lib/ramazon/abstract_element.rb +18 -0
- data/lib/ramazon/browse_node.rb +69 -0
- data/lib/ramazon/configuration.rb +66 -0
- data/lib/ramazon/error.rb +12 -0
- data/lib/ramazon/image.rb +18 -0
- data/lib/ramazon/merchant.rb +21 -0
- data/lib/ramazon/offer.rb +27 -0
- data/lib/ramazon/price.rb +18 -0
- data/lib/ramazon/product.rb +303 -0
- data/lib/ramazon/product_collection.rb +30 -0
- data/lib/ramazon/rails_additions.rb +99 -0
- data/lib/ramazon/request.rb +82 -0
- data/lib/ramazon/search_bin.rb +11 -0
- data/lib/ramazon/search_bin_parameter.rb +9 -0
- data/lib/ramazon/search_bin_set.rb +11 -0
- data/lib/ramazon/signatory.rb +10 -0
- data/lib/ramazon_advertising.rb +41 -0
- data/lib/root_nodes.yml +64 -0
- data/lib/tasks/ramazon.rake +9 -0
- data/spec/ramazon/configuration_spec.rb +21 -0
- data/spec/spec_helper.rb +11 -0
- 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
|
+
|
File without changes
|
@@ -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,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
|