aboisvert_aws 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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