amazon-product-advertising-api-prezjordan 0.2.2

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.
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ ._*
3
+ rdoc
4
+ test/test_local.rb
@@ -0,0 +1,128 @@
1
+ Basic usage
2
+ ###########
3
+
4
+ require 'rubygems'
5
+ require 'amazon_product_advertising_api'
6
+
7
+ # Setup your API keys - in an initializer or something like that
8
+ AmazonProductAdvertisingApi::Base.access_key_id = "<insert api key here>"
9
+ AmazonProductAdvertisingApi::Base.secret_access_key = "<insert secret access key here>"
10
+
11
+ # Setup Associates IDs for whichever regions you're selling to
12
+ AmazonProductAdvertisingApi::Base.associate_ids.uk = "<insert UK Associate ID here>"
13
+ AmazonProductAdvertisingApi::Base.associate_ids.us = "<insert US Associate ID here>"
14
+
15
+ lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("0201485419")
16
+ lookup.run
17
+
18
+ # Returned XML, tidied
19
+ # <?xml version="1.0"?>
20
+ # <ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
21
+ # <OperationRequest>
22
+ # <RequestId>d6c19392-7710-4b05-ad9c-1637755170d8</RequestId>
23
+ # <Arguments>
24
+ # <Argument Name="Operation" Value="ItemLookup"/>
25
+ # <Argument Name="Service" Value="AWSECommerceService"/>
26
+ # <Argument Name="ItemId" Value="0201485419"/>
27
+ # <Argument Name="AWSAccessKeyId" Value="<your api key>"/>
28
+ # </Arguments>
29
+ # <RequestProcessingTime>0.0376290000000000</RequestProcessingTime>
30
+ # </OperationRequest>
31
+ # <Items>
32
+ # <Request>
33
+ # <IsValid>True</IsValid>
34
+ # <ItemLookupRequest>
35
+ # <Condition>New</Condition>
36
+ # <DeliveryMethod>Ship</DeliveryMethod>
37
+ # <IdType>ASIN</IdType>
38
+ # <MerchantId>Amazon</MerchantId>
39
+ # <OfferPage>1</OfferPage>
40
+ # <ItemId>0201485419</ItemId>
41
+ # <ResponseGroup>Small</ResponseGroup>
42
+ # <ReviewPage>1</ReviewPage>
43
+ # </ItemLookupRequest>
44
+ # </Request>
45
+ # <Item>
46
+ # <ASIN>0201485419</ASIN>
47
+ # <DetailPageURL>http://www.amazon.co.uk/Art-Computer-Programming-Information-Processing/dp/0201485419%3FSubscriptionId%3D<your api key>%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0201485419</DetailPageURL>
48
+ # <ItemAttributes>git
49
+ # <Author>Donald E. Knuth</Author>
50
+ # <Manufacturer>Addison Wesley</Manufacturer>
51
+ # <ProductGroup>Book</ProductGroup>
52
+ # <Title>The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science &amp; Information Processing)</Title>
53
+ # </ItemAttributes>
54
+ # </Item>
55
+ # </Items>
56
+ # </ItemLookupResponse>
57
+
58
+ # is_valid is part of the api and says whether the request was valid
59
+ lookup.is_valid
60
+ => true
61
+
62
+ lookup.response.items.size
63
+ => 1
64
+
65
+ lookup.response.items.first
66
+ => #<AmazonProductAdvertisingApi::Item:0x139154c @detail_page_url="http://www.amazon.co.uk/Art-Computer-Programming-Information-Processing/dp/0201485419%3FSubscriptionId%3D<your api key>%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0201485419", @item_attributes=#<AmazonProductAdvertisingApi::Container:0x1391448 @manufacturer="Addison Wesley", @product_group="Book", @title="The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science &amp; Information Processing)", @author="Donald E. Knuth">, @asin="0201485419">
67
+
68
+ lookup.response.items.first.asin
69
+ => "0201485419"
70
+
71
+ # ItemAttributes is a container element, doesn't return a string
72
+ lookup.response.items.first.item_attributes
73
+ => #<AmazonProductAdvertisingApi::Container:0x1391448 @manufacturer="Addison Wesley", @product_group="Book", @title="The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science &amp; Information Processing)", @author="Donald E. Knuth">
74
+
75
+ lookup.response.items.first.item_attributes.title
76
+ => "The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science &amp; Information Processing)"
77
+
78
+ lookup.response.items.first.item_attributes.author
79
+ => "Donald E. Knuth"
80
+
81
+ # If you need to debug, you can access all the xml data easily
82
+ lookup.raw_data
83
+ => <XML string, straight from the request body>
84
+
85
+ lookup.hpricot_data
86
+ => <the xml data, having been fed into Hpricot>
87
+
88
+ # And also the request uri
89
+ lookup.request_uri
90
+ => #<URI::HTTP:0x1c7021c URL:http://ecs.amazonaws.co.uk/onca/xml?Service=AWSECommerceService&Operation=ItemLookup&ItemId=0201485419&AWSAccessKeyId=%3Cinsert%20api%20key%20here%3E>
91
+
92
+
93
+ Error handling - invalid request credentials, url form, etc
94
+ ###########################################################
95
+ AmazonProductAdvertisingApi::Base.access_key_id = "<an invalid id>"
96
+ lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("0201485419")
97
+ lookup.run
98
+ => false
99
+
100
+ lookup.is_valid
101
+ => false
102
+
103
+ lookup.errors
104
+ => <an array of error objects>
105
+
106
+ lookup.errors.first.code
107
+ => AWS.InvalidParameterValue
108
+
109
+ lookup.errors.first.message
110
+ => <an invalid id> is not a valid value for AWSAccessKeyId. Please change this value and retry your request.
111
+
112
+
113
+ Error handling - invalid request credentials, url form, etc
114
+ ###########################################################
115
+ AmazonProductAdvertisingApi::Base.access_key_id = "<valid api key>"
116
+ lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("<invalid asin>")
117
+ lookup.run
118
+ => false
119
+
120
+ # is_valid returns true as the request was well formed, it just doesn't have any valid data to return
121
+ lookup.is_valid
122
+ => true
123
+
124
+ lookup.errors.first.code
125
+ => AWS.InvalidParameterValue
126
+
127
+ lookup.errors.first.message
128
+ => <an invalid asin> is not a valid value for ItemId. Please change this value and retry your request.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jon Gilbraith, CompletelyNovel Ltd
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.
@@ -0,0 +1,99 @@
1
+ = amazon-product-advertising-api
2
+
3
+ == Introduction
4
+ A nice rubyish interface to the Amazon Product Advertising API, formerly
5
+ known as the Associates Web Service and before that the Amazon E-Commerce
6
+ Service.
7
+
8
+ The basic design philosophy is the 'Principle of least surprise', so it's:
9
+ - a dsl that should feel familiar to ruby folk.
10
+ - structured generally like the API.
11
+
12
+ == Prerequisites
13
+ It's assumed you're reasonably familiar with Ruby as a language, and the
14
+ typical coding patterns and styles employed by the community.
15
+
16
+ It's also assumed that you're familiar with the API. If not go RTFM for a bit,
17
+ it's all pretty straight forward.
18
+
19
+ == Overview
20
+ As mentioned before, the idea behind this is to model the API so that once
21
+ you're familiar with this library and the API, you'll find this an easy way to
22
+ navigate around it. You should then be able to just refer to the lists of
23
+ Operations, Response Groups and Response Elements in the appendices for all
24
+ you need to know.
25
+
26
+ You initiate requests by instantiating classes representing api Operations and
27
+ named the same way, i.e. ItemSearch, ItemLookup, etc. The request parameters
28
+ are represented by attributes on those objects and can be manipulated as you
29
+ wish. If a parameter is required it'll be required or defaulted on
30
+ initialization, but everywhere else we defer to the defaults provided by the
31
+ api.
32
+
33
+ Once setup, you call 'run' on the operation which will send the request and
34
+ return a response object (and after that also available as operation.response).
35
+
36
+ The response has a bit less structure than the request operations. There is no
37
+ representation for Response Groups as beyond one level deep there is no clear
38
+ pattern for how returned XML is structured (at least not without going down the
39
+ route trying to define custom handling for each of them). So, instead we group
40
+ the Operations into broad groups which return the same collections of data.
41
+ ItemSearch, ItemLookup and SimilarityLookup all return a Items container
42
+ element with one or more Item tag in it, etc.
43
+
44
+ Everything beyond one level deep is done with a recursive sweep instantiating
45
+ Element objects as we go.
46
+
47
+ == Things to know
48
+ - Amazon uses a lot of Camel case in their XML, but to suit Ruby / Rails
49
+ conventions attributes, etc are converted to underscore style.
50
+ - One gotcha - Response Elements can either be a simple value or a container
51
+ with more elements inside. We recognise this situation by simply comparing
52
+ the parent and child names (plural parent, singular child) though that isn't
53
+ always the pattern so we also look for multiple children of the same name.
54
+
55
+ That means that some situations might creep through and not work as expected.
56
+ If the parent and child don't follow the same naming convention and there is
57
+ also only one child element you'd find the child would be defined as
58
+ parent.child rather than parent.children.first.
59
+
60
+ This is a bug and will have to be addressed (probably by making a list of
61
+ container elements to check against).
62
+
63
+ == Having problems?
64
+ After running the request uri, returned raw xml and hpricot data will be
65
+ available in operation.raw_data and operation.hpricot_data which should tell
66
+ you all you need to know about the response.
67
+
68
+ == Test coverage
69
+ There aren't any tests at the moment but I'll try to add some.
70
+
71
+ For the most part there isn't really all that much to test though as it's all
72
+ largely just making http requests, however the XML parsing is quite convoluted
73
+ so some tests would be good there to make sure the parsing is following the
74
+ intended rules.
75
+
76
+
77
+ = TODO
78
+ - Implement the rest of the Operations (Cart*, Customer*, Help, List*, Seller*,
79
+ Tag*, Transaction* and Vehicle*).
80
+ - Implement batch and multiple operation requests, abstracted away from the
81
+ user within the dsl.
82
+ - Some sort of internal caching mechanism.
83
+ - Ability to bind to different IP addresses, in order to circumvent API rate
84
+ limiting.
85
+
86
+
87
+ = Obtaining
88
+ The main repository is at github but there is also a rubyforge project.
89
+
90
+ - https://github.com/completelynovel/amazon-product-advertising-api/tree
91
+ - http://amazon-pa-api.rubyforge.org/
92
+
93
+
94
+ = Credits
95
+ Created by Jon Gilbraith, jon@completelynovel.com, while working with
96
+ CompletelyNovel.
97
+
98
+ Contents of support.rb are basically taken and adapted from Rails'
99
+ ActiveSupport.
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Generate documentation for the amazon-product-advertising-api gem.'
6
+ Rake::RDocTask.new(:rdoc) do |rdoc|
7
+ rdoc.rdoc_dir = 'rdoc'
8
+ rdoc.title = 'AmazonProductAdvertisingApi'
9
+ rdoc.options << '--line-numbers' << '--inline-source'
10
+ rdoc.rdoc_files.include('README.rdoc')
11
+ rdoc.rdoc_files.include('lib/**/*.rb')
12
+ end
13
+
14
+ begin
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gemspec|
17
+ gemspec.name = "amazon-product-advertising-api"
18
+ gemspec.summary = "A nice rubyish interface to the Amazon Product Advertising API."
19
+ gemspec.email = "jon@completelynovel.com"
20
+ gemspec.homepage = "http://github.com/completelynovel/amazon-product-advertising-api"
21
+ gemspec.description = "A nice rubyish interface to the Amazon Product Advertising API, formerly known as the Associates Web Service and before that the Amazon E-Commerce Service."
22
+ gemspec.authors = ["Jon Gilbraith"]
23
+ gemspec.add_dependency("hpricot")
24
+ gemspec.add_dependency("ruby-hmac")
25
+ end
26
+ rescue LoadError
27
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
28
+ end
29
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{amazon-product-advertising-api-prezjordan}
5
+ s.version = "0.2.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jon Gilbraith", "Jordan Scales"]
9
+ s.date = %q{2009-07-30}
10
+ s.description = %q{A nice rubyish interface to the Amazon Product Advertising API (MODIFIED to handle ResponseGroup), formerly known as the Associates Web Service and before that the Amazon E-Commerce Service.}
11
+ s.email = %q{jon@completelynovel.com}
12
+ s.extra_rdoc_files = [
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "EXAMPLE.txt",
18
+ "MIT-LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "amazon-product-advertising-api.gemspec",
23
+ "lib/amazon_product_advertising_api.rb",
24
+ "lib/amazon_product_advertising_api/base.rb",
25
+ "lib/amazon_product_advertising_api/operations/base.rb",
26
+ "lib/amazon_product_advertising_api/operations/browse_node.rb",
27
+ "lib/amazon_product_advertising_api/operations/item.rb",
28
+ "lib/amazon_product_advertising_api/response_elements.rb",
29
+ "lib/amazon_product_advertising_api/support.rb"
30
+ ]
31
+ s.has_rdoc = true
32
+ s.homepage = %q{http://github.com/prezjordan/amazon-product-advertising-api}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.1}
36
+ s.summary = %q{A nice rubyish interface to the Amazon Product Advertising API. (MODIFIED to handle ResponseGroup)}
37
+
38
+ if s.respond_to? :specification_version then
39
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
40
+ s.specification_version = 2
41
+
42
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<hpricot>, [">= 0"])
44
+ s.add_runtime_dependency(%q<ruby-hmac>, [">= 0"])
45
+ else
46
+ s.add_dependency(%q<hpricot>, [">= 0"])
47
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
48
+ end
49
+ else
50
+ s.add_dependency(%q<hpricot>, [">= 0"])
51
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ require 'net/http'
2
+ require 'cgi'
3
+ require 'hpricot'
4
+ require 'time'
5
+ require 'hmac'
6
+ require 'hmac-sha2'
7
+ require 'base64'
8
+
9
+ require 'amazon_product_advertising_api/support'
10
+
11
+ Class.send(:include, AmazonProductAdvertisingApi::CoreExtensions::Class)
12
+ String.send(:include, AmazonProductAdvertisingApi::CoreExtensions::String)
13
+
14
+ require 'amazon_product_advertising_api/base'
15
+ require 'amazon_product_advertising_api/response_elements'
16
+ require 'amazon_product_advertising_api/operations/base'
17
+ require 'amazon_product_advertising_api/operations/browse_node'
18
+ require 'amazon_product_advertising_api/operations/item'
@@ -0,0 +1,24 @@
1
+ module AmazonProductAdvertisingApi #:nodoc:
2
+
3
+ # This is the main base class where you define your config data. Setup like so:
4
+ # AmazonProductAdvertisingApi.base.access_key_id = <your Amazon AccessKeyId>
5
+ # AmazonProductAdvertisingApi.base.secret_access_key = <your Amazon SecretAccessKey>
6
+ #
7
+ # You also setup your Associates codes for different regions here (the right one is supplied)
8
+ # based on what region you are requesting for.
9
+ # AmazonProductAdvertisingApi.base.associate_ids.ca = <your associates CA Affiliate key>
10
+ # AmazonProductAdvertisingApi.base.associate_ids.uk = <your associates UK Affiliate key>
11
+ class Base
12
+
13
+ cattr_accessor :access_key_id
14
+
15
+ cattr_accessor :secret_access_key
16
+
17
+ cattr_accessor :response_group
18
+
19
+ cattr_accessor :associate_ids
20
+ @@associate_ids = Struct.new(:ca, :de, :fr, :jp, :uk, :us).new
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,184 @@
1
+ module AmazonProductAdvertisingApi #:nodoc:
2
+ module Operations #:nodoc:
3
+ module Base #:nodoc:
4
+
5
+ # This is the parent class of any Operations performed.
6
+ #
7
+ # Each class should have a constant defined called REQUEST_PARAMETERS which contains the available parameters
8
+ # as defined in the API docs. Each class should also override the parse method with a custom version to suit the
9
+ # particular pattern of XML returned for it.
10
+ #
11
+ # Subclasses should also override initialize to take any parameters the API defines as required, and follow the pattern
12
+ # of the region being the last parameter.
13
+ #
14
+ # Before doing that though it should call super back to this one.
15
+ class Request
16
+
17
+ # String - Amazon API calls can be sent to any of 6 regions, so this defines which one.
18
+ # It'll also use this data to pick the right Associates key to use.
19
+ attr_accessor :region
20
+
21
+ # String - The name of the Operation you want to perform, i.e. ItemSearch, ItemLookup, etc.
22
+ attr_accessor :operation
23
+
24
+ # String - This stores the request that gets sent to Amazon (for investigation if you want to look under the covers).
25
+ attr_accessor :request_uri
26
+
27
+ # String - This stores the raw data of the request response (again, for investigation if you want to look under the covers).
28
+ attr_accessor :raw_data
29
+
30
+ # Hpricot - This stores the raw data of the request response should you feel the need / desire to do some parsing yourself.
31
+ attr_accessor :hpricot_data
32
+
33
+ # Element - This is the root of the structure that the lib assembles from the data when parsed.
34
+ attr_accessor :response
35
+
36
+ # Boolean - All responses have a field saying whether the request was valid. Note that this refers to the format of the request, etc
37
+ # and not errors with the request parameters, etc. I.e. a lookup for an item that doesn't exsist is still valid.
38
+ attr_accessor :is_valid
39
+
40
+ # Array of Struct - Any errors will be added to this attribute and each has an attribute of code or message.
41
+ attr_accessor :errors
42
+
43
+ SERVICE_URLS = {
44
+ :us => 'http://ecs.amazonaws.com/onca/xml',
45
+ :uk => 'http://ecs.amazonaws.co.uk/onca/xml',
46
+ :ca => 'http://ecs.amazonaws.ca/onca/xml',
47
+ :de => 'http://ecs.amazonaws.de/onca/xml',
48
+ :jp => 'http://webservices.amazon.co.jp/onca/xml',
49
+ :fr => 'http://ecs.amazonaws.fr/onca/xml'
50
+ }
51
+
52
+ API_VERSION = "2009-03-31"
53
+
54
+ # hacky :(
55
+ def initialize(arg1, arg2)
56
+ self.response = AmazonProductAdvertisingApi::Operations::Base::Element.new
57
+ self.errors = []
58
+ end
59
+
60
+ # This takes care of building request, performing it, storing the results, checking for errors then parsing the data (if the request was valid).
61
+ def query_amazon(params)
62
+ request_params = {}
63
+ request_params["Service"] = "AWSECommerceService"
64
+ request_params["SignatureVersion"] = 2
65
+ request_params["SignatureMethod"] = "HmacSHA256"
66
+ request_params["Timestamp"] = Time.now.gmtime.iso8601
67
+ request_params["AWSAccessKeyId"] = AmazonProductAdvertisingApi::Base.access_key_id
68
+ request_params["Operation"] = self.operation
69
+ request_params["AssociateTag"] = AmazonProductAdvertisingApi::Base.associate_ids.send(self.region) unless AmazonProductAdvertisingApi::Base.associate_ids.send(self.region).nil?
70
+ request_params["Version"] = API_VERSION
71
+ request_params["ResponseGroup"] = AmazonProductAdvertisingApi::Base.response_group.try(:join, ',')
72
+
73
+ request_params.merge!(params)
74
+
75
+ # Process all params - make sure they're all strings, camelize and escape (where appropriate)
76
+ request_params = request_params.collect { |var, val| [var.to_s.camelize, val.to_s] }
77
+ request_params = request_params.collect { |var, val| [var, CGI::escape(val).gsub('+', '%20')] }
78
+
79
+ # Assemble into a full request string
80
+ unsigned_uri = URI.parse("#{SERVICE_URLS[self.region]}?#{request_params.sort { |a, b| a[0] <=> b[0] }.collect { |var, val| var + "=" + val }.join("&")}")
81
+
82
+ # Generate hmac
83
+ hmac = HMAC::SHA256.new(AmazonProductAdvertisingApi::Base.secret_access_key)
84
+ hmac.update("GET\n#{unsigned_uri.host}\n#{unsigned_uri.path}\n#{unsigned_uri.query}")
85
+
86
+ self.request_uri = URI.parse("#{unsigned_uri}&Signature=#{CGI::escape(Base64.encode64(hmac.digest).chomp)}")
87
+
88
+ result = Net::HTTP::get_response(self.request_uri)
89
+ raise("Error connecting to Amazon - #{result.to_s}") if !result.kind_of?(Net::HTTPSuccess)
90
+
91
+ # Store away the raw data for debugging or if more direct access is required
92
+ self.raw_data = result.body
93
+ self.hpricot_data = Hpricot.XML(self.raw_data)
94
+
95
+ # Now parse the xml and build out the reponse elements
96
+ self.is_valid = self.hpricot_data.at(:IsValid).inner_html == "True"
97
+
98
+ self.parse
99
+
100
+ # is_valid only refers to the request, so we could still have errors - check and parse if present
101
+ if !self.hpricot_data.at(:Errors).nil?
102
+ self.hpricot_data.at(:Errors).search(:Error).each do |error|
103
+ self.errors << Struct.new(:code, :message).new(error.at(:Code).inner_html, error.at(:Message).inner_html)
104
+ end
105
+ end
106
+
107
+ # Return false if it's not a valid request, otherwise return the response
108
+ self.is_valid ? self.response : false
109
+ end
110
+
111
+ # The parse method of a request should be overwritted by any subclasses to account for different patterns in the XML.
112
+ def parse
113
+ raise "This should be being overridden by it's subclass to provide custom parsing for the particular operation concerned."
114
+ end
115
+
116
+ # Launches the request's query to Amazon (via query_amazon).
117
+ def run
118
+ self.query_amazon(params)
119
+ end
120
+
121
+ private
122
+ # When passed an hpricot element it returns true or false based on whether this item is thought to be inside a Container Element.
123
+ # It does this in the rather crude way of seeing if the parent's name is the pluralized form of it's own, or it is one of
124
+ # several with the same name. This isn't 100% fool proof so I think at some point using a definitive list of the container elements
125
+ # would be a better way to go.
126
+ #
127
+ # The pluralisation could also do with something a bit more sophisticated.
128
+ def parent_a_container?(hpricot_element)
129
+ hpricot_element.parent.name == hpricot_element.name + "s" || hpricot_element.parent.search("> #{hpricot_element.name}").size > 1
130
+ end
131
+
132
+ end
133
+
134
+ # XML data that is returned by Amazon gets built into a tree of nodes, which are made up of instances of this class.
135
+ # They represent the 'response element' entity within the API docs.
136
+ #
137
+ # As well as various having attributes it can also contain a collection and behave like an array.
138
+ class Element < Array
139
+
140
+ attr_reader :attributes
141
+
142
+ def initialize
143
+ @attributes = {}
144
+ end
145
+
146
+ # Defines a new accessor on the element and if supplied assigns that attribute a value.
147
+ def add_element(name, value = nil)
148
+ name = name.underscore
149
+
150
+ @attributes[name.to_s] = value
151
+
152
+ self.instance_eval %{
153
+ def self.#{name}
154
+ @#{name}
155
+ end
156
+ def self.#{name}=(value)
157
+ @#{name} ||= value
158
+ end
159
+ }
160
+
161
+ if !value.nil?
162
+ value = value.to_s if value.is_a?(Symbol)
163
+ self.send("#{name}=", value)
164
+ end
165
+
166
+ # Return the element
167
+ self.instance_eval("self.#{name}")
168
+ end
169
+
170
+ def method_missing(method, *args)
171
+ return super unless RESPONSE_ELEMENTS.include?(method.to_sym)
172
+
173
+ if CONTAINER_RESPONSE_ELEMENTS.include?(method.to_sym)
174
+ self.class.new
175
+ else
176
+ nil
177
+ end
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+ end
184
+ end