dpickett-ramazon_advertising 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ features/support/ramazon_advertising.yml
7
+ features/support/root_nodes.yml
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Dan Pickett
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,44 @@
1
+ = ramazon_advertising
2
+
3
+ Ruby + Amazon Advertising == Car RAmazon_Advertising - Say Car RAmazon_Advertising!
4
+
5
+ Ruh roh' Rorge it's an object oriented approach to Amazon's overly complicated product api that has a new name every 2 weeks.
6
+
7
+ Complete with the ability to use nokogiri selectors for results
8
+
9
+ Currently only supports product search and retrieval. Requests are signed properly. More soon!
10
+
11
+ Ramazon::Configuration.access_key = "Your Access Key"
12
+ Ramazon::Configuration.secret_key = "Your Secret Key"
13
+
14
+ @products = Ramazon::Product.find(:item_id => "B000NU2CY4", :response_group => "Medium")
15
+ @products[0].title
16
+ @products[0].asin
17
+ @products[0].upc
18
+ @products[0].large_image.url
19
+ @products[0].url
20
+
21
+ #you can also use a nokogiri search string to get elements that don't have built-in accessors
22
+ @products[0].get("ItemAttributes Actor").collect{|a| a.content}
23
+
24
+ == What's under the hood?
25
+
26
+ * HTTParty
27
+ * HappyMapper
28
+ * will_paginate
29
+ * nokogiri
30
+
31
+ == Note on Patches/Pull Requests
32
+
33
+ * Fork the project.
34
+ * Make your feature addition or bug fix.
35
+ * Add tests for it. This is important so I don't break it in a
36
+ future version unintentionally.
37
+ * Commit, do not mess with rakefile, version, or history.
38
+ (if you want to have your own version, that is fine but
39
+ bump version in a commit by itself I can ignore when I pull)
40
+ * Send me a pull request. Bonus points for topic branches.
41
+
42
+ == Copyright
43
+
44
+ Copyright (c) 2009 Dan Pickett. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require 'lib/ramazon_advertising'
5
+ load File.join(File.dirname(__FILE__), 'lib', 'tasks', 'ramazon.rake')
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "ramazon_advertising"
11
+ gem.summary = %Q{TODO: one-line summary of your gem}
12
+ gem.description = %Q{TODO: longer description of your gem}
13
+ gem.email = "dpickett@enlightsolutions.com"
14
+ gem.homepage = "http://github.com/dpickett/ramazon_advertising"
15
+ gem.authors = ["Dan Pickett"]
16
+ gem.add_dependency("jnunemaker-httparty", ">= 0.4.3")
17
+ gem.add_dependency("jnunemaker-happymapper", ">= 0.2.5")
18
+ gem.add_dependency("mislav-will_paginate", ">= 2.3.11")
19
+ gem.add_dependency("nokogiri", ">= 1.3.3")
20
+ gem.add_dependency("configatron", ">= 2.4.1")
21
+ gem.add_dependency("rails", ">= 2.3.3")
22
+ gem.add_dependency("ruby-hmac", ">= 0.3.2")
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+
26
+ rescue LoadError
27
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
28
+ end
29
+
30
+ require 'spec/rake/spectask'
31
+ Spec::Rake::SpecTask.new(:spec) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.spec_files = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
37
+ spec.libs << 'lib' << 'spec'
38
+ spec.pattern = 'spec/**/*_spec.rb'
39
+ spec.rcov = true
40
+ end
41
+
42
+ task :default => :spec
43
+
44
+ begin
45
+ require 'cucumber/rake/task'
46
+ Cucumber::Rake::Task.new(:cucumber)
47
+ rescue LoadError
48
+ task :cucumber do
49
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
50
+ end
51
+ end
52
+
53
+ task :default => :test
54
+
55
+ require 'rake/rdoctask'
56
+ Rake::RDocTask.new do |rdoc|
57
+ if File.exist?('VERSION.yml')
58
+ config = YAML.load(File.read('VERSION.yml'))
59
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
60
+ else
61
+ version = ""
62
+ end
63
+
64
+ rdoc.rdoc_dir = 'rdoc'
65
+ rdoc.title = "ramazon_advertising #{version}"
66
+ rdoc.rdoc_files.include('README*')
67
+ rdoc.rdoc_files.include('lib/**/*.rb')
68
+ end
69
+
70
+ begin
71
+ require "YARD"
72
+ if File.exist?('VERSION.yml')
73
+ config = YAML.load(File.read('VERSION.yml'))
74
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
75
+ else
76
+ version = ""
77
+ end
78
+
79
+ YARD::Rake::YardocTask.new do |t|
80
+ end
81
+ rescue LoadError
82
+
83
+ end
84
+
85
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,15 @@
1
+ @errors
2
+ Feature: Friendly errors
3
+ As a user of ramazon_advertising
4
+ I want to get friendly error messages
5
+ So that I know I did something wrong
6
+
7
+ Background:
8
+ Given I have a valid access key
9
+ And I have a valid secret key
10
+
11
+ Scenario: I don't specify a keyword for search
12
+ When I perform the product search
13
+ Then I should get an error
14
+ And the error should have a "code" of "AWS.MinimumParameterRequirement"
15
+ And the error should have a "message"
@@ -0,0 +1,12 @@
1
+ Feature: As a user of the Ramazon Advertising API
2
+ I want a list of browse nodes
3
+ So that I have starting points for browse node traversal
4
+
5
+ Scenario: Retrieving browse nodes
6
+ Given I want browse nodes to be stored in a temporary file
7
+ And the browse node temporary file doesn't exist
8
+ When I retrieve root nodes
9
+ Then I should get a temporary file for root nodes
10
+ And I should have a "Books" root node
11
+ And I should have a "Grocery" root node
12
+ And I should have a list of root nodes
@@ -0,0 +1,23 @@
1
+ Feature: Retrieving a product
2
+ As a user of the Ramazon_advertising_api
3
+ I want to get specific information
4
+ In order to leverage the data provided by the api
5
+
6
+ Background:
7
+ Given I have a valid access key
8
+ And I have a valid secret key
9
+
10
+ When I try to find the asin "B000NU2CY4"
11
+ Then I should get a product
12
+ And the product should have the "title" "Gladiator [Blu-ray]"
13
+ And the product should have a "manufacturer"
14
+ And the product should have a "product_group"
15
+ And the product should have a "sales_rank"
16
+ And the product should have a "large_image"
17
+ And the product should have a "list_price"
18
+ And the product should have a "upc"
19
+ And the product should have a "lowest_new_price"
20
+ And the product should have a "new_count"
21
+ And the product should have a "used_count"
22
+
23
+
@@ -0,0 +1,15 @@
1
+ Feature: Search for products
2
+ As a user of the Ramazon_advertising api
3
+ I want to perform a search
4
+ In order to find products my customers will want to purchase
5
+
6
+ Background:
7
+ Given I have a valid access key
8
+ And I have a valid secret key
9
+
10
+ Given I am searching with the "search_index" of "DVD"
11
+ And I am searching with the "browse_node" of "130"
12
+ When I perform the product search
13
+ Then I should get a list of products
14
+ And the list of products should have more than 1 product
15
+ And each product should have the "product_group" "DVD"
@@ -0,0 +1,8 @@
1
+ Given /^I have a valid access key$/ do
2
+ configatron.ramazon.access_key.should_not be_nil
3
+ end
4
+
5
+ Given /^I have a valid secret key$/ do
6
+ configatron.ramazon.secret_key.should_not be_nil
7
+ end
8
+
@@ -0,0 +1,36 @@
1
+ Given /^I want browse nodes to be stored in a temporary file$/ do
2
+ @browse_root_filename = File.join(File.dirname(__FILE__), '..', 'support', 'root_nodes.yml')
3
+ end
4
+
5
+ Given /^the browse node temporary file doesn't exist$/ do
6
+ FileUtils.rm_f @browse_root_filename
7
+ end
8
+
9
+ When /^I retrieve root nodes$/ do
10
+ if @browse_root_filename
11
+ Ramazon::BrowseNode.generate_root_nodes @browse_root_filename
12
+ else
13
+ Ramazon::BrowseNode.generate_root_nodes
14
+ end
15
+ end
16
+
17
+ Then /^I should get a temporary file for root nodes$/ do
18
+ FileTest.exists?(@browse_root_filename).should be_true
19
+ end
20
+
21
+ Then /^I should have a "([^\"]*)" root node$/ do |name|
22
+ get_root_nodes[name].should_not be_nil
23
+ end
24
+
25
+ Then /^I should have a list of root nodes$/ do
26
+ get_root_nodes.should_not be_empty
27
+ end
28
+
29
+ def get_root_nodes
30
+ if @browse_root_filename
31
+ nodes = Ramazon::BrowseNode.root_nodes(@browse_root_filename)
32
+ else
33
+ nodes = Ramazon::BrowseNode.root_nodes
34
+ end
35
+
36
+ end
@@ -0,0 +1,14 @@
1
+ Then /^I should get an error$/ do
2
+ @error.should_not be_nil
3
+ end
4
+
5
+ Then /^the error should have a "([^\"]*)" of "([^\"]*)"$/ do |attr, value|
6
+ @error.send(attr).should eql(value)
7
+ end
8
+
9
+ Then /^the error should have a "([^\"]*)"$/ do |attr|
10
+ @error.send(attr).should_not be_nil
11
+ end
12
+
13
+
14
+
@@ -0,0 +1,48 @@
1
+ When /^I try to find the asin "([^\"]*)"$/ do |asin|
2
+ @products = Ramazon::Product.find(:item_id => asin, :response_group => "Medium")
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] = 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.should_not be_nil
39
+ end
40
+
41
+ Then /^the product should have the "([^\"]*)" "([^\"]*)"$/ do |attr, value|
42
+ @product.send(attr).should eql(value)
43
+ end
44
+
45
+ Then /^the product should have a "([^\"]*)"$/ do |attr|
46
+ @product.send(attr).should_not be_nil
47
+ end
48
+
@@ -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,32 @@
1
+ module Ramazon
2
+ class BrowseNode
3
+ include HTTParty
4
+
5
+ DEFAULT_ROOT_FILE = File.join(File.dirname(__FILE__), '..', 'root_nodes.yml')
6
+ def self.generate_root_nodes(file_name = DEFAULT_ROOT_FILE)
7
+ if Ramazon::Configuration.locale == :us
8
+ doc = Nokogiri::HTML(get('http://www.amazon.com').body)
9
+ root_nodes = {}
10
+ doc.search(".navSaMenu .navSaChildItem a").each do |element|
11
+ if element["href"] =~ /node=(\d+)\&/
12
+ root_nodes[element.content] = $1
13
+ end
14
+ end
15
+
16
+ unless root_nodes.empty?
17
+ FileUtils.rm_f(file_name)
18
+ File.open(file_name, 'w') do |f|
19
+ f.write(root_nodes.to_yaml)
20
+ end
21
+ end
22
+ else
23
+ #todo correlate ECS locales to actual amazon.* urls
24
+ raise "generating root nodes for locale's other than the US is not supported"
25
+ end
26
+ end
27
+
28
+ def self.root_nodes(file_name = DEFAULT_ROOT_FILE)
29
+ @root_nodes ||= File.open(file_name) { |yf| YAML::load(yf) }
30
+ end
31
+ end
32
+ 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,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,144 @@
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
+ # @example find an individual item
44
+ # @products = Ramazon::Product.find(:item_id => "B000NU2CY4", :response_group => "Medium")
45
+ # @products[0].title
46
+ # @products[0].asin
47
+ # @products[0].upc
48
+ # @products[0].large_image.url
49
+ # @products[0].url
50
+ #
51
+
52
+ class Product
53
+ include HappyMapper
54
+ include Ramazon::AbstractElement
55
+ tag 'Item'
56
+
57
+ element :asin, String, :tag => 'ASIN'
58
+ element :upc, String, :tag => 'ItemAttributes/UPC'
59
+ element :title, String, :tag => 'Title', :deep => true
60
+ element :product_group, String, :tag => 'ItemAttributes/ProductGroup'
61
+ element :manufacturer, String, :tag => 'ItemAttributes/Manufacturer'
62
+ element :brand, String, :tag => 'ItemAttributes/Brand'
63
+ element :url, String, :tag => 'DetailPageURL'
64
+ abstract_element :small_image, Ramazon::Image, :tag => "SmallImage"
65
+ abstract_element :large_image, Ramazon::Image, :tag => "LargeImage"
66
+ abstract_element :medium_image, Ramazon::Image, :tag => "MediumImage"
67
+ abstract_element :tiny_image, Ramazon::Image, :tag => "TinyImage"
68
+ abstract_element :thumb_image, Ramazon::Image, :tag => "ThumbImage"
69
+ abstract_element :list_price, Ramazon::Price, :tag => "ItemAttributes/ListPrice"
70
+ abstract_element :lowest_new_price, Ramazon::Price, :tag => "OfferSummary/LowestNewPrice"
71
+
72
+ element :sales_rank, Integer, :tag => "SalesRank"
73
+
74
+ element :new_count, Integer, :tag => "OfferSummary/TotalNew"
75
+ element :used_count, Integer, :tag => "OfferSummary/TotalUsed"
76
+ element :collectible_count, Integer, :tag => "OfferSummary/TotalCollectible"
77
+ element :refurbished_count, Integer, :tag => "OfferSummary/TotalRefurbished"
78
+
79
+ element :release_date, Date, :tag => 'ItemAttributes/ReleaseDate'
80
+ element :original_release_date, Date, :tag => 'ItemAttributes/OriginalReleaseDate'
81
+
82
+
83
+ # Creates the worker that performs the delta indexing
84
+ # @param options Amazon request options (you can use an underscore convention)
85
+ # (ie. passing the :response_group option will be converted to "ResponseGroup")
86
+ # <tt>:item_id</tt> - the ASIN or UPC you're looking for
87
+ # @return [Array] array of Ramazon::Product objects
88
+ def self.find(*args)
89
+ options = args.extract_options!
90
+ if options[:item_id]
91
+ item_lookup(options[:item_id], options)
92
+ else
93
+ options[:operation] ||= "ItemSearch"
94
+ options[:search_index] ||= "Blended"
95
+ options[:item_page] ||= 1
96
+ res = Ramazon::Request.new(options).submit
97
+ Ramazon::ProductCollection.create_from_results(options[:item_page] || 1, 10, res)
98
+ end
99
+ end
100
+
101
+ # Performs an item lookup
102
+ # @param item_id the ASIN or UPC you're looking for
103
+ # @options additional Amazon request options (i.e. :response_group)
104
+ def self.item_lookup(item_id, options = {})
105
+ req = Ramazon::Request.new({:item_id => item_id,
106
+ :operation => "ItemLookup"}.merge(options))
107
+ res = req.submit
108
+
109
+ Ramazon::ProductCollection.create_from_results(1,1,res)
110
+ end
111
+
112
+ # assembles the available images for the object
113
+ # @return [Hash] hash of symbolized image_name => Ramazon::Image pairs
114
+ def images
115
+ if !@images
116
+ @images = {}
117
+ @images[:thumb] = self.thumb_image if self.thumb_image
118
+ @images[:tiny_image] = self.tiny_image if self.tiny_image
119
+ @images[:small] = self.small_image if self.small_image
120
+ @images[:medium] = self.medium_image if self.medium_image
121
+ @images[:large] = self.large_image if self.large_image
122
+ end
123
+ end
124
+
125
+ attr_accessor :xml_doc
126
+ def self.parse(xml, options = {})
127
+ node = XML::Parser.string(xml.to_s).parse.root
128
+ node.find("//Item").collect do |n|
129
+ p = super(n.to_s)
130
+ p.xml_doc = Nokogiri::XML.parse(n.to_s)
131
+ p
132
+ end
133
+ end
134
+
135
+ # perform a nokogiri search on the product's XML
136
+ # @param args Passes directly to a Nokogiri::Xml.parse(xml).search method
137
+ # @example find the actor
138
+ # @product = Ramazon::Product.find(:item_id => "B000NU2CY4", :response_group => "Medium")[0]
139
+ # @product.get("ItemAttributes Actor").collect{|a| a.content}
140
+ def get(*args)
141
+ result = @xml_doc.search(args)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,27 @@
1
+ module Ramazon
2
+ class ProductCollection < WillPaginate::Collection
3
+ include HappyMapper
4
+
5
+ def self.create_from_results(page, per_page, body)
6
+ results = Items.parse(body, {})[0]
7
+ col = create(page, 10, results.total_results || 0) do |pager|
8
+ pager.replace(results.products)
9
+ end
10
+
11
+ col
12
+ end
13
+
14
+ end
15
+
16
+ class Items
17
+ include HappyMapper
18
+ tag 'Items'
19
+
20
+ element :total_results, Integer, :tag => 'TotalResults'
21
+ element :total_pages, Integer, :tag => 'TotalPages'
22
+ has_many :products,
23
+ Ramazon::Product,
24
+ :tag => "Item",
25
+ :raw => true
26
+ end
27
+ end
@@ -0,0 +1,99 @@
1
+ #this code courtesy of Pat Allan and Rails Core
2
+ module Ramazon
3
+ module HashExcept
4
+ # Returns a new hash without the given keys.
5
+ def except(*keys)
6
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
7
+ reject { |key,| rejected.include?(key) }
8
+ end
9
+
10
+ # Replaces the hash without only the given keys.
11
+ def except!(*keys)
12
+ replace(except(*keys))
13
+ end
14
+ end
15
+ end
16
+
17
+ Hash.send(
18
+ :include, Ramazon::HashExcept
19
+ ) unless Hash.instance_methods.include?("except")
20
+
21
+ module Ramazon
22
+ module ArrayExtractOptions
23
+ def extract_options!
24
+ last.is_a?(::Hash) ? pop : {}
25
+ end
26
+ end
27
+ end
28
+
29
+ Array.send(
30
+ :include, Ramazon::ArrayExtractOptions
31
+ ) unless Array.instance_methods.include?("extract_options!")
32
+
33
+ module Ramazon
34
+ module ClassAttributeMethods
35
+ def cattr_reader(*syms)
36
+ syms.flatten.each do |sym|
37
+ next if sym.is_a?(Hash)
38
+ class_eval(<<-EOS, __FILE__, __LINE__)
39
+ unless defined? @@#{sym}
40
+ @@#{sym} = nil
41
+ end
42
+
43
+ def self.#{sym}
44
+ @@#{sym}
45
+ end
46
+
47
+ def #{sym}
48
+ @@#{sym}
49
+ end
50
+ EOS
51
+ end
52
+ end
53
+
54
+ def cattr_writer(*syms)
55
+ options = syms.extract_options!
56
+ syms.flatten.each do |sym|
57
+ class_eval(<<-EOS, __FILE__, __LINE__)
58
+ unless defined? @@#{sym}
59
+ @@#{sym} = nil
60
+ end
61
+
62
+ def self.#{sym}=(obj)
63
+ @@#{sym} = obj
64
+ end
65
+
66
+ #{"
67
+ def #{sym}=(obj)
68
+ @@#{sym} = obj
69
+ end
70
+ " unless options[:instance_writer] == false }
71
+ EOS
72
+ end
73
+ end
74
+
75
+ def cattr_accessor(*syms)
76
+ cattr_reader(*syms)
77
+ cattr_writer(*syms)
78
+ end
79
+ end
80
+ end
81
+
82
+ Class.extend(
83
+ Ramazon::ClassAttributeMethods
84
+ ) unless Class.respond_to?(:cattr_reader)
85
+
86
+ module Ramazon
87
+ module MetaClass
88
+ def metaclass
89
+ class << self
90
+ self
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ unless Object.new.respond_to?(:metaclass)
97
+ Object.send(:include, Ramazon::MetaClass)
98
+ end
99
+
@@ -0,0 +1,82 @@
1
+ module Ramazon
2
+ #the object used to prepare and submit requests to amazon
3
+ class Request
4
+ include HTTParty
5
+ attr_accessor :options
6
+
7
+ def initialize(options = {})
8
+ self.options = options.merge(default_options)
9
+ end
10
+
11
+ def submit
12
+ @response = self.class.get(signed_url)
13
+ if valid?
14
+ @response.body
15
+ else
16
+ raise self.errors[0], "The following response errors were returned: #{self.errors.collect{|e| e.to_s}}"
17
+ end
18
+ end
19
+
20
+ # Error checking for responses (will return true if errors are present)
21
+ # @returns [boolean] whether the response yielded errors
22
+ def valid?
23
+ @response && errors.empty?
24
+ end
25
+
26
+ #errors returned from amazon
27
+ def errors
28
+ @errors ||= Ramazon::Error.parse(@response.body) || []
29
+ end
30
+
31
+ #use this if any type of caching is desired (TBD)
32
+ def unsigned_path
33
+ qs = ""
34
+
35
+ sorted_params.each do |key, value|
36
+ qs << "&#{key}=#{URI.encode(value.to_s)}"
37
+ end
38
+
39
+ qs.size > 0 ? qs[1..qs.size] : qs
40
+ end
41
+
42
+ def signed_url
43
+ self.options[:timestamp] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S")
44
+ encoded_param_strings = []
45
+ sorted_params.each do |p|
46
+ encoded_param_strings << "#{p[0]}=#{CGI::escape(p[1].to_s)}"
47
+ end
48
+ string_to_sign =
49
+ "GET
50
+ #{Ramazon::Configuration.base_uri.gsub("http://", "")}
51
+ #{Ramazon::Configuration.path}
52
+ #{encoded_param_strings.join("&")}"
53
+
54
+ signature = Ramazon::Signatory.sign(string_to_sign)
55
+ encoded_param_strings << "Signature=" + signature
56
+
57
+ "#{Ramazon::Configuration.uri}?#{encoded_param_strings.join("&")}"
58
+ end
59
+
60
+ def sorted_params
61
+ params.sort {|a, b| a <=> b}
62
+ end
63
+
64
+ def params
65
+ formatted_params = {}
66
+ self.options.each do |key, value|
67
+ formatted_params[key.to_s.classify] = value.is_a?(Array) ? value.join(",") : value
68
+ end
69
+
70
+ formatted_params
71
+ end
72
+
73
+ private
74
+ def default_options
75
+ {
76
+ :service => "AWSECommerceService",
77
+ :version => "2009-07-01",
78
+ :AWSAccessKeyId => Ramazon::Configuration.access_key
79
+ }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,10 @@
1
+ module Ramazon
2
+ class Signatory
3
+ def self.sign(string_to_sign)
4
+ sha1 = HMAC::SHA256.digest(Ramazon::Configuration.secret_key, string_to_sign)
5
+
6
+ #Base64 encoding adds a linefeed to the end of the string so chop the last character!
7
+ CGI.escape(Base64.encode64(sha1).chomp)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ require "rubygems"
4
+
5
+ require "fileutils"
6
+ require "base64"
7
+ require "hmac-sha2"
8
+ require "digest/sha1"
9
+ require "digest/sha2"
10
+ require "cgi"
11
+
12
+ require "will_paginate"
13
+ require "httparty"
14
+ require "happymapper"
15
+ require "nokogiri"
16
+ require "configatron"
17
+ require "active_support/inflector"
18
+
19
+ require "ramazon/rails_additions"
20
+
21
+ require "ramazon/abstract_element"
22
+
23
+ require "ramazon/signatory"
24
+ require "ramazon/configuration"
25
+ require "ramazon/error"
26
+ require "ramazon/request"
27
+
28
+ require "ramazon/image"
29
+ require "ramazon/price"
30
+ require "ramazon/product"
31
+ require "ramazon/product_collection"
32
+ require "ramazon/browse_node"
33
+
34
+ configatron.ramazon.locale = :us
@@ -0,0 +1,64 @@
1
+ ---
2
+ Apparel (Kids & Baby): "1040662"
3
+ Baby: "165796011"
4
+ Gourmet Food: "3370831"
5
+ Home Appliances: "361395011"
6
+ Camera & Photo: "502394"
7
+ All Sports & Outdoors: "3375251"
8
+ Health & Personal Care: "3760901"
9
+ Cell Phones & Service: "301185"
10
+ Home Audio & Theater: "667846011"
11
+ Power & Hand Tools: "328182011"
12
+ Golf: "3410851"
13
+ Team Sports: "706809011"
14
+ Action Sports: "706812011"
15
+ Vacuums & Storage: "510080"
16
+ Office Products & Supplies: "1064954"
17
+ Magazines: "1263069011"
18
+ Industrial & Scientific: "16310091"
19
+ Jewelry: "3367581"
20
+ Pet Supplies: "12923371"
21
+ Home Improvement: "228013"
22
+ Bedding & Bath: "1057792"
23
+ TV & Video: "1266092011"
24
+ Computers & Accessories: "541966"
25
+ Books: "4"
26
+ Motorcycle & ATV: "346333011"
27
+ Outdoor Power Equipment: "551242"
28
+ Outdoor Recreation: "706814011"
29
+ Natural & Organic: "51537011"
30
+ "Furniture & D\xC3\xA9cor": "1057794"
31
+ Newspapers: "1263068011"
32
+ MP3 & Media Players: "172630"
33
+ Accessories: "1268192011"
34
+ MP3 Downloads: "163856011"
35
+ Kindle Books: "1286228011"
36
+ Toys & Games: "165793011"
37
+ Beauty: "3760911"
38
+ Grocery: "16310101"
39
+ Blogs: "401358011"
40
+ Video Games: "471306"
41
+ Cycling: "3403201"
42
+ Watches: "377110011"
43
+ Movies: "163414"
44
+ Sewing, Craft & Hobby: "12890711"
45
+ Software: "229548"
46
+ Kindle Store: "133141011"
47
+ Game Downloads: "979455011"
48
+ Video On Demand: "16261631"
49
+ Movies & TV: "130"
50
+ Textbooks: "465600"
51
+ Shoes: "672123011"
52
+ Apparel & Accessories: "1036592"
53
+ Kitchen & Dining: "284507"
54
+ Amazon Shorts: "13993911"
55
+ Blu-ray: "193640011"
56
+ Car Electronics & GPS: "1077068"
57
+ PC Games: "229575"
58
+ Computer Components: "193870011"
59
+ Music: "173425"
60
+ Automotive: "15684181"
61
+ Fan Gear: "3386071"
62
+ Exercise & Fitness: "3407731"
63
+ Patio, Lawn & Garden: "286168"
64
+ Musical Instruments: "11091801"
@@ -0,0 +1,9 @@
1
+ namespace :ramazon do
2
+ desc "Generate the yml file used for looking up root browse nodes"
3
+ task :generate_node_yml do
4
+ puts "Generating root nodes"
5
+ Ramazon::BrowseNode.generate_root_nodes
6
+ puts "Done"
7
+ end
8
+ end
9
+
@@ -0,0 +1,92 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ramazon_advertising}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Dan Pickett"]
9
+ s.date = %q{2009-09-08}
10
+ s.description = %q{TODO: longer description of your gem}
11
+ s.email = %q{dpickett@enlightsolutions.com}
12
+ s.extra_rdoc_files = [
13
+ "LICENSE",
14
+ "README.rdoc"
15
+ ]
16
+ s.files = [
17
+ ".document",
18
+ ".gitignore",
19
+ "LICENSE",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "features/friendly_errors.feature",
24
+ "features/generate_root_browse_nodes.feature",
25
+ "features/retrieving_a_product.feature",
26
+ "features/searching_for_products.feature",
27
+ "features/step_definitions/auth_steps.rb",
28
+ "features/step_definitions/browse_node_steps.rb",
29
+ "features/step_definitions/error_steps.rb",
30
+ "features/step_definitions/product_steps.rb",
31
+ "features/step_definitions/ramazon_advertising_steps.rb",
32
+ "features/support/env.rb",
33
+ "features/support/ramazon_advertising.example.yml",
34
+ "lib/ramazon/abstract_element.rb",
35
+ "lib/ramazon/browse_node.rb",
36
+ "lib/ramazon/configuration.rb",
37
+ "lib/ramazon/error.rb",
38
+ "lib/ramazon/image.rb",
39
+ "lib/ramazon/price.rb",
40
+ "lib/ramazon/product.rb",
41
+ "lib/ramazon/product_collection.rb",
42
+ "lib/ramazon/rails_additions.rb",
43
+ "lib/ramazon/request.rb",
44
+ "lib/ramazon/signatory.rb",
45
+ "lib/ramazon_advertising.rb",
46
+ "lib/root_nodes.yml",
47
+ "lib/tasks/ramazon.rake",
48
+ "ramazon_advertising.gemspec",
49
+ "spec/ramazon/configuration_spec.rb",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/dpickett/ramazon_advertising}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.5}
56
+ s.summary = %q{TODO: one-line summary of your gem}
57
+ s.test_files = [
58
+ "spec/ramazon/configuration_spec.rb",
59
+ "spec/spec_helper.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
+ s.add_runtime_dependency(%q<jnunemaker-httparty>, [">= 0.4.3"])
68
+ s.add_runtime_dependency(%q<jnunemaker-happymapper>, [">= 0.2.5"])
69
+ s.add_runtime_dependency(%q<mislav-will_paginate>, [">= 2.3.11"])
70
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.3.3"])
71
+ s.add_runtime_dependency(%q<configatron>, [">= 2.4.1"])
72
+ s.add_runtime_dependency(%q<rails>, [">= 2.3.3"])
73
+ s.add_runtime_dependency(%q<ruby-hmac>, [">= 0.3.2"])
74
+ else
75
+ s.add_dependency(%q<jnunemaker-httparty>, [">= 0.4.3"])
76
+ s.add_dependency(%q<jnunemaker-happymapper>, [">= 0.2.5"])
77
+ s.add_dependency(%q<mislav-will_paginate>, [">= 2.3.11"])
78
+ s.add_dependency(%q<nokogiri>, [">= 1.3.3"])
79
+ s.add_dependency(%q<configatron>, [">= 2.4.1"])
80
+ s.add_dependency(%q<rails>, [">= 2.3.3"])
81
+ s.add_dependency(%q<ruby-hmac>, [">= 0.3.2"])
82
+ end
83
+ else
84
+ s.add_dependency(%q<jnunemaker-httparty>, [">= 0.4.3"])
85
+ s.add_dependency(%q<jnunemaker-happymapper>, [">= 0.2.5"])
86
+ s.add_dependency(%q<mislav-will_paginate>, [">= 2.3.11"])
87
+ s.add_dependency(%q<nokogiri>, [">= 1.3.3"])
88
+ s.add_dependency(%q<configatron>, [">= 2.4.1"])
89
+ s.add_dependency(%q<rails>, [">= 2.3.3"])
90
+ s.add_dependency(%q<ruby-hmac>, [">= 0.3.2"])
91
+ end
92
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe Ramazon::Configuration do
4
+ it "should default to the us" do
5
+ Ramazon::Configuration.locale.should eql(:us)
6
+ end
7
+
8
+ it "should raise an exception if a set a locale that doesn't exist" do
9
+ lambda {Ramazon::Configuration.locale = :ffsf}.should raise_error
10
+ end
11
+
12
+ it "should derive a url of the locale" do
13
+ Ramazon::Configuration.locale = :de
14
+ Ramazon::Configuration.base_uri.should =~ /#{Ramazon::Configuration.locale.to_s}/
15
+ end
16
+
17
+ it "should use .com for a us address" do
18
+ Ramazon::Configuration.locale = :us
19
+ Ramazon::Configuration.base_uri.should =~ /\.com/
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'ramazon_advertising'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dpickett-ramazon_advertising
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Dan Pickett
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jnunemaker-httparty
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.4.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: jnunemaker-happymapper
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.5
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mislav-will_paginate
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.11
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: nokogiri
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.3
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: configatron
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.4.1
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: rails
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.3.3
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: ruby-hmac
77
+ type: :runtime
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 0.3.2
84
+ version:
85
+ description: "TODO: longer description of your gem"
86
+ email: dpickett@enlightsolutions.com
87
+ executables: []
88
+
89
+ extensions: []
90
+
91
+ extra_rdoc_files:
92
+ - LICENSE
93
+ - README.rdoc
94
+ files:
95
+ - .document
96
+ - .gitignore
97
+ - LICENSE
98
+ - README.rdoc
99
+ - Rakefile
100
+ - VERSION
101
+ - features/friendly_errors.feature
102
+ - features/generate_root_browse_nodes.feature
103
+ - features/retrieving_a_product.feature
104
+ - features/searching_for_products.feature
105
+ - features/step_definitions/auth_steps.rb
106
+ - features/step_definitions/browse_node_steps.rb
107
+ - features/step_definitions/error_steps.rb
108
+ - features/step_definitions/product_steps.rb
109
+ - features/step_definitions/ramazon_advertising_steps.rb
110
+ - features/support/env.rb
111
+ - features/support/ramazon_advertising.example.yml
112
+ - lib/ramazon/abstract_element.rb
113
+ - lib/ramazon/browse_node.rb
114
+ - lib/ramazon/configuration.rb
115
+ - lib/ramazon/error.rb
116
+ - lib/ramazon/image.rb
117
+ - lib/ramazon/price.rb
118
+ - lib/ramazon/product.rb
119
+ - lib/ramazon/product_collection.rb
120
+ - lib/ramazon/rails_additions.rb
121
+ - lib/ramazon/request.rb
122
+ - lib/ramazon/signatory.rb
123
+ - lib/ramazon_advertising.rb
124
+ - lib/root_nodes.yml
125
+ - lib/tasks/ramazon.rake
126
+ - ramazon_advertising.gemspec
127
+ - spec/ramazon/configuration_spec.rb
128
+ - spec/spec_helper.rb
129
+ has_rdoc: false
130
+ homepage: http://github.com/dpickett/ramazon_advertising
131
+ post_install_message:
132
+ rdoc_options:
133
+ - --charset=UTF-8
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: "0"
141
+ version:
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: "0"
147
+ version:
148
+ requirements: []
149
+
150
+ rubyforge_project:
151
+ rubygems_version: 1.2.0
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: "TODO: one-line summary of your gem"
155
+ test_files:
156
+ - spec/ramazon/configuration_spec.rb
157
+ - spec/spec_helper.rb