aws_sdb_bare 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +88 -0
- data/Rakefile +64 -0
- data/VERSION.yml +4 -0
- data/aws_sdb_bare.gemspec +58 -0
- data/lib/aws_sdb_bare/request.rb +318 -0
- data/lib/aws_sdb_bare/response.rb +163 -0
- data/lib/aws_sdb_bare.rb +7 -0
- data/test/aws_sdb_request_template_test.rb +75 -0
- data/test/aws_sdb_request_test.rb +45 -0
- data/test/aws_sdb_response_test.rb +111 -0
- data/test/samples/responses.rb +27 -0
- data/test/test_helper.rb +27 -0
- metadata +74 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Paolo Negri
|
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,88 @@
|
|
1
|
+
= aws_sdb_bare
|
2
|
+
|
3
|
+
AwsSdb Bare is a ruby gem intended to provide a basic modular interface to use
|
4
|
+
the SimpleDB service provided by Amazon.
|
5
|
+
|
6
|
+
== Concept
|
7
|
+
|
8
|
+
The aim of the project is to not commit to any http handling strategy and to
|
9
|
+
not enforce any response parsing solution by default.
|
10
|
+
|
11
|
+
The idea behind this project is to provide a basic layer on top of which
|
12
|
+
more high level libraries can be built.
|
13
|
+
|
14
|
+
The library provides XML response parsing through Hpricot or Nokogiri to
|
15
|
+
run the test against a specific parser
|
16
|
+
|
17
|
+
rake nokogiri test
|
18
|
+
|
19
|
+
Or
|
20
|
+
|
21
|
+
rake hpricot test
|
22
|
+
|
23
|
+
|
24
|
+
== Project sources
|
25
|
+
|
26
|
+
the project is hosted on github http://github.com/hungryblank/aws_sdb_bare
|
27
|
+
|
28
|
+
blog posts explaining the usage can be found on http://assertbuggy.blogspot.com
|
29
|
+
|
30
|
+
== Support and bug tracking
|
31
|
+
|
32
|
+
issues can be posted on github http://github.com/hungryblank/aws_sdb_bare
|
33
|
+
forking and patch proposal throguh github are encouraged
|
34
|
+
|
35
|
+
== Installation
|
36
|
+
|
37
|
+
sudo gem install hungryblank-aws_sdb_bare -s http://gems.github.com
|
38
|
+
|
39
|
+
== Usage - Requests
|
40
|
+
|
41
|
+
The most basic primitive is the request, as a sample here's how a ListDomains
|
42
|
+
request is generated
|
43
|
+
|
44
|
+
require 'rubygems'
|
45
|
+
require 'aws_sdb_bare'
|
46
|
+
|
47
|
+
ENV['AMAZON_ACCESS_KEY_ID'] = 'your_amazon_access_key'
|
48
|
+
ENV['AMAZON_SECRET_ACCESS_KEY'] = 'your_amazon_secret'
|
49
|
+
|
50
|
+
#create the request object
|
51
|
+
request = AwsSdb::Request::Base.new('GET', 'Action' => 'ListDomains')
|
52
|
+
|
53
|
+
#using curl we can send the request as http get and print out the xml
|
54
|
+
#response with the domains under your account
|
55
|
+
puts `curl #{request.uri}`
|
56
|
+
|
57
|
+
The gem provides classes to create directly all the requests listed in
|
58
|
+
Amazon's 2007 11 07 specs so you can do
|
59
|
+
|
60
|
+
request = AwsSdb::Request::ListDomains.new
|
61
|
+
|
62
|
+
Instead of using the Base class like the previous example.
|
63
|
+
|
64
|
+
Please refer to the gem rdocs and to the amazon documentation to see
|
65
|
+
a list of all the available requests and their parameters
|
66
|
+
|
67
|
+
== Usage - Responses
|
68
|
+
|
69
|
+
To parse a response you simply call AwsSdb::Response.parse(xml_doc) example:
|
70
|
+
|
71
|
+
require 'open-uri'
|
72
|
+
require 'aws_sdb_bare'
|
73
|
+
#the following line could require nokogiri instead
|
74
|
+
require 'hpricot'
|
75
|
+
|
76
|
+
request = AwsSdb::Request::ListDomains.new
|
77
|
+
xml_response = open request.uri
|
78
|
+
response = AwsSdb::Response.parse(xml_response)
|
79
|
+
puts response.domains
|
80
|
+
puts response.metadata.box_usage
|
81
|
+
|
82
|
+
== Documentation
|
83
|
+
|
84
|
+
Rdoc are available at http://rdoc.elastastic.com/aws_sdb_bare
|
85
|
+
|
86
|
+
== Copyright
|
87
|
+
|
88
|
+
Copyright (c) 2009 Paolo Negri. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "aws_sdb_bare"
|
8
|
+
gem.summary = %Q{Amazon AWS SDB SimpleDB client library, low level and http lib agnostic}
|
9
|
+
gem.email = "hungryblank@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/hungryblank/aws_sdb_bare"
|
11
|
+
gem.authors = ["Paolo Negri"]
|
12
|
+
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/*_test.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
task :hpricot do
|
43
|
+
ENV['PARSER'] = 'hpricot'
|
44
|
+
end
|
45
|
+
|
46
|
+
task :nokogiri do
|
47
|
+
ENV['PARSER'] = 'nokogiri'
|
48
|
+
end
|
49
|
+
|
50
|
+
require 'rake/rdoctask'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
if File.exist?('VERSION.yml')
|
53
|
+
config = YAML.load(File.read('VERSION.yml'))
|
54
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
55
|
+
else
|
56
|
+
version = ""
|
57
|
+
end
|
58
|
+
|
59
|
+
rdoc.rdoc_dir = 'rdoc'
|
60
|
+
rdoc.title = "aws_sdb_bare #{version}"
|
61
|
+
rdoc.rdoc_files.include('README*')
|
62
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
+
end
|
64
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{aws_sdb_bare}
|
8
|
+
s.version = "1.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Paolo Negri"]
|
12
|
+
s.date = %q{2010-02-20}
|
13
|
+
s.email = %q{hungryblank@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION.yml",
|
25
|
+
"aws_sdb_bare.gemspec",
|
26
|
+
"lib/aws_sdb_bare.rb",
|
27
|
+
"lib/aws_sdb_bare/request.rb",
|
28
|
+
"lib/aws_sdb_bare/response.rb",
|
29
|
+
"test/aws_sdb_request_template_test.rb",
|
30
|
+
"test/aws_sdb_request_test.rb",
|
31
|
+
"test/aws_sdb_response_test.rb",
|
32
|
+
"test/samples/responses.rb",
|
33
|
+
"test/test_helper.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/hungryblank/aws_sdb_bare}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.5}
|
39
|
+
s.summary = %q{Amazon AWS SDB SimpleDB client library, low level and http lib agnostic}
|
40
|
+
s.test_files = [
|
41
|
+
"test/test_helper.rb",
|
42
|
+
"test/aws_sdb_request_test.rb",
|
43
|
+
"test/samples/responses.rb",
|
44
|
+
"test/aws_sdb_response_test.rb",
|
45
|
+
"test/aws_sdb_request_template_test.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
else
|
54
|
+
end
|
55
|
+
else
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,318 @@
|
|
1
|
+
module AwsSdb
|
2
|
+
|
3
|
+
module Request
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
HOST = 'sdb.amazonaws.com'
|
8
|
+
|
9
|
+
attr_accessor :account, :secret, :params
|
10
|
+
|
11
|
+
def initialize(method, params, opts={})
|
12
|
+
@account = opts[:account] || ENV['AMAZON_ACCESS_KEY_ID']
|
13
|
+
@secret = opts[:secret] || ENV['AMAZON_SECRET_ACCESS_KEY']
|
14
|
+
raise <<-end_msg unless @account && @secret
|
15
|
+
Amazon AWS account or access key not defined
|
16
|
+
Please pass {:account => 'your account', :secret => 'your secret'}
|
17
|
+
as a last argument or define the following environment variables
|
18
|
+
ENV['AMAZON_ACCESS_KEY_ID']
|
19
|
+
ENV['AMAZON_SECRET_ACCESS_KEY']
|
20
|
+
end_msg
|
21
|
+
@method = method
|
22
|
+
@params = params
|
23
|
+
add_req_data_to_params
|
24
|
+
end
|
25
|
+
|
26
|
+
#Hostname for the request
|
27
|
+
def host
|
28
|
+
HOST
|
29
|
+
end
|
30
|
+
|
31
|
+
#Uri path
|
32
|
+
def path
|
33
|
+
'/'
|
34
|
+
end
|
35
|
+
|
36
|
+
#The full uri for the request, it takes the protocol as argument
|
37
|
+
def uri(protocol = 'http')
|
38
|
+
"#{protocol}://" + host + path + '?' + uri_query
|
39
|
+
end
|
40
|
+
|
41
|
+
#Only the query part of the uri
|
42
|
+
def uri_query
|
43
|
+
params_query + '&Signature=' + signature
|
44
|
+
end
|
45
|
+
|
46
|
+
alias :to_s :uri
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def add_req_data_to_params
|
51
|
+
@params.update({'Version' => '2007-11-07',
|
52
|
+
'SignatureVersion' => '2',
|
53
|
+
'SignatureMethod' => 'HmacSHA256',
|
54
|
+
'AWSAccessKeyId' => @account,
|
55
|
+
'Timestamp' => Time.now.gmtime.iso8601})
|
56
|
+
end
|
57
|
+
|
58
|
+
def params_query
|
59
|
+
@params_query ||= @params.keys.sort.map do |key|
|
60
|
+
key + '=' + escape(@params[key].to_s)
|
61
|
+
end.join('&')
|
62
|
+
end
|
63
|
+
|
64
|
+
def data_to_sign
|
65
|
+
[@method, host, path, params_query].join("\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
def signature
|
69
|
+
@signature ||= begin
|
70
|
+
digest = OpenSSL::Digest::Digest.new('sha256')
|
71
|
+
hmac = OpenSSL::HMAC.digest(digest, @secret, data_to_sign)
|
72
|
+
escape(Base64.encode64(hmac).strip)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def escape(string)
|
77
|
+
URI.escape(string, /[^-_.~a-zA-Z\d]/)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
#Is the parent of all the request templates, defines some methods
|
83
|
+
class Template
|
84
|
+
|
85
|
+
class << self
|
86
|
+
|
87
|
+
#Generates method to allow shorter expressions in parameter
|
88
|
+
#definition takes an hash
|
89
|
+
#
|
90
|
+
# shortcuts({'LongUrlQueryParams' => :abbr})
|
91
|
+
#
|
92
|
+
#will cause :abbr params to be expanded in LongUrlQueryParams
|
93
|
+
def shortcuts(shortcuts_hash)
|
94
|
+
shortcuts_hash.each do |full_key, shortcut|
|
95
|
+
define_method("#{shortcut}=") do |value|
|
96
|
+
@params[full_key] = value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
#takes the params for the request and an optional options hash
|
104
|
+
#params for the request are request specific, options accepted are only
|
105
|
+
#AWS credentials in form of
|
106
|
+
#
|
107
|
+
# {:account => 'your account', :secret => 'your secret'}
|
108
|
+
def initialize(params={}, opts={})
|
109
|
+
@params, @opts = params, opts
|
110
|
+
end
|
111
|
+
|
112
|
+
def token=(value)
|
113
|
+
@params['NextToken'] = value
|
114
|
+
end
|
115
|
+
|
116
|
+
def attributes=(attributes)
|
117
|
+
attributes.each_with_index do |attribute, index|
|
118
|
+
@params["AttributeName.#{index}"] = attribute.to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def request
|
123
|
+
expand!
|
124
|
+
@request ||= begin
|
125
|
+
Base.new('GET', @params.merge('Action' => self.class.to_s.split(':')[-1]), @opts)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
[:to_s, :uri, :uri_query].each do |method|
|
130
|
+
define_method(method) do
|
131
|
+
request.send(method)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def expand!
|
138
|
+
@params.keys.each do |key|
|
139
|
+
send("#{key}=", @params.delete(key)) if respond_to?("#{key}=")
|
140
|
+
end
|
141
|
+
@params
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
#List the domains
|
147
|
+
#
|
148
|
+
# ListDomains.new({:max => 4}, {:account => 'account', :secret => 'secret'})
|
149
|
+
#
|
150
|
+
#Shortcut in params:
|
151
|
+
#
|
152
|
+
# :max - the maximum number of domain that can be returned
|
153
|
+
# :token - the token for follow up requests
|
154
|
+
class ListDomains < Template
|
155
|
+
shortcuts({'MaxNumberOfDomains' => :max})
|
156
|
+
end
|
157
|
+
|
158
|
+
#Creates a new domain
|
159
|
+
#
|
160
|
+
# CreateDomain.new({:name => 'my_domain'})
|
161
|
+
#
|
162
|
+
#Shortcut in params:
|
163
|
+
#
|
164
|
+
# :name - the name for the new domain
|
165
|
+
class CreateDomain < Template
|
166
|
+
shortcuts({'DomainName' => :name})
|
167
|
+
end
|
168
|
+
|
169
|
+
#Deletes a domain
|
170
|
+
#
|
171
|
+
# DeleteDomain.new({:name => 'my_domain'})
|
172
|
+
#
|
173
|
+
#Shortcut in params:
|
174
|
+
#
|
175
|
+
# :name - the name for domain
|
176
|
+
class DeleteDomain < Template
|
177
|
+
shortcuts({'DomainName' => :name})
|
178
|
+
end
|
179
|
+
|
180
|
+
#Provides informations for a domain
|
181
|
+
#
|
182
|
+
# DomainMetadata.new({:name => 'my_domain'})
|
183
|
+
#
|
184
|
+
#Shortcut in params:
|
185
|
+
#
|
186
|
+
# :name - the name for domain
|
187
|
+
class DomainMetadata < Template
|
188
|
+
shortcuts({'DomainName' => :name})
|
189
|
+
end
|
190
|
+
|
191
|
+
#Get the attributes for an item
|
192
|
+
#
|
193
|
+
# GetAttributes.new({:name => 'my_item', :attributes => [:first, :second]})
|
194
|
+
#
|
195
|
+
#Shortcut in params:
|
196
|
+
#
|
197
|
+
# :name - the item name
|
198
|
+
# :attributes - the list of the attributes to fetch, if not specified
|
199
|
+
# fetches all the attributes
|
200
|
+
class GetAttributes < Template
|
201
|
+
shortcuts({'ItemName' => :name, 'DomainName' => :domain})
|
202
|
+
end
|
203
|
+
|
204
|
+
#Add attributes to an item or create a new item whith the given attributes
|
205
|
+
#
|
206
|
+
# req = PutAttributes.new({:name => 'my_item'})
|
207
|
+
# req.attributes = {:color => :black, :shape => {:value => :square, :replace => true}}
|
208
|
+
#
|
209
|
+
#Note: in this request the value 'black' will be added to other color
|
210
|
+
#values if they already exist, while the shape value 'square' will replace
|
211
|
+
#pre existing values of shape
|
212
|
+
#the request, adding the :attributes to the params hash in the new method
|
213
|
+
#would have the same effect
|
214
|
+
#
|
215
|
+
#Note: in the sample above attributes are defined after initializing
|
216
|
+
#the request, adding the :attributes to the params hash in the new method
|
217
|
+
#would have the same effect
|
218
|
+
class PutAttributes < Template
|
219
|
+
|
220
|
+
attr_accessor :attributes
|
221
|
+
|
222
|
+
class << self
|
223
|
+
|
224
|
+
#multiple PutAttributes requests can be batched in one BatchPutAttributes
|
225
|
+
#just define the single PutAttributes and then pass them as args to
|
226
|
+
#the PutAttributes::batch method and you'll be returned the batched
|
227
|
+
#request.
|
228
|
+
#At the moment of writing the maximum number of requests you can batch
|
229
|
+
#are 25
|
230
|
+
def batch(*requests)
|
231
|
+
items_params = requests.map { |r| r.send(:expand!) }
|
232
|
+
first_request = requests.first.request
|
233
|
+
account = first_request.account
|
234
|
+
secret = first_request.secret
|
235
|
+
batch_params = {}
|
236
|
+
batch_params['DomainName'] ||= first_request.params['DomainName']
|
237
|
+
items_params.each_with_index do |params, index|
|
238
|
+
params.keys.each do |key|
|
239
|
+
batch_params["Item.#{index.to_s + '.' + key}"] = params[key] if key =~ /^ItemName|Attribute/
|
240
|
+
end
|
241
|
+
end
|
242
|
+
BatchPutAttributes.new(batch_params, {:account => account, :secret => secret})
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
shortcuts({'ItemName' => :name, 'DomainName' => :domain})
|
247
|
+
|
248
|
+
def expand_attributes!
|
249
|
+
index = 0
|
250
|
+
@attributes.keys.each do |attr_name|
|
251
|
+
replace = nil
|
252
|
+
values = case @attributes[attr_name]
|
253
|
+
when Hash
|
254
|
+
replace = @attributes[attr_name][:replace].to_s
|
255
|
+
@attributes[attr_name][:value].to_s
|
256
|
+
else
|
257
|
+
@attributes[attr_name].to_s
|
258
|
+
end
|
259
|
+
values = [values] unless values.is_a?(Array)
|
260
|
+
values.each do |value|
|
261
|
+
@params["Attribute.#{index}.Replace"] = replace if replace
|
262
|
+
@params["Attribute.#{index}.Name"] = attr_name.to_s
|
263
|
+
@params["Attribute.#{index}.Value"] = value
|
264
|
+
index += 1
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
def expand!
|
272
|
+
super
|
273
|
+
expand_attributes!
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
class BatchPutAttributes < Template
|
279
|
+
shortcuts({'DomainName' => :domain})
|
280
|
+
end
|
281
|
+
|
282
|
+
#delete some attributes from the given item, if no attributes
|
283
|
+
#are provided the whole item is deleted
|
284
|
+
#
|
285
|
+
# DeleteAttributes.new(:domain => 'test_domain', :name => 'item_1'
|
286
|
+
# :attributes => [:color, :shape])
|
287
|
+
class DeleteAttributes < Template
|
288
|
+
shortcuts({'ItemName' => :name, 'DomainName' => :domain})
|
289
|
+
end
|
290
|
+
|
291
|
+
#execute a select statement
|
292
|
+
# Select.new(:query => "SELECT * FROM test_domain")
|
293
|
+
class Select < Template
|
294
|
+
shortcuts({'SelectExpression' => :query})
|
295
|
+
end
|
296
|
+
|
297
|
+
#execute a query statement returns the array of the item names
|
298
|
+
#satisfying the statment
|
299
|
+
#
|
300
|
+
# Query.new(:domain => 'test_domain', :query => "['shape' = 'square']")
|
301
|
+
class Query < Template
|
302
|
+
shortcuts({'DomainName' => :domain,
|
303
|
+
'QueryExpression' => :query,
|
304
|
+
'MaxNumberOfItems' => :max})
|
305
|
+
end
|
306
|
+
|
307
|
+
#execute a query with attributes statement returns the items
|
308
|
+
#satisfying statement
|
309
|
+
#
|
310
|
+
# QueryWithAttributes.new(:domain => 'test_domain', :query => "['shape' = 'square']")
|
311
|
+
class QueryWithAttributes < Template
|
312
|
+
shortcuts({'DomainName' => :domain,
|
313
|
+
'QueryExpression' => :query,
|
314
|
+
'MaxNumberOfItems' => :max})
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module AwsSdb
|
2
|
+
|
3
|
+
module Response
|
4
|
+
|
5
|
+
MEMBERS = {}
|
6
|
+
|
7
|
+
class UnknownResponse < ArgumentError
|
8
|
+
end
|
9
|
+
|
10
|
+
#returns an instance of the response, the class of the response is determined
|
11
|
+
#by the response content
|
12
|
+
def self.parse(doc)
|
13
|
+
@@parser ||= Hpricot if defined?(Hpricot)
|
14
|
+
@@parser ||= Nokogiri if defined?(Nokogiri)
|
15
|
+
parsed_doc = @@parser.XML(doc)
|
16
|
+
begin
|
17
|
+
MEMBERS[parsed_doc.root.name].new(parsed_doc)
|
18
|
+
rescue
|
19
|
+
raise UnknownResponse, "unkwonw response #{doc.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module XmlUtils
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def xml_attr_reader(*attributes)
|
28
|
+
attributes.each do |attr|
|
29
|
+
define_method(attr.gsub(/[A-Z]+/,'\1_\0').downcase[1..-1]) do
|
30
|
+
value = @doc.at(attr).inner_html
|
31
|
+
value = yield(value) if block_given?
|
32
|
+
value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Base
|
40
|
+
|
41
|
+
class << self
|
42
|
+
|
43
|
+
def register
|
44
|
+
Response::MEMBERS[to_s.split(':').pop + 'Response'] = self
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_items
|
48
|
+
define_method :items do
|
49
|
+
@items ||= begin
|
50
|
+
(@doc / 'Item').inject({}) do |items_hash, item|
|
51
|
+
items_hash[item.at('Name').inner_html] = attributes_hash(item)
|
52
|
+
items_hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(doc)
|
61
|
+
@doc = doc
|
62
|
+
end
|
63
|
+
|
64
|
+
def metadata
|
65
|
+
@metadata ||= Metadata.new(@doc.at('ResponseMetadata'))
|
66
|
+
end
|
67
|
+
|
68
|
+
#the token to get the following chunk of response in case the response
|
69
|
+
#is broken in parts due to the size limit being exceeded
|
70
|
+
def token
|
71
|
+
if token_element = @doc.at('NextToken')
|
72
|
+
token_element.inner_html
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def attributes_hash(element)
|
79
|
+
attr_hash = Hash.new() { |h, k| h[k] = [] }
|
80
|
+
(element / 'Attribute').each do |attribute|
|
81
|
+
attr_hash[attribute.at('Name').inner_html] << attribute.at('Value').inner_html
|
82
|
+
end
|
83
|
+
attr_hash
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
%w(BatchPutAttributesResponse)
|
89
|
+
|
90
|
+
class GetAttributes < Base
|
91
|
+
|
92
|
+
register
|
93
|
+
|
94
|
+
#hash of item attributes
|
95
|
+
def attributes
|
96
|
+
@attributes ||= attributes_hash(@doc)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class Select < Base
|
102
|
+
register
|
103
|
+
has_items
|
104
|
+
end
|
105
|
+
|
106
|
+
class QueryWithAttributes < Base
|
107
|
+
register
|
108
|
+
has_items
|
109
|
+
end
|
110
|
+
|
111
|
+
class Query < Base
|
112
|
+
|
113
|
+
register
|
114
|
+
|
115
|
+
def item_names
|
116
|
+
@items ||= (@doc / 'ItemName').map { |i| i.inner_html }
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
class ListDomains < Base
|
122
|
+
|
123
|
+
register
|
124
|
+
|
125
|
+
def domains
|
126
|
+
@domains ||= (@doc / 'DomainName').map { |d| d.inner_html }
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
class DomainMetadata < Base
|
132
|
+
|
133
|
+
extend XmlUtils
|
134
|
+
register
|
135
|
+
xml_attr_reader('ItemCount', 'ItemNamesSizeBytes', 'AttributeNameCount',
|
136
|
+
'AttributeNamesSizeBytes', 'AttributeValueCount',
|
137
|
+
'AttributeValuesSizeBytes', 'Timestamp') { |value| value.to_i }
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
%w(CreateDomain DeleteDomain PutAttributes BatchPutAttributes DeleteAttributes).each do |klass|
|
142
|
+
module_eval <<-eval_end
|
143
|
+
class #{klass} < Base
|
144
|
+
register
|
145
|
+
end
|
146
|
+
eval_end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Metadata
|
150
|
+
|
151
|
+
extend XmlUtils
|
152
|
+
|
153
|
+
xml_attr_reader 'RequestId', 'BoxUsage'
|
154
|
+
|
155
|
+
def initialize(doc)
|
156
|
+
@doc = doc
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/lib/aws_sdb_bare.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class AwsSdbRequestTemplateTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include AwsSdb::Request
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ENV['AMAZON_ACCESS_KEY_ID'] = 'a'
|
9
|
+
ENV['AMAZON_SECRET_ACCESS_KEY'] = 'a'
|
10
|
+
end
|
11
|
+
|
12
|
+
should "add the token using shortcut" do
|
13
|
+
req = Template.new({'a' => '1', :token => 'this_is_the_token'})
|
14
|
+
assert_attribute({'NextToken' => 'this_is_the_token'}, req.uri_query)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "add attributes and number them" do
|
18
|
+
req = Template.new({'a' => 1, :attributes => [:attr_1, :attr_2]}, {:account => 'account', :secret => 'secret'})
|
19
|
+
assert_attribute({'AttributeName.0' => 'attr_1'}, req.uri_query)
|
20
|
+
assert_attribute({'AttributeName.1' => 'attr_2'}, req.uri_query)
|
21
|
+
end
|
22
|
+
|
23
|
+
should "generate all the parameters for a ListDomains" do
|
24
|
+
req = ListDomains.new({:max => 4}, {:account => 'account', :secret => 'secret'})
|
25
|
+
assert_attribute({'Action' => 'ListDomains'}, req.uri_query)
|
26
|
+
assert_attribute({'MaxNumberOfDomains' => '4'}, req.uri_query)
|
27
|
+
end
|
28
|
+
|
29
|
+
should "generate all the parameters for an CreateDomain" do
|
30
|
+
req = CreateDomain.new({:name => 'my_domain'}, {:account => 'account', :secret => 'secret'})
|
31
|
+
assert_attribute({'Action' => 'CreateDomain'}, req.uri_query)
|
32
|
+
assert_attribute({'DomainName' => 'my_domain'}, req.uri_query)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "generate all the parameters for a DeleteDomain" do
|
36
|
+
req = DeleteDomain.new({:name => 'my_domain'}, {:account => 'account', :secret => 'secret'})
|
37
|
+
assert_attribute({'Action' => 'DeleteDomain'}, req.uri_query)
|
38
|
+
assert_attribute({'DomainName' => 'my_domain'}, req.uri_query)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "generate all the parameters for a GetAttributes" do
|
42
|
+
req = GetAttributes.new({:name => 'my_item', :attributes => [:first, :second]}, {:account => 'account', :secret => 'secret'})
|
43
|
+
assert_attribute({'Action' => 'GetAttributes'}, req.uri_query)
|
44
|
+
assert_attribute({'ItemName' => 'my_item'}, req.uri_query)
|
45
|
+
end
|
46
|
+
|
47
|
+
should "generate all the parameters for a PutAttributes" do
|
48
|
+
req = PutAttributes.new({:name => 'my_domain', :name => 'my_item'},
|
49
|
+
{:account => 'account', :secret => 'secret'})
|
50
|
+
req.attributes = {:color => :black, :shape => {:value => :square, :replace => true}}
|
51
|
+
assert_attribute({'Action' => 'PutAttributes'}, req.uri_query)
|
52
|
+
assert_attribute({'ItemName' => 'my_item'}, req.uri_query)
|
53
|
+
shape_index = query_hash(req.uri_query)['Attribute.0.Name'] == 'shape' ? 0 : 1
|
54
|
+
assert_attribute({"Attribute.#{shape_index}.Name" => 'shape'}, req.uri_query)
|
55
|
+
assert_attribute({"Attribute.#{shape_index}.Value" => 'square'}, req.uri_query)
|
56
|
+
assert_attribute({"Attribute.#{shape_index}.Replace" => 'true'}, req.uri_query)
|
57
|
+
color_index = (shape_index - 1).abs
|
58
|
+
assert_attribute({"Attribute.#{color_index}.Name" => 'color'}, req.uri_query)
|
59
|
+
assert_attribute({"Attribute.#{color_index}.Value" => 'black'}, req.uri_query)
|
60
|
+
assert_attribute({"Attribute.#{color_index}.Replace" => nil}, req.uri_query)
|
61
|
+
end
|
62
|
+
|
63
|
+
should "generate all the parameters for a Select" do
|
64
|
+
req = Select.new({:query => 'Select * from my_domain'}, {:account => 'account', :secret => 'secret'})
|
65
|
+
assert_attribute({'Action' => 'Select'}, req.uri_query)
|
66
|
+
assert_attribute({'SelectExpression' => /^Select/}, req.uri_query)
|
67
|
+
end
|
68
|
+
|
69
|
+
should "generate all the parameters for a Query" do
|
70
|
+
req = Query.new({:query => "['Color' = 'blue']", :domain => 'my_domain', :max => 10 }, {:account => 'account', :secret => 'secret'})
|
71
|
+
assert_attribute({'Action' => 'Query'}, req.uri_query)
|
72
|
+
assert_attribute({'QueryExpression' => /Color.*blue/}, req.uri_query)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class AwsSdbRequestTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include AwsSdb::Request
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ENV['AMAZON_ACCESS_KEY_ID'] = 'a'
|
9
|
+
ENV['AMAZON_SECRET_ACCESS_KEY'] = 'a'
|
10
|
+
end
|
11
|
+
|
12
|
+
should "prepend the verb and host to the params for signing" do
|
13
|
+
req = Base.new('GET', 'Action' => 'ListDomains')
|
14
|
+
assert_match /^GET\n#{AwsSdb::Request::Base::HOST}/, req.send(:data_to_sign)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "build the query and sort alphabetically the parameters" do
|
18
|
+
req = Base.new('GET', 'b' => '2', 'x' => '1', 'a' => '1')
|
19
|
+
assert_match /a=1&b=2&x=1/, req.send(:params_query)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "url encode the param values" do
|
23
|
+
safe_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"
|
24
|
+
some_unsafe_chars = "+*!@£$%^&*()"
|
25
|
+
req = Base.new('GET', 'SafeChars' => safe_chars, 'UnsafeChars' => some_unsafe_chars)
|
26
|
+
assert_attribute({'SafeChars' => safe_chars}, req.send(:params_query))
|
27
|
+
assert_attribute({'UnsafeChars' => '%2B%2A%21%40%C2%A3%24%25%5E%26%2A%28%29'}, req.send(:params_query))
|
28
|
+
end
|
29
|
+
|
30
|
+
should "append the signature method to the request" do
|
31
|
+
req = Base.new('GET', {'a' => '1'}, {:account => 'account', :secret => 'secret'})
|
32
|
+
assert_match /&SignatureMethod=HmacSHA256&SignatureVersion=2/, req.uri_query
|
33
|
+
end
|
34
|
+
|
35
|
+
should "add the actual signature" do
|
36
|
+
req = Base.new('GET', {'a' => '1'}, {:account => 'account', :secret => 'secret'})
|
37
|
+
assert_attribute({'Signature' => /^[0-9a-zA-Z%]+$/}, req.uri_query)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "build a correct uri" do
|
41
|
+
req = Base.new('GET', {'a' => '1'}, {:account => 'account', :secret => 'secret'})
|
42
|
+
assert URI.parse(req.uri)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), 'samples/responses')
|
3
|
+
|
4
|
+
ENV['PARSER'] ||= 'hpricot'
|
5
|
+
|
6
|
+
require ENV['PARSER']
|
7
|
+
|
8
|
+
class AwsSdbRequestTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
include AwsSdb
|
11
|
+
|
12
|
+
context "parsing a GetAttributes" do
|
13
|
+
|
14
|
+
should "generate the attributes hash" do
|
15
|
+
response = Response.parse(SAMPLE_RESPONSES['GetAttributes'])
|
16
|
+
assert_same_elements ["20:45:22", "22:50:25", "22:51:06", "20:44:30", "20:44:19",
|
17
|
+
"22:50:55", "22:49:55", "20:30:08", "22:51:02"], response.attributes['last_visit']
|
18
|
+
assert_equal ['62'], response.attributes['code']
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context "parsing a Select" do
|
24
|
+
|
25
|
+
should "generate the items hash" do
|
26
|
+
response = Response.parse(SAMPLE_RESPONSES['Select'])
|
27
|
+
assert_same_elements ["bar81", "bar83", "bar98", "my_entity5"], response.items.keys
|
28
|
+
end
|
29
|
+
|
30
|
+
should "generate the attributes for each item" do
|
31
|
+
response = Response.parse(SAMPLE_RESPONSES['Select'])
|
32
|
+
assert_equal({"code" => ["48"], "foo" => ["value"]}, response.items['bar98'])
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
context "parsing a Query" do
|
38
|
+
|
39
|
+
should "generate the item names array" do
|
40
|
+
response = Response.parse(SAMPLE_RESPONSES['Query'])
|
41
|
+
assert_same_elements ["bar81", "bar83", "bar98", "my_entity5"], response.item_names
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
context "parsing a QueryWithAttributes" do
|
47
|
+
|
48
|
+
should "generate the items hash" do
|
49
|
+
response = Response.parse(SAMPLE_RESPONSES['QueryWithAttributes'])
|
50
|
+
assert_equal({"code" => ["48"], "foo" => ["value"]}, response.items['bar98'])
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "parsing a response with token" do
|
56
|
+
|
57
|
+
should "find and return the token" do
|
58
|
+
response = Response.parse(SAMPLE_RESPONSES['ResWithToken'])
|
59
|
+
assert_equal "rO0ABXNyACdjb20uYW1hem9uLnNkcy5RdWVyeVByb2Nlc3Nvci5Nb3JlVG9rZW7racXLnINNqwMA\nCkkAFGluaXRpYWxDb25qdW5jdEluZGV4WgAOaXNQYWdlQm91bmRhcnlKAAxsYXN0RW50aXR5SURa\nAApscnFFbmFibGVkSQAPcXVlcnlDb21wbGV4aXR5SgATcXVlcnlTdHJpbmdDaGVja3N1bUkACnVu\naW9uSW5kZXhaAA11c2VRdWVyeUluZGV4TAASbGFzdEF0dHJpYnV0ZVZhbHVldAASTGphdmEvbGFu\nZy9TdHJpbmc7TAAJc29ydE9yZGVydAAvTGNvbS9hbWF6b24vc2RzL1F1ZXJ5UHJvY2Vzc29yL1F1\nZXJ5JFNvcnRPcmRlcjt4cAAAAAAATZbLYbTOwAAAAAAAAAAAAAArd5WtAAAAAAF0AAI0M35yAC1j\nb20uYW1hem9uLnNkcy5RdWVyeVByb2Nlc3Nvci5RdWVyeSRTb3J0T3JkZXIAAAAAAAAAABIAAHhy\nAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AAlBU0NFTkRJTkd4", response.token
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
context "parsing ListDomains" do
|
65
|
+
|
66
|
+
should "return the list of domains" do
|
67
|
+
response = Response.parse(SAMPLE_RESPONSES['ListDomains'])
|
68
|
+
assert_same_elements ['myapp_development', 'myapp_production', 'myapp_test', 'test_domain'], response.domains
|
69
|
+
end
|
70
|
+
%w(ListDomains DomainMetadata)
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "parsing a request" do
|
75
|
+
|
76
|
+
should "return the metadata" do
|
77
|
+
response = Response.parse(SAMPLE_RESPONSES['ListDomains'])
|
78
|
+
assert_equal '0.0000071759', response.metadata.box_usage
|
79
|
+
end
|
80
|
+
%w(ListDomains DomainMetadata)
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "parsing DomainMetadata" do
|
85
|
+
|
86
|
+
should "provide direct acceess to attributes" do
|
87
|
+
response = Response.parse(SAMPLE_RESPONSES['DomainMetadata'])
|
88
|
+
assert_equal 282, response.item_count
|
89
|
+
assert_equal 7237, response.item_names_size_bytes
|
90
|
+
assert_equal 13, response.attribute_name_count
|
91
|
+
assert_equal 92, response.attribute_names_size_bytes
|
92
|
+
assert_equal 1095, response.attribute_value_count
|
93
|
+
assert_equal 5689, response.attribute_values_size_bytes
|
94
|
+
assert_equal 1241131496, response.timestamp
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
context "parsing simple responses" do
|
100
|
+
|
101
|
+
%w(CreateDomain DeleteDomain PutAttributes BatchPutAttributes DeleteAttributes).each do |simple_request|
|
102
|
+
|
103
|
+
should "recognize #{simple_request}" do
|
104
|
+
assert_nothing_raised { Response.parse(SAMPLE_RESPONSES[simple_request]) }
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
SAMPLE_RESPONSES = {
|
2
|
+
'GetAttributes' =>
|
3
|
+
"<?xml version=\"1.0\"?>\n<GetAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><GetAttributesResult><Attribute><Name>code</Name><Value>62</Value></Attribute><Attribute><Name>last_visit</Name><Value>20:30:08</Value></Attribute><Attribute><Name>last_visit</Name><Value>20:44:19</Value></Attribute><Attribute><Name>last_visit</Name><Value>20:44:30</Value></Attribute><Attribute><Name>last_visit</Name><Value>20:45:22</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:49:55</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:50:25</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:50:55</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:51:02</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:51:06</Value></Attribute></GetAttributesResult><ResponseMetadata><RequestId>0d5da91d-e2c4-1de0-7106-cc1d08bf0e8f</RequestId><BoxUsage>0.0000093282</BoxUsage></ResponseMetadata></GetAttributesResponse>",
|
4
|
+
'Select' =>
|
5
|
+
"<?xml version=\"1.0\"?>\n<SelectResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><SelectResult><Item><Name>my_entity5</Name><Attribute><Name>last_visit</Name><Value>12:58:53</Value></Attribute><Attribute><Name>last_visit</Name><Value>13:13:05</Value></Attribute><Attribute><Name>last_visit</Name><Value>13:13:22</Value></Attribute><Attribute><Name>last_visit</Name><Value>21:45:29</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:11:18</Value></Attribute><Attribute><Name>code</Name><Value>16</Value></Attribute></Item><Item><Name>bar83</Name><Attribute><Name>code</Name><Value>43</Value></Attribute></Item><Item><Name>bar81</Name><Attribute><Name>foo</Name><Value>value</Value></Attribute><Attribute><Name>code</Name><Value>44</Value></Attribute></Item><Item><Name>bar98</Name><Attribute><Name>foo</Name><Value>value</Value></Attribute><Attribute><Name>code</Name><Value>48</Value></Attribute></Item></SelectResult><ResponseMetadata><RequestId>0201e630-dea7-8c4e-88ad-d76cc297388c</RequestId><BoxUsage>0.0000503747</BoxUsage></ResponseMetadata></SelectResponse>",
|
6
|
+
'Query' =>
|
7
|
+
"<?xml version=\"1.0\"?>\n<QueryResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><QueryResult><ItemName>my_entity5</ItemName><ItemName>bar83</ItemName><ItemName>bar81</ItemName><ItemName>bar98</ItemName></QueryResult><ResponseMetadata><RequestId>48de733e-b08d-7aeb-b855-e08e376da538</RequestId><BoxUsage>0.0000140320</BoxUsage></ResponseMetadata></QueryResponse>",
|
8
|
+
'QueryWithAttributes' =>
|
9
|
+
"<?xml version=\"1.0\"?>\n<QueryWithAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><QueryWithAttributesResult><Item><Name>my_entity5</Name><Attribute><Name>last_visit</Name><Value>12:58:53</Value></Attribute><Attribute><Name>last_visit</Name><Value>13:13:05</Value></Attribute><Attribute><Name>last_visit</Name><Value>13:13:22</Value></Attribute><Attribute><Name>last_visit</Name><Value>21:45:29</Value></Attribute><Attribute><Name>last_visit</Name><Value>22:11:18</Value></Attribute><Attribute><Name>code</Name><Value>16</Value></Attribute></Item><Item><Name>bar83</Name><Attribute><Name>code</Name><Value>43</Value></Attribute></Item><Item><Name>bar81</Name><Attribute><Name>foo</Name><Value>value</Value></Attribute><Attribute><Name>code</Name><Value>44</Value></Attribute></Item><Item><Name>bar98</Name><Attribute><Name>foo</Name><Value>value</Value></Attribute><Attribute><Name>code</Name><Value>48</Value></Attribute></Item></QueryWithAttributesResult><ResponseMetadata><RequestId>d849662b-b02b-f0b8-45d2-e13685d10026</RequestId><BoxUsage>0.0000503747</BoxUsage></ResponseMetadata></QueryWithAttributesResponse>",
|
10
|
+
'CreateDomain' =>
|
11
|
+
"<?xml version=\"1.0\"?>\n<CreateDomainResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ResponseMetadata><RequestId>606100ad-9b94-9e55-09f5-21de864591f2</RequestId><BoxUsage>0.0055590278</BoxUsage></ResponseMetadata></CreateDomainResponse>",
|
12
|
+
'DeleteDomain' =>
|
13
|
+
"<?xml version=\"1.0\"?>\n<DeleteDomainResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ResponseMetadata><RequestId>ba116912-8684-41fd-b60f-87dda3a6d301</RequestId><BoxUsage>0.0055590278</BoxUsage></ResponseMetadata></DeleteDomainResponse>",
|
14
|
+
'PutAttributes' =>
|
15
|
+
"<?xml version=\"1.0\"?>\n<PutAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ResponseMetadata><RequestId>9583937f-4127-20c8-d34c-bd44a676afad</RequestId><BoxUsage>0.0000219923</BoxUsage></ResponseMetadata></PutAttributesResponse>",
|
16
|
+
'BatchPutAttributes' =>
|
17
|
+
"<?xml version=\"1.0\"?>\n<BatchPutAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ResponseMetadata><RequestId>ab252f42-e5f5-dca5-9aa9-f695cc1fb113</RequestId><BoxUsage>0.0000307892</BoxUsage></ResponseMetadata></BatchPutAttributesResponse>",
|
18
|
+
'DeleteAttributes' =>
|
19
|
+
"<?xml version=\"1.0\"?>\n<DeleteAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ResponseMetadata><RequestId>cafe7877-cc29-8c41-fa68-f14101665b3a</RequestId><BoxUsage>0.0000219907</BoxUsage></ResponseMetadata></DeleteAttributesResponse>",
|
20
|
+
'ResWithToken' =>
|
21
|
+
"<?xml version=\"1.0\"?>\n<QueryWithAttributesResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><QueryWithAttributesResult><Item><Name>bar83</Name><Attribute><Name>code</Name><Value>43</Value></Attribute></Item><NextToken>rO0ABXNyACdjb20uYW1hem9uLnNkcy5RdWVyeVByb2Nlc3Nvci5Nb3JlVG9rZW7racXLnINNqwMA\nCkkAFGluaXRpYWxDb25qdW5jdEluZGV4WgAOaXNQYWdlQm91bmRhcnlKAAxsYXN0RW50aXR5SURa\nAApscnFFbmFibGVkSQAPcXVlcnlDb21wbGV4aXR5SgATcXVlcnlTdHJpbmdDaGVja3N1bUkACnVu\naW9uSW5kZXhaAA11c2VRdWVyeUluZGV4TAASbGFzdEF0dHJpYnV0ZVZhbHVldAASTGphdmEvbGFu\nZy9TdHJpbmc7TAAJc29ydE9yZGVydAAvTGNvbS9hbWF6b24vc2RzL1F1ZXJ5UHJvY2Vzc29yL1F1\nZXJ5JFNvcnRPcmRlcjt4cAAAAAAATZbLYbTOwAAAAAAAAAAAAAArd5WtAAAAAAF0AAI0M35yAC1j\nb20uYW1hem9uLnNkcy5RdWVyeVByb2Nlc3Nvci5RdWVyeSRTb3J0T3JkZXIAAAAAAAAAABIAAHhy\nAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AAlBU0NFTkRJTkd4</NextToken></QueryWithAttributesResult><ResponseMetadata><RequestId>5f1cd665-843a-17df-bb07-8c3a3bbbcbdd</RequestId><BoxUsage>0.0000228636</BoxUsage></ResponseMetadata></QueryWithAttributesResponse>",
|
22
|
+
'DomainMetadata' =>
|
23
|
+
"<?xml version=\"1.0\"?>\n<DomainMetadataResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><DomainMetadataResult><ItemCount>282</ItemCount><ItemNamesSizeBytes>7237</ItemNamesSizeBytes><AttributeNameCount>13</AttributeNameCount><AttributeNamesSizeBytes>92</AttributeNamesSizeBytes><AttributeValueCount>1095</AttributeValueCount><AttributeValuesSizeBytes>5689</AttributeValuesSizeBytes><Timestamp>1241131496</Timestamp></DomainMetadataResult><ResponseMetadata><RequestId>fdca49d6-5d0c-1006-5571-648d29e7f183</RequestId><BoxUsage>0.0000071759</BoxUsage></ResponseMetadata></DomainMetadataResponse>",
|
24
|
+
'ListDomains' =>
|
25
|
+
"<?xml version=\"1.0\"?>\n<ListDomainsResponse xmlns=\"http://sdb.amazonaws.com/doc/2007-11-07/\"><ListDomainsResult><DomainName>myapp_development</DomainName><DomainName>myapp_production</DomainName><DomainName>myapp_test</DomainName><DomainName>test_domain</DomainName></ListDomainsResult><ResponseMetadata><RequestId>f79cd7ab-5f1e-4a32-5a90-de630ed679b8</RequestId><BoxUsage>0.0000071759</BoxUsage></ResponseMetadata></ListDomainsResponse>"
|
26
|
+
|
27
|
+
}
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'aws_sdb_bare'
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
|
11
|
+
def assert_attribute(attribute_hash, query_string)
|
12
|
+
query_hash = query_hash(query_string)
|
13
|
+
attribute_hash.keys.each do |key|
|
14
|
+
method = attribute_hash[key].is_a?(Regexp) ? :assert_match : :assert_equal
|
15
|
+
send(method, attribute_hash[key], query_hash[key])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def query_hash(query_string)
|
20
|
+
query_hash = query_string.split('&').inject({}) do |hash, token|
|
21
|
+
key, value = *token.split('=')
|
22
|
+
hash[key] = value
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws_sdb_bare
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paolo Negri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-20 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: hungryblank@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION.yml
|
32
|
+
- aws_sdb_bare.gemspec
|
33
|
+
- lib/aws_sdb_bare.rb
|
34
|
+
- lib/aws_sdb_bare/request.rb
|
35
|
+
- lib/aws_sdb_bare/response.rb
|
36
|
+
- test/aws_sdb_request_template_test.rb
|
37
|
+
- test/aws_sdb_request_test.rb
|
38
|
+
- test/aws_sdb_response_test.rb
|
39
|
+
- test/samples/responses.rb
|
40
|
+
- test/test_helper.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/hungryblank/aws_sdb_bare
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.5
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Amazon AWS SDB SimpleDB client library, low level and http lib agnostic
|
69
|
+
test_files:
|
70
|
+
- test/test_helper.rb
|
71
|
+
- test/aws_sdb_request_test.rb
|
72
|
+
- test/samples/responses.rb
|
73
|
+
- test/aws_sdb_response_test.rb
|
74
|
+
- test/aws_sdb_request_template_test.rb
|