aboisvert_aws 3.0.0

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.
Files changed (78) hide show
  1. data/History.txt +329 -0
  2. data/Manifest.txt +61 -0
  3. data/README.txt +163 -0
  4. data/Rakefile +130 -0
  5. data/lib/acf/right_acf_interface.rb +549 -0
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +229 -0
  9. data/lib/acw/right_acw_interface.rb +248 -0
  10. data/lib/as/right_as_interface.rb +698 -0
  11. data/lib/awsbase/benchmark_fix.rb +39 -0
  12. data/lib/awsbase/right_awsbase.rb +1343 -0
  13. data/lib/awsbase/support.rb +35 -0
  14. data/lib/awsbase/version.rb +9 -0
  15. data/lib/ec2/right_ec2.rb +541 -0
  16. data/lib/ec2/right_ec2_ebs.rb +481 -0
  17. data/lib/ec2/right_ec2_images.rb +444 -0
  18. data/lib/ec2/right_ec2_instances.rb +788 -0
  19. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  20. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  21. data/lib/ec2/right_ec2_reserved_instances.rb +184 -0
  22. data/lib/ec2/right_ec2_security_groups.rb +491 -0
  23. data/lib/ec2/right_ec2_spot_instances.rb +422 -0
  24. data/lib/ec2/right_ec2_tags.rb +139 -0
  25. data/lib/ec2/right_ec2_vpc.rb +590 -0
  26. data/lib/ec2/right_ec2_vpc2.rb +381 -0
  27. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  28. data/lib/elb/right_elb_interface.rb +573 -0
  29. data/lib/emr/right_emr_interface.rb +727 -0
  30. data/lib/iam/right_iam_access_keys.rb +71 -0
  31. data/lib/iam/right_iam_groups.rb +195 -0
  32. data/lib/iam/right_iam_interface.rb +341 -0
  33. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  34. data/lib/iam/right_iam_users.rb +251 -0
  35. data/lib/rds/right_rds_interface.rb +1384 -0
  36. data/lib/right_aws.rb +86 -0
  37. data/lib/route_53/right_route_53_interface.rb +640 -0
  38. data/lib/s3/right_s3.rb +1138 -0
  39. data/lib/s3/right_s3_interface.rb +1278 -0
  40. data/lib/sdb/active_sdb.rb +1107 -0
  41. data/lib/sdb/right_sdb_interface.rb +762 -0
  42. data/lib/sns/right_sns_interface.rb +286 -0
  43. data/lib/sqs/right_sqs.rb +387 -0
  44. data/lib/sqs/right_sqs_gen2.rb +342 -0
  45. data/lib/sqs/right_sqs_gen2_interface.rb +523 -0
  46. data/lib/sqs/right_sqs_interface.rb +593 -0
  47. data/right_aws.gemspec +90 -0
  48. data/test/README.mdown +39 -0
  49. data/test/acf/test_helper.rb +2 -0
  50. data/test/acf/test_right_acf.rb +138 -0
  51. data/test/awsbase/test_helper.rb +2 -0
  52. data/test/awsbase/test_right_awsbase.rb +11 -0
  53. data/test/ec2/test_helper.rb +2 -0
  54. data/test/ec2/test_right_ec2.rb +107 -0
  55. data/test/elb/test_helper.rb +2 -0
  56. data/test/elb/test_right_elb.rb +43 -0
  57. data/test/http_connection.rb +87 -0
  58. data/test/rds/test_helper.rb +2 -0
  59. data/test/rds/test_right_rds.rb +120 -0
  60. data/test/route_53/fixtures/a_record.xml +18 -0
  61. data/test/route_53/fixtures/alias_record.xml +18 -0
  62. data/test/route_53/test_helper.rb +2 -0
  63. data/test/route_53/test_right_route_53.rb +141 -0
  64. data/test/s3/test_helper.rb +2 -0
  65. data/test/s3/test_right_s3.rb +528 -0
  66. data/test/s3/test_right_s3_stubbed.rb +97 -0
  67. data/test/sdb/test_active_sdb.rb +357 -0
  68. data/test/sdb/test_batch_put_attributes.rb +54 -0
  69. data/test/sdb/test_helper.rb +3 -0
  70. data/test/sdb/test_right_sdb.rb +253 -0
  71. data/test/sns/test_helper.rb +2 -0
  72. data/test/sns/test_right_sns.rb +153 -0
  73. data/test/sqs/test_helper.rb +2 -0
  74. data/test/sqs/test_right_sqs.rb +285 -0
  75. data/test/sqs/test_right_sqs_gen2.rb +264 -0
  76. data/test/test_credentials.rb +37 -0
  77. data/test/ts_right_aws.rb +15 -0
  78. metadata +257 -0
@@ -0,0 +1,1107 @@
1
+ #
2
+ # Copyright (c) 2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ # = RightAws::ActiveSdb -- RightScale SDB interface (alpha release)
27
+ # The RightAws::ActiveSdb class provides a complete interface to Amazon's Simple
28
+ # Database Service.
29
+ #
30
+ # ActiveSdb is in alpha and does not load by default with the rest of RightAws. You must use an additional require statement to load the ActiveSdb class. For example:
31
+ #
32
+ # require 'right_aws'
33
+ # require 'sdb/active_sdb'
34
+ #
35
+ # Simple ActiveSdb usage example:
36
+ #
37
+ # class Client < RightAws::ActiveSdb::Base
38
+ # end
39
+ #
40
+ # # connect to SDB
41
+ # RightAws::ActiveSdb.establish_connection
42
+ #
43
+ # # create domain
44
+ # Client.create_domain
45
+ #
46
+ # # create initial DB
47
+ # Client.create 'name' => 'Bush', 'country' => 'USA', 'gender' => 'male', 'expiration' => '2009', 'post' => 'president'
48
+ # Client.create 'name' => 'Putin', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2008', 'post' => 'president'
49
+ # Client.create 'name' => 'Medvedev', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2012', 'post' => 'president'
50
+ # Client.create 'name' => 'Mary', 'country' => 'USA', 'gender' => 'female', 'hobby' => ['patchwork', 'bundle jumping']
51
+ # Client.create 'name' => 'Mary', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']
52
+ # sandy_id = Client.create('name' => 'Sandy', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']).id
53
+ #
54
+ # # find all Bushes in USA
55
+ # Client.find(:all, :conditions => ["['name'=?] intersection ['country'=?]",'Bush','USA']).each do |client|
56
+ # client.reload
57
+ # puts client.attributes.inspect
58
+ # end
59
+ #
60
+ # # find all Maries through the world
61
+ # Client.find_all_by_name_and_gender('Mary','female').each do |mary|
62
+ # mary.reload
63
+ # puts "#{mary[:name]}, gender: #{mary[:gender]}, hobbies: #{mary[:hobby].join(',')}"
64
+ # end
65
+ #
66
+ # # find new russian president
67
+ # medvedev = Client.find_by_post_and_country_and_expiration('president','Russia','2012')
68
+ # if medvedev
69
+ # medvedev.reload
70
+ # medvedev.save_attributes('age' => '42', 'hobby' => 'Gazprom')
71
+ # end
72
+ #
73
+ # # retire old president
74
+ # Client.find_by_name('Putin').delete
75
+ #
76
+ # # Sandy disappointed in 'cooking' and decided to hide her 'gender' and 'country' ()
77
+ # sandy = Client.find(sandy_id)
78
+ # sandy.reload
79
+ # sandy.delete_values('hobby' => ['cooking'] )
80
+ # sandy.delete_attributes('country', 'gender')
81
+ #
82
+ # # remove domain
83
+ # Client.delete_domain
84
+ #
85
+ # # Dynamic attribute accessors
86
+ #
87
+ # class KdClient < RightAws::ActiveSdb::Base
88
+ # end
89
+ #
90
+ # client = KdClient.select(:all, :order => 'expiration').first
91
+ # pp client.attributes #=>
92
+ # {"name"=>["Putin"],
93
+ # "post"=>["president"],
94
+ # "country"=>["Russia"],
95
+ # "expiration"=>["2008"],
96
+ # "id"=>"376d2e00-75b0-11dd-9557-001bfc466dd7",
97
+ # "gender"=>["male"]}
98
+ #
99
+ # pp client.name #=> ["Putin"]
100
+ # pp client.country #=> ["Russia"]
101
+ # pp client.post #=> ["president"]
102
+ #
103
+ # # Columns and simple typecasting
104
+ #
105
+ # class Person < RightAws::ActiveSdb::Base
106
+ # columns do
107
+ # name
108
+ # email
109
+ # score :Integer
110
+ # is_active :Boolean
111
+ # registered_at :DateTime
112
+ # created_at :DateTime, :default => lambda{ Time.now }
113
+ # end
114
+ # end
115
+ # Person::create( :name => 'Yetta E. Andrews', :email => 'nulla.facilisis@metus.com', :score => 100, :is_active => true, :registered_at => Time.local(2000, 1, 1) )
116
+ #
117
+ # person = Person.find_by_email 'nulla.facilisis@metus.com'
118
+ # person.reload
119
+ #
120
+ # pp person.attributes #=>
121
+ # {"name"=>["Yetta E. Andrews"],
122
+ # "created_at"=>["2010-04-02T20:51:58+0400"],
123
+ # "id"=>"0ee24946-3e60-11df-9d4c-0025b37efad0",
124
+ # "registered_at"=>["2000-01-01T00:00:00+0300"],
125
+ # "is_active"=>["T"],
126
+ # "score"=>["100"],
127
+ # "email"=>["nulla.facilisis@metus.com"]}
128
+ # pp person.name #=> "Yetta E. Andrews"
129
+ # pp person.name.class #=> String
130
+ # pp person.registered_at.to_s #=> "2000-01-01T00:00:00+03:00"
131
+ # pp person.registered_at.class #=> DateTime
132
+ # pp person.is_active #=> true
133
+ # pp person.is_active.class #=> TrueClass
134
+ # pp person.score #=> 100
135
+ # pp person.score.class #=> Fixnum
136
+ # pp person.created_at.to_s #=> "2010-04-02T20:51:58+04:00"
137
+ #
138
+ class ActiveSdb
139
+
140
+ module ActiveSdbConnect
141
+ def connection
142
+ @connection || raise(ActiveSdbError.new('Connection to SDB is not established'))
143
+ end
144
+ # Create a new handle to an Sdb account. All handles share the same per process or per thread
145
+ # HTTP connection to Amazon Sdb. Each handle is for a specific account.
146
+ # The +params+ are passed through as-is to RightAws::SdbInterface.new
147
+ # Params:
148
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
149
+ # :port => 443 # Amazon service port: 80 or 443(default)
150
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
151
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
152
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
153
+ # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil')
154
+
155
+ def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
156
+ @connection = RightAws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params)
157
+ end
158
+ end
159
+
160
+ class ActiveSdbError < RuntimeError ; end
161
+
162
+ class << self
163
+ include ActiveSdbConnect
164
+
165
+ # Retreive a list of domains.
166
+ #
167
+ # put RightAws::ActiveSdb.domains #=> ['co-workers','family','friends','clients']
168
+ #
169
+ def domains
170
+ connection.list_domains[:domains]
171
+ end
172
+
173
+ # Create new domain.
174
+ # Raises no errors if the domain already exists.
175
+ #
176
+ # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
177
+ #
178
+ def create_domain(domain_name)
179
+ connection.create_domain(domain_name)
180
+ end
181
+
182
+ # Remove domain from SDB.
183
+ # Raises no errors if the domain does not exist.
184
+ #
185
+ # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41c6-91f4-3ed390a3d3b2", :box_usage=>"0.0055590001"}
186
+ #
187
+ def delete_domain(domain_name)
188
+ connection.delete_domain(domain_name)
189
+ end
190
+ end
191
+
192
+ class Base
193
+
194
+ class << self
195
+ include ActiveSdbConnect
196
+
197
+ # next_token value returned by last find: is useful to continue finding
198
+ attr_accessor :next_token
199
+
200
+ # Returns a RightAws::SdbInterface object
201
+ #
202
+ # class A < RightAws::ActiveSdb::Base
203
+ # end
204
+ #
205
+ # class B < RightAws::ActiveSdb::Base
206
+ # end
207
+ #
208
+ # class C < RightAws::ActiveSdb::Base
209
+ # end
210
+ #
211
+ # RightAws::ActiveSdb.establish_connection 'key_id_1', 'secret_key_1'
212
+ #
213
+ # C.establish_connection 'key_id_2', 'secret_key_2'
214
+ #
215
+ # # A and B uses the default connection, C - uses its own
216
+ # puts A.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
217
+ # puts B.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
218
+ # puts C.connection #=> #<RightAws::SdbInterface:0xb76d6ca0>
219
+ #
220
+ def connection
221
+ @connection || ActiveSdb::connection
222
+ end
223
+
224
+ @domain = nil
225
+
226
+ # Current domain name.
227
+ #
228
+ # # if 'ActiveSupport' is not loaded then class name converted to downcase
229
+ # class Client < RightAws::ActiveSdb::Base
230
+ # end
231
+ # puts Client.domain #=> 'client'
232
+ #
233
+ # # if 'ActiveSupport' is loaded then class name being tableized
234
+ # require 'activesupport'
235
+ # class Client < RightAws::ActiveSdb::Base
236
+ # end
237
+ # puts Client.domain #=> 'clients'
238
+ #
239
+ # # Explicit domain name definition
240
+ # class Client < RightAws::ActiveSdb::Base
241
+ # set_domain_name :foreign_clients
242
+ # end
243
+ # puts Client.domain #=> 'foreign_clients'
244
+ #
245
+ def domain
246
+ unless @domain
247
+ if defined? ActiveSupport::CoreExtensions::String::Inflections
248
+ @domain = name.tableize
249
+ else
250
+ @domain = name.downcase
251
+ end
252
+ end
253
+ @domain
254
+ end
255
+
256
+ # Change the default domain name to user defined.
257
+ #
258
+ # class Client < RightAws::ActiveSdb::Base
259
+ # set_domain_name :foreign_clients
260
+ # end
261
+ #
262
+ def set_domain_name(domain)
263
+ @domain = domain.to_s
264
+ end
265
+
266
+ # Create domain at SDB.
267
+ # Raises no errors if the domain already exists.
268
+ #
269
+ # class Client < RightAws::ActiveSdb::Base
270
+ # end
271
+ # Client.create_domain #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
272
+ #
273
+ def create_domain
274
+ connection.create_domain(domain)
275
+ end
276
+
277
+ # Remove domain from SDB.
278
+ # Raises no errors if the domain does not exist.
279
+ #
280
+ # class Client < RightAws::ActiveSdb::Base
281
+ # end
282
+ # Client.delete_domain #=> {:request_id=>"e14d90d3-0000-4898-9995-0de28cdda270", :box_usage=>"0.0055590278"}
283
+ #
284
+ def delete_domain
285
+ connection.delete_domain(domain)
286
+ end
287
+
288
+ def columns(&block)
289
+ @columns ||= ColumnSet.new
290
+ @columns.instance_eval(&block) if block
291
+ @columns
292
+ end
293
+
294
+ def column?(col_name)
295
+ columns.include?(col_name)
296
+ end
297
+
298
+ def type_of(col_name)
299
+ columns.type_of(col_name)
300
+ end
301
+
302
+ def serialize(attribute, value)
303
+ s = serialization_for_type(type_of(attribute))
304
+ s ? s.serialize(value) : value.to_s
305
+ end
306
+
307
+ def deserialize(attribute, value)
308
+ s = serialization_for_type(type_of(attribute))
309
+ s ? s.deserialize(value) : value
310
+ end
311
+
312
+ # Perform a find request.
313
+ #
314
+ # Single record:
315
+ #
316
+ # Client.find(:first)
317
+ # Client.find(:first, :conditions=> [ "['name'=?] intersection ['wife'=?]", "Jon", "Sandy"])
318
+ #
319
+ # Bunch of records:
320
+ #
321
+ # Client.find(:all)
322
+ # Client.find(:all, :limit => 10)
323
+ # Client.find(:all, :conditions=> [ "['name'=?] intersection ['girlfriend'=?]", "Jon", "Judy"])
324
+ # Client.find(:all, :conditions=> [ "['name'=?]", "Sandy"], :limit => 3)
325
+ #
326
+ # Records by ids:
327
+ #
328
+ # Client.find('1')
329
+ # Client.find('1234987b4583475347523948')
330
+ # Client.find('1','2','3','4', :conditions=> [ "['toys'=?]", "beer"])
331
+ #
332
+ # Find helpers: RightAws::ActiveSdb::Base.find_by_... and RightAws::ActiveSdb::Base.find_all_by_...
333
+ #
334
+ # Client.find_by_name('Matias Rust')
335
+ # Client.find_by_name_and_city('Putin','Moscow')
336
+ # Client.find_by_name_and_city_and_post('Medvedev','Moscow','president')
337
+ #
338
+ # Client.find_all_by_author('G.Bush jr')
339
+ # Client.find_all_by_age_and_gender_and_ethnicity('34','male','russian')
340
+ # Client.find_all_by_gender_and_country('male', 'Russia', :auto_load => true, :order => 'name desc')
341
+ #
342
+ # Returned records have to be +reloaded+ to access their attributes.
343
+ #
344
+ # item = Client.find_by_name('Cat') #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
345
+ # item.reload #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
346
+ #
347
+ # Continue listing:
348
+ # # initial listing
349
+ # Client.find(:all, :limit => 10)
350
+ # # continue listing
351
+ # begin
352
+ # Client.find(:all, :limit => 10, :next_token => Client.next_token)
353
+ # end while Client.next_token
354
+ #
355
+ # Sort oder:
356
+ # Client.find(:all, :order => 'gender')
357
+ # Client.find(:all, :order => 'name desc')
358
+ #
359
+ # Attributes auto load (be carefull - this may take lot of time for a huge bunch of records):
360
+ # Client.find(:first) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
361
+ # Client.find(:first, :auto_load => true) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
362
+ #
363
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingQuery.html
364
+ #
365
+ def find(*args)
366
+ options = args.last.is_a?(Hash) ? args.pop : {}
367
+ case args.first
368
+ when :all then find_every options
369
+ when :first then find_initial options
370
+ else find_from_ids args, options
371
+ end
372
+ end
373
+
374
+ # Perform a SQL-like select request.
375
+ #
376
+ # Single record:
377
+ #
378
+ # Client.select(:first)
379
+ # Client.select(:first, :conditions=> [ "name=? AND wife=?", "Jon", "Sandy"])
380
+ # Client.select(:first, :conditions=> { :name=>"Jon", :wife=>"Sandy" }, :select => :girfriends)
381
+ #
382
+ # Bunch of records:
383
+ #
384
+ # Client.select(:all)
385
+ # Client.select(:all, :limit => 10)
386
+ # Client.select(:all, :conditions=> [ "name=? AND 'girlfriend'=?", "Jon", "Judy"])
387
+ # Client.select(:all, :conditions=> { :name=>"Sandy" }, :limit => 3)
388
+ #
389
+ # Records by ids:
390
+ #
391
+ # Client.select('1')
392
+ # Client.select('1234987b4583475347523948')
393
+ # Client.select('1','2','3','4', :conditions=> ["toys=?", "beer"])
394
+ #
395
+ # Find helpers: RightAws::ActiveSdb::Base.select_by_... and RightAws::ActiveSdb::Base.select_all_by_...
396
+ #
397
+ # Client.select_by_name('Matias Rust')
398
+ # Client.select_by_name_and_city('Putin','Moscow')
399
+ # Client.select_by_name_and_city_and_post('Medvedev','Moscow','president')
400
+ #
401
+ # Client.select_all_by_author('G.Bush jr')
402
+ # Client.select_all_by_age_and_gender_and_ethnicity('34','male','russian')
403
+ # Client.select_all_by_gender_and_country('male', 'Russia', :order => 'name')
404
+ #
405
+ # Continue listing:
406
+ #
407
+ # # initial listing
408
+ # Client.select(:all, :limit => 10)
409
+ # # continue listing
410
+ # begin
411
+ # Client.select(:all, :limit => 10, :next_token => Client.next_token)
412
+ # end while Client.next_token
413
+ #
414
+ # Sort oder:
415
+ # If :order=>'attribute' option is specified then result response (ordered by 'attribute') will contain only items where attribute is defined (is not null).
416
+ #
417
+ # Client.select(:all) # returns all records
418
+ # Client.select(:all, :order => 'gender') # returns all records ordered by gender where gender attribute exists
419
+ # Client.select(:all, :order => 'name desc') # returns all records ordered by name in desc order where name attribute exists
420
+ #
421
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
422
+ #
423
+ def select(*args)
424
+ options = args.last.is_a?(Hash) ? args.pop : {}
425
+ case args.first
426
+ when :all then sql_select(options)
427
+ when :first then sql_select(options.merge(:limit => 1)).first
428
+ else select_from_ids args, options
429
+ end
430
+ end
431
+
432
+ def generate_id # :nodoc:
433
+ AwsUtils::generate_unique_token
434
+ end
435
+
436
+ protected
437
+
438
+ # Select
439
+
440
+ def select_from_ids(args, options) # :nodoc:
441
+ cond = []
442
+ # detect amount of records requested
443
+ bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
444
+ # flatten ids
445
+ args = Array(args).flatten
446
+ args.each { |id| cond << "id=#{self.connection.escape(id)}" }
447
+ ids_cond = "(#{cond.join(' OR ')})"
448
+ # user defined :conditions to string (if it was defined)
449
+ options[:conditions] = build_conditions(options[:conditions])
450
+ # join ids condition and user defined conditions
451
+ options[:conditions] = options[:conditions].right_blank? ? ids_cond : "(#{options[:conditions]}) AND #{ids_cond}"
452
+ result = sql_select(options)
453
+ # if one record was requested then return it
454
+ unless bunch_of_records_requested
455
+ record = result.first
456
+ # railse if nothing was found
457
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
458
+ record
459
+ else
460
+ # if a bunch of records was requested then return check that we found all of them
461
+ # and return as an array
462
+ unless args.size == result.size
463
+ id_list = args.map{|i| "'#{i}'"}.join(',')
464
+ raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
465
+ else
466
+ result
467
+ end
468
+ end
469
+ end
470
+
471
+ def sql_select(options) # :nodoc:
472
+ @next_token = options[:next_token]
473
+ select_expression = build_select(options)
474
+ # request items
475
+ query_result = self.connection.select(select_expression, @next_token)
476
+ @next_token = query_result[:next_token]
477
+ items = query_result[:items].map do |hash|
478
+ id, attributes = hash.shift
479
+ new_item = self.new( attributes.merge({ 'id' => id }))
480
+ new_item.mark_as_old
481
+ new_item
482
+ end
483
+ items
484
+ end
485
+
486
+ # select_by helpers
487
+ def select_all_by_(format_str, args, options) # :nodoc:
488
+ fields = format_str.to_s.sub(/^select_(all_)?by_/,'').split('_and_')
489
+ conditions = fields.map { |field| "#{field}=?" }.join(' AND ')
490
+ options[:conditions] = [conditions, *args]
491
+ select(:all, options)
492
+ end
493
+
494
+ def select_by_(format_str, args, options) # :nodoc:
495
+ options[:limit] = 1
496
+ select_all_by_(format_str, args, options).first
497
+ end
498
+
499
+ # Query
500
+
501
+ # Returns an array of query attributes.
502
+ # Query_expression must be a well formated SDB query string:
503
+ # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
504
+ def query_attributes(query_expression) # :nodoc:
505
+ attrs = []
506
+ array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
507
+ until array.empty? do
508
+ attrs << array.shift # skip it's value
509
+ array.shift #
510
+ end
511
+ attrs
512
+ end
513
+
514
+ # Returns an array of [attribute_name, 'asc'|'desc']
515
+ def sort_options(sort_string)
516
+ sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i]
517
+ [$1, ($2 || 'asc')]
518
+ end
519
+
520
+ # Perform a query request.
521
+ #
522
+ # Options
523
+ # :query_expression nil | string | array
524
+ # :max_number_of_items nil | integer
525
+ # :next_token nil | string
526
+ # :sort_option nil | string "name desc|asc"
527
+ #
528
+ def query(options) # :nodoc:
529
+ @next_token = options[:next_token]
530
+ query_expression = build_conditions(options[:query_expression])
531
+ # add sort_options to the query_expression
532
+ if options[:sort_option]
533
+ sort_by, sort_order = sort_options(options[:sort_option])
534
+ sort_query_expression = "['#{sort_by}' starts-with '']"
535
+ sort_by_expression = " sort '#{sort_by}' #{sort_order}"
536
+ # make query_expression to be a string (it may be null)
537
+ query_expression = query_expression.to_s
538
+ # quote from Amazon:
539
+ # The sort attribute must be present in at least one of the predicates of the query expression.
540
+ if query_expression.right_blank?
541
+ query_expression = sort_query_expression
542
+ elsif !query_attributes(query_expression).include?(sort_by)
543
+ query_expression += " intersection #{sort_query_expression}"
544
+ end
545
+ query_expression += sort_by_expression
546
+ end
547
+ # request items
548
+ query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token)
549
+ @next_token = query_result[:next_token]
550
+ items = query_result[:items].map do |name|
551
+ new_item = self.new('id' => name)
552
+ new_item.mark_as_old
553
+ reload_if_exists(record) if options[:auto_load]
554
+ new_item
555
+ end
556
+ items
557
+ end
558
+
559
+ # reload a record unless it is nil
560
+ def reload_if_exists(record) # :nodoc:
561
+ record && record.reload
562
+ end
563
+
564
+ def reload_all_records(*list) # :nodoc:
565
+ list.flatten.each { |record| reload_if_exists(record) }
566
+ end
567
+
568
+ def find_every(options) # :nodoc:
569
+ records = query( :query_expression => options[:conditions],
570
+ :max_number_of_items => options[:limit],
571
+ :next_token => options[:next_token],
572
+ :sort_option => options[:sort] || options[:order] )
573
+ options[:auto_load] ? reload_all_records(records) : records
574
+ end
575
+
576
+ def find_initial(options) # :nodoc:
577
+ options[:limit] = 1
578
+ record = find_every(options).first
579
+ options[:auto_load] ? reload_all_records(record).first : record
580
+ end
581
+
582
+ def find_from_ids(args, options) # :nodoc:
583
+ cond = []
584
+ # detect amount of records requested
585
+ bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
586
+ # flatten ids
587
+ args = Array(args).flatten
588
+ args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
589
+ ids_cond = "[#{cond.join(' OR ')}]"
590
+ # user defined :conditions to string (if it was defined)
591
+ options[:conditions] = build_conditions(options[:conditions])
592
+ # join ids condition and user defined conditions
593
+ options[:conditions] = options[:conditions].right_blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
594
+ result = find_every(options)
595
+ # if one record was requested then return it
596
+ unless bunch_of_records_requested
597
+ record = result.first
598
+ # railse if nothing was found
599
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
600
+ options[:auto_load] ? reload_all_records(record).first : record
601
+ else
602
+ # if a bunch of records was requested then return check that we found all of them
603
+ # and return as an array
604
+ unless args.size == result.size
605
+ id_list = args.map{|i| "'#{i}'"}.join(',')
606
+ raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
607
+ else
608
+ options[:auto_load] ? reload_all_records(result) : result
609
+ end
610
+ end
611
+ end
612
+
613
+ # find_by helpers
614
+ def find_all_by_(format_str, args, options) # :nodoc:
615
+ fields = format_str.to_s.sub(/^find_(all_)?by_/,'').split('_and_')
616
+ conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ')
617
+ options[:conditions] = [conditions, *args]
618
+ find(:all, options)
619
+ end
620
+
621
+ def find_by_(format_str, args, options) # :nodoc:
622
+ options[:limit] = 1
623
+ find_all_by_(format_str, args, options).first
624
+ end
625
+
626
+ # Misc
627
+
628
+ def method_missing(method, *args) # :nodoc:
629
+ if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/]
630
+ options = args.last.is_a?(Hash) ? args.pop : {}
631
+ __send__($1, method, args, options)
632
+ else
633
+ super(method, *args)
634
+ end
635
+ end
636
+
637
+ def build_select(options) # :nodoc:
638
+ select = options[:select] || '*'
639
+ from = options[:from] || domain
640
+ conditions = options[:conditions] ? " WHERE #{build_conditions(options[:conditions])}" : ''
641
+ order = options[:order] ? " ORDER BY #{options[:order]}" : ''
642
+ limit = options[:limit] ? " LIMIT #{options[:limit]}" : ''
643
+ # mix sort by argument (it must present in response)
644
+ unless order.right_blank?
645
+ sort_by, sort_order = sort_options(options[:order])
646
+ conditions << (conditions.right_blank? ? " WHERE " : " AND ") << "(#{sort_by} IS NOT NULL)"
647
+ end
648
+ "SELECT #{select} FROM #{from}#{conditions}#{order}#{limit}"
649
+ end
650
+
651
+ def build_conditions(conditions) # :nodoc:
652
+ case
653
+ when conditions.is_a?(Array) then connection.query_expression_from_array(conditions)
654
+ when conditions.is_a?(Hash) then connection.query_expression_from_hash(conditions)
655
+ else conditions
656
+ end
657
+ end
658
+
659
+ def serialization_for_type(type)
660
+ @serializations ||= {}
661
+ unless @serializations.has_key? type
662
+ @serializations[type] = ::RightAws::ActiveSdb.const_get("#{type}Serialization") rescue false
663
+ end
664
+ @serializations[type]
665
+ end
666
+ end
667
+
668
+ public
669
+
670
+ # instance attributes
671
+ attr_accessor :attributes
672
+
673
+ # item name
674
+ attr_accessor :id
675
+
676
+ # Create new Item instance.
677
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
678
+ #
679
+ # item = Client.new('name' => 'Jon', 'toys' => ['girls', 'beer', 'pub'])
680
+ # puts item.inspect #=> #<Client:0xb77a2698 @new_record=true, @attributes={"name"=>["Jon"], "toys"=>["girls", "beer", "pub"]}>
681
+ # item.save #=> {"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}
682
+ # puts item.inspect #=> #<Client:0xb77a2698 @new_record=false, @attributes={"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}>
683
+ #
684
+ def initialize(attrs={})
685
+ @attributes = uniq_values(attrs)
686
+ @new_record = true
687
+ end
688
+
689
+ # Create and save new Item instance.
690
+ # +Attributes+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
691
+ #
692
+ # item = Client.create('name' => 'Cat', 'toys' => ['Jons socks', 'mice', 'clew'])
693
+ # puts item.inspect #=> #<Client:0xb77a0a78 @new_record=false, @attributes={"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "mice", "clew"]}>
694
+ #
695
+ def self.create(attributes={})
696
+ item = self.new(attributes)
697
+ item.save
698
+ item
699
+ end
700
+
701
+ # Returns an item id. Same as: item['id'] or item.attributes['id']
702
+ def id
703
+ @attributes['id']
704
+ end
705
+
706
+ # Sets an item id.
707
+ def id=(id)
708
+ @attributes['id'] = id.to_s
709
+ end
710
+
711
+ # Returns a hash of all the attributes.
712
+ #
713
+ # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
714
+ #
715
+ def attributes
716
+ @attributes.dup
717
+ end
718
+
719
+ # Allows one to set all the attributes at once by passing in a hash with keys matching the attribute names.
720
+ # if '+id+' attribute is not set in new attributes has then it being derived from old attributes.
721
+ #
722
+ # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
723
+ # # set new attributes ('id' is missed)
724
+ # item.attributes = { 'name'=>'Dog', 'toys'=>['bones','cats'] }
725
+ # puts item.attributes.inspect #=> {"name"=>["Dog"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["bones", "cats"]}
726
+ # # set new attributes ('id' is set)
727
+ # item.attributes = { 'id' => 'blah-blah', 'name'=>'Birds', 'toys'=>['seeds','dogs tail'] }
728
+ # puts item.attributes.inspect #=> {"name"=>["Birds"], "id"=>"blah-blah", "toys"=>["seeds", "dogs tail"]}
729
+ #
730
+ def attributes=(attrs)
731
+ old_id = @attributes['id']
732
+ @attributes = uniq_values(attrs)
733
+ @attributes['id'] = old_id if @attributes['id'].right_blank? && !old_id.right_blank?
734
+ self.attributes
735
+ end
736
+
737
+ def columns
738
+ self.class.columns
739
+ end
740
+
741
+ def connection
742
+ self.class.connection
743
+ end
744
+
745
+ # Item domain name.
746
+ def domain
747
+ self.class.domain
748
+ end
749
+
750
+ # Returns the values of the attribute identified by +attribute+.
751
+ #
752
+ # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
753
+ #
754
+ def [](attribute)
755
+ raw = @attributes[attribute.to_s]
756
+ self.class.column?(attribute) && raw ? self.class.deserialize(attribute, raw.first) : raw
757
+ end
758
+
759
+ # Updates the attribute identified by +attribute+ with the specified +values+.
760
+ #
761
+ # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
762
+ # item['Cat'] = ["Whiskas", "chicken"]
763
+ # puts item['Cat'].inspect #=> ["Whiskas", "chicken"]
764
+ #
765
+ def []=(attribute, values)
766
+ attribute = attribute.to_s
767
+ @attributes[attribute] = case
768
+ when attribute == 'id'
769
+ values.to_s
770
+ when self.class.column?(attribute)
771
+ self.class.serialize(attribute, values)
772
+ else
773
+ Array(values).uniq
774
+ end
775
+ end
776
+
777
+ # Reload attributes from SDB. Replaces in-memory attributes.
778
+ #
779
+ # item = Client.find_by_name('Cat') #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
780
+ # item.reload #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
781
+ #
782
+ def reload
783
+ raise_on_id_absence
784
+ old_id = id
785
+ attrs = connection.get_attributes(domain, id)[:attributes]
786
+ @attributes = {}
787
+ unless attrs.right_blank?
788
+ attrs.each { |attribute, values| @attributes[attribute] = values }
789
+ @attributes['id'] = old_id
790
+ end
791
+ mark_as_old
792
+ @attributes
793
+ end
794
+
795
+ # Reload a set of attributes from SDB. Adds the loaded list to in-memory data.
796
+ # +attrs_list+ is an array or comma separated list of attributes names.
797
+ # Returns a hash of loaded attributes.
798
+ #
799
+ # This is not the best method to get a bunch of attributes because
800
+ # a web service call is being performed for every attribute.
801
+ #
802
+ # item = Client.find_by_name('Cat')
803
+ # item.reload_attributes('toys', 'name') #=> {"name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}
804
+ #
805
+ def reload_attributes(*attrs_list)
806
+ raise_on_id_absence
807
+ attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
808
+ attrs_list.delete('id')
809
+ result = {}
810
+ attrs_list.flatten.uniq.each do |attribute|
811
+ attribute = attribute.to_s
812
+ values = connection.get_attributes(domain, id, attribute)[:attributes][attribute]
813
+ unless values.right_blank?
814
+ @attributes[attribute] = result[attribute] = values
815
+ else
816
+ @attributes.delete(attribute)
817
+ end
818
+ end
819
+ mark_as_old
820
+ result
821
+ end
822
+
823
+ # Stores in-memory attributes to SDB.
824
+ # Adds the attributes values to already stored at SDB.
825
+ # Returns a hash of stored attributes.
826
+ #
827
+ # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
828
+ # sandy['toys'] = 'boys'
829
+ # sandy.put
830
+ # sandy['toys'] = 'patchwork'
831
+ # sandy.put
832
+ # sandy['toys'] = 'kids'
833
+ # sandy.put
834
+ # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
835
+ # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}
836
+ #
837
+ # compare to +save+ method
838
+ def put
839
+ @attributes = uniq_values(@attributes)
840
+ prepare_for_update
841
+ attrs = @attributes.dup
842
+ attrs.delete('id')
843
+ connection.put_attributes(domain, id, attrs) unless attrs.right_blank?
844
+ connection.put_attributes(domain, id, { 'id' => id }, :replace)
845
+ mark_as_old
846
+ @attributes
847
+ end
848
+
849
+ # Stores specified attributes.
850
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
851
+ # Returns a hash of saved attributes.
852
+ #
853
+ # see to +put+ method
854
+ def put_attributes(attrs)
855
+ attrs = uniq_values(attrs)
856
+ prepare_for_update
857
+ # if 'id' is present in attrs hash:
858
+ # replace internal 'id' attribute and remove it from the attributes to be sent
859
+ @attributes['id'] = attrs['id'] unless attrs['id'].right_blank?
860
+ attrs.delete('id')
861
+ # add new values to all attributes from list
862
+ connection.put_attributes(domain, id, attrs) unless attrs.right_blank?
863
+ connection.put_attributes(domain, id, { 'id' => id }, :replace)
864
+ attrs.each do |attribute, values|
865
+ @attributes[attribute] ||= []
866
+ @attributes[attribute] += values
867
+ @attributes[attribute].uniq!
868
+ end
869
+ mark_as_old
870
+ attributes
871
+ end
872
+
873
+ # Store in-memory attributes to SDB.
874
+ # Replaces the attributes values already stored at SDB by in-memory data.
875
+ # Returns a hash of stored attributes.
876
+ #
877
+ # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
878
+ # sandy['toys'] = 'boys'
879
+ # sandy.put
880
+ # sandy['toys'] = 'patchwork'
881
+ # sandy.put
882
+ # sandy['toys'] = 'kids'
883
+ # sandy.put
884
+ # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
885
+ # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
886
+ #
887
+ # compare to +put+ method
888
+ def save
889
+ @attributes = uniq_values(@attributes)
890
+ prepare_for_update
891
+ connection.put_attributes(domain, id, @attributes, :replace)
892
+ mark_as_old
893
+ @attributes
894
+ end
895
+
896
+ # Replaces the attributes at SDB by the given values.
897
+ # +Attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
898
+ # The other in-memory attributes are not being saved.
899
+ # Returns a hash of stored attributes.
900
+ #
901
+ # see +save+ method
902
+ def save_attributes(attrs)
903
+ prepare_for_update
904
+ attrs = uniq_values(attrs)
905
+ # if 'id' is present in attrs hash then replace internal 'id' attribute
906
+ unless attrs['id'].right_blank?
907
+ @attributes['id'] = attrs['id']
908
+ else
909
+ attrs['id'] = id
910
+ end
911
+ connection.put_attributes(domain, id, attrs, :replace) unless attrs.right_blank?
912
+ attrs.each { |attribute, values| attrs[attribute] = values }
913
+ mark_as_old
914
+ attrs
915
+ end
916
+
917
+ # Remove specified values from corresponding attributes.
918
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
919
+ #
920
+ # sandy = Client.find_by_name 'Sandy'
921
+ # sandy.reload
922
+ # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}>
923
+ # puts sandy.delete_values('toys' => 'patchwork') #=> { 'toys' => ['patchwork'] }
924
+ # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids"]}>
925
+ #
926
+ def delete_values(attrs)
927
+ raise_on_id_absence
928
+ attrs = uniq_values(attrs)
929
+ attrs.delete('id')
930
+ unless attrs.right_blank?
931
+ connection.delete_attributes(domain, id, attrs)
932
+ attrs.each do |attribute, values|
933
+ # remove the values from the attribute
934
+ if @attributes[attribute]
935
+ @attributes[attribute] -= values
936
+ else
937
+ # if the attribute is unknown remove it from a resulting list of fixed attributes
938
+ attrs.delete(attribute)
939
+ end
940
+ end
941
+ end
942
+ attrs
943
+ end
944
+
945
+ # Removes specified attributes from the item.
946
+ # +attrs_list+ is an array or comma separated list of attributes names.
947
+ # Returns the list of deleted attributes.
948
+ #
949
+ # sandy = Client.find_by_name 'Sandy'
950
+ # sandy.reload
951
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
952
+ # puts sandy.delete_attributes('toys') #=> ['toys']
953
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7"}>
954
+ #
955
+ def delete_attributes(*attrs_list)
956
+ raise_on_id_absence
957
+ attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
958
+ attrs_list.delete('id')
959
+ unless attrs_list.right_blank?
960
+ connection.delete_attributes(domain, id, attrs_list)
961
+ attrs_list.each { |attribute| @attributes.delete(attribute) }
962
+ end
963
+ attrs_list
964
+ end
965
+
966
+ # Delete the Item entirely from SDB.
967
+ #
968
+ # sandy = Client.find_by_name 'Sandy'
969
+ # sandy.reload
970
+ # sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
971
+ # puts sandy.delete
972
+ # sandy.reload
973
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @attributes={}, @new_record=false>
974
+ #
975
+ def delete
976
+ raise_on_id_absence
977
+ connection.delete_attributes(domain, id)
978
+ end
979
+
980
+ # Item ID
981
+ def to_s
982
+ @id
983
+ end
984
+
985
+ # Returns true if this object hasn‘t been saved yet.
986
+ def new_record?
987
+ @new_record
988
+ end
989
+
990
+ def mark_as_old # :nodoc:
991
+ @new_record = false
992
+ end
993
+
994
+ # support accessing attribute values via method call
995
+ def method_missing(method_sym, *args)
996
+ method_name = method_sym.to_s
997
+ setter = method_name[-1,1] == '='
998
+ method_name.chop! if setter
999
+
1000
+ if @attributes.has_key?(method_name) || self.class.column?(method_name)
1001
+ setter ? self[method_name] = args.first : self[method_name]
1002
+ else
1003
+ super
1004
+ end
1005
+ end
1006
+
1007
+ private
1008
+
1009
+ def raise_on_id_absence
1010
+ raise ActiveSdbError.new('Unknown record id') unless id
1011
+ end
1012
+
1013
+ def prepare_for_update
1014
+ @attributes['id'] = self.class.generate_id if @attributes['id'].right_blank?
1015
+ columns.all.each do |col_name|
1016
+ self[col_name] ||= columns.default(col_name)
1017
+ end
1018
+ end
1019
+
1020
+ def uniq_values(attributes=nil) # :nodoc:
1021
+ attrs = {}
1022
+ attributes.each do |attribute, values|
1023
+ attribute = attribute.to_s
1024
+ attrs[attribute] = case
1025
+ when attribute == 'id'
1026
+ values.to_s
1027
+ when self.class.column?(attribute)
1028
+ values.is_a?(String) ? values : self.class.serialize(attribute, values)
1029
+ else
1030
+ Array(values).uniq
1031
+ end
1032
+ attrs.delete(attribute) if values.right_blank?
1033
+ end
1034
+ attrs
1035
+ end
1036
+ end
1037
+
1038
+ class ColumnSet
1039
+ attr_accessor :columns
1040
+ def initialize
1041
+ @columns = {}
1042
+ end
1043
+
1044
+ def all
1045
+ @columns.keys
1046
+ end
1047
+
1048
+ def column(col_name)
1049
+ @columns[col_name.to_s]
1050
+ end
1051
+ alias_method :include?, :column
1052
+
1053
+ def type_of(col_name)
1054
+ column(col_name) && column(col_name)[:type]
1055
+ end
1056
+
1057
+ def default(col_name)
1058
+ return nil unless include?(col_name)
1059
+ default = column(col_name)[:default]
1060
+ default.respond_to?(:call) ? default.call : default
1061
+ end
1062
+
1063
+ def method_missing(method_sym, *args)
1064
+ data_type = args.shift || :String
1065
+ options = args.shift || {}
1066
+ @columns[method_sym.to_s] = options.merge( :type => data_type )
1067
+ end
1068
+ end
1069
+
1070
+ class DateTimeSerialization
1071
+ class << self
1072
+ def serialize(date)
1073
+ date.strftime('%Y-%m-%dT%H:%M:%S%z')
1074
+ end
1075
+
1076
+ def deserialize(string)
1077
+ r = DateTime.parse(string) rescue nil
1078
+ end
1079
+ end
1080
+ end
1081
+
1082
+ class BooleanSerialization
1083
+ class << self
1084
+ def serialize(boolean)
1085
+ boolean ? 'T' : 'F'
1086
+ end
1087
+
1088
+ def deserialize(string)
1089
+ string == 'T'
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ class IntegerSerialization
1095
+ class << self
1096
+ def serialize(int)
1097
+ int.to_s
1098
+ end
1099
+
1100
+ def deserialize(string)
1101
+ string.to_i
1102
+ end
1103
+ end
1104
+ end
1105
+
1106
+ end
1107
+ end