right_aws 1.9.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +164 -13
  2. data/Manifest.txt +28 -1
  3. data/README.txt +12 -10
  4. data/Rakefile +56 -29
  5. data/lib/acf/right_acf_interface.rb +343 -172
  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/right_awsbase.rb +755 -115
  12. data/lib/awsbase/support.rb +2 -78
  13. data/lib/awsbase/version.rb +9 -0
  14. data/lib/ec2/right_ec2.rb +274 -1294
  15. data/lib/ec2/right_ec2_ebs.rb +514 -0
  16. data/lib/ec2/right_ec2_images.rb +444 -0
  17. data/lib/ec2/right_ec2_instances.rb +797 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +243 -0
  21. data/lib/ec2/right_ec2_security_groups.rb +496 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +422 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +598 -0
  25. data/lib/ec2/right_ec2_vpc2.rb +382 -0
  26. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  27. data/lib/elb/right_elb_interface.rb +573 -0
  28. data/lib/emr/right_emr_interface.rb +728 -0
  29. data/lib/iam/right_iam_access_keys.rb +71 -0
  30. data/lib/iam/right_iam_groups.rb +195 -0
  31. data/lib/iam/right_iam_interface.rb +341 -0
  32. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  33. data/lib/iam/right_iam_users.rb +251 -0
  34. data/lib/rds/right_rds_interface.rb +1657 -0
  35. data/lib/right_aws.rb +30 -13
  36. data/lib/route_53/right_route_53_interface.rb +641 -0
  37. data/lib/s3/right_s3.rb +108 -41
  38. data/lib/s3/right_s3_interface.rb +349 -118
  39. data/lib/sdb/active_sdb.rb +388 -54
  40. data/lib/sdb/right_sdb_interface.rb +323 -64
  41. data/lib/sns/right_sns_interface.rb +286 -0
  42. data/lib/sqs/right_sqs.rb +1 -2
  43. data/lib/sqs/right_sqs_gen2.rb +73 -17
  44. data/lib/sqs/right_sqs_gen2_interface.rb +146 -73
  45. data/lib/sqs/right_sqs_interface.rb +12 -22
  46. data/right_aws.gemspec +91 -0
  47. data/test/README.mdown +39 -0
  48. data/test/acf/test_right_acf.rb +11 -19
  49. data/test/awsbase/test_helper.rb +2 -0
  50. data/test/awsbase/test_right_awsbase.rb +11 -0
  51. data/test/ec2/test_right_ec2.rb +32 -1
  52. data/test/elb/test_helper.rb +2 -0
  53. data/test/elb/test_right_elb.rb +43 -0
  54. data/test/rds/test_helper.rb +2 -0
  55. data/test/rds/test_right_rds.rb +120 -0
  56. data/test/route_53/fixtures/a_record.xml +18 -0
  57. data/test/route_53/fixtures/alias_record.xml +18 -0
  58. data/test/route_53/test_helper.rb +2 -0
  59. data/test/route_53/test_right_route_53.rb +141 -0
  60. data/test/s3/test_right_s3.rb +176 -42
  61. data/test/s3/test_right_s3_stubbed.rb +6 -4
  62. data/test/sdb/test_active_sdb.rb +120 -19
  63. data/test/sdb/test_batch_put_attributes.rb +54 -0
  64. data/test/sdb/test_right_sdb.rb +71 -16
  65. data/test/sns/test_helper.rb +2 -0
  66. data/test/sns/test_right_sns.rb +153 -0
  67. data/test/sqs/test_right_sqs.rb +0 -6
  68. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  69. data/test/ts_right_aws.rb +1 -0
  70. metadata +181 -22
@@ -21,13 +21,6 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #
23
23
 
24
- begin
25
- require 'uuidtools'
26
- rescue LoadError => e
27
- STDERR.puts("RightSDB Alpha requires the uuidtools gem. Run \'gem install uuidtools\' and try again.")
28
- exit
29
- end
30
-
31
24
  module RightAws
32
25
 
33
26
  # = RightAws::ActiveSdb -- RightScale SDB interface (alpha release)
@@ -39,9 +32,6 @@ module RightAws
39
32
  # require 'right_aws'
40
33
  # require 'sdb/active_sdb'
41
34
  #
42
- # Additionally, the ActiveSdb class requires the 'uuidtools' gem; this gem is not normally required by RightAws and is not installed as a
43
- # dependency of RightAws.
44
- #
45
35
  # Simple ActiveSdb usage example:
46
36
  #
47
37
  # class Client < RightAws::ActiveSdb::Base
@@ -92,6 +82,59 @@ module RightAws
92
82
  # # remove domain
93
83
  # Client.delete_domain
94
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
+ #
95
138
  class ActiveSdb
96
139
 
97
140
  module ActiveSdbConnect
@@ -106,7 +149,6 @@ module RightAws
106
149
  # :port => 443 # Amazon service port: 80 or 443(default)
107
150
  # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
108
151
  # :signature_version => '0' # The signature version : '0' or '1'(default)
109
- # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
110
152
  # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
111
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')
112
154
 
@@ -242,7 +284,31 @@ module RightAws
242
284
  def delete_domain
243
285
  connection.delete_domain(domain)
244
286
  end
245
-
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
+
246
312
  # Perform a find request.
247
313
  #
248
314
  # Single record:
@@ -294,6 +360,8 @@ module RightAws
294
360
  # Client.find(:first) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
295
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>
296
362
  #
363
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingQuery.html
364
+ #
297
365
  def find(*args)
298
366
  options = args.last.is_a?(Hash) ? args.pop : {}
299
367
  case args.first
@@ -303,12 +371,137 @@ module RightAws
303
371
  end
304
372
  end
305
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
+
306
436
  protected
307
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
+
308
501
  # Returns an array of query attributes.
309
502
  # Query_expression must be a well formated SDB query string:
310
503
  # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
311
- def query_attributes(query_expression)
504
+ def query_attributes(query_expression) # :nodoc:
312
505
  attrs = []
313
506
  array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
314
507
  until array.empty? do
@@ -334,8 +527,7 @@ module RightAws
334
527
  #
335
528
  def query(options) # :nodoc:
336
529
  @next_token = options[:next_token]
337
- query_expression = options[:query_expression]
338
- query_expression = connection.query_expression_from_array(query_expression) if query_expression.is_a?(Array)
530
+ query_expression = build_conditions(options[:query_expression])
339
531
  # add sort_options to the query_expression
340
532
  if options[:sort_option]
341
533
  sort_by, sort_order = sort_options(options[:sort_option])
@@ -345,7 +537,7 @@ module RightAws
345
537
  query_expression = query_expression.to_s
346
538
  # quote from Amazon:
347
539
  # The sort attribute must be present in at least one of the predicates of the query expression.
348
- if query_expression.blank?
540
+ if query_expression.right_blank?
349
541
  query_expression = sort_query_expression
350
542
  elsif !query_attributes(query_expression).include?(sort_by)
351
543
  query_expression += " intersection #{sort_query_expression}"
@@ -358,14 +550,19 @@ module RightAws
358
550
  items = query_result[:items].map do |name|
359
551
  new_item = self.new('id' => name)
360
552
  new_item.mark_as_old
361
- new_item.reload if options[:auto_load]
553
+ reload_if_exists(record) if options[:auto_load]
362
554
  new_item
363
555
  end
364
556
  items
365
557
  end
366
558
 
559
+ # reload a record unless it is nil
560
+ def reload_if_exists(record) # :nodoc:
561
+ record && record.reload
562
+ end
563
+
367
564
  def reload_all_records(*list) # :nodoc:
368
- list.flatten.each { |record| record.reload }
565
+ list.flatten.each { |record| reload_if_exists(record) }
369
566
  end
370
567
 
371
568
  def find_every(options) # :nodoc:
@@ -387,19 +584,20 @@ module RightAws
387
584
  # detect amount of records requested
388
585
  bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
389
586
  # flatten ids
390
- args = args.to_a.flatten
587
+ args = Array(args).flatten
391
588
  args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
392
589
  ids_cond = "[#{cond.join(' OR ')}]"
393
590
  # user defined :conditions to string (if it was defined)
394
- if options[:conditions].is_a?(Array)
395
- options[:conditions] = connection.query_expression_from_array(options[:conditions])
396
- end
591
+ options[:conditions] = build_conditions(options[:conditions])
397
592
  # join ids condition and user defined conditions
398
- options[:conditions] = options[:conditions].blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
593
+ options[:conditions] = options[:conditions].right_blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
399
594
  result = find_every(options)
400
595
  # if one record was requested then return it
401
596
  unless bunch_of_records_requested
402
- options[:auto_load] ? reload_all_records(result.first).first : result.first
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
403
601
  else
404
602
  # if a bunch of records was requested then return check that we found all of them
405
603
  # and return as an array
@@ -425,22 +623,46 @@ module RightAws
425
623
  find_all_by_(format_str, args, options).first
426
624
  end
427
625
 
626
+ # Misc
627
+
428
628
  def method_missing(method, *args) # :nodoc:
429
- if method.to_s[/^(find_all_by_|find_by_)/]
629
+ if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/]
430
630
  options = args.last.is_a?(Hash) ? args.pop : {}
431
- if method.to_s[/^find_all_by_/]
432
- find_all_by_(method, args, options)
433
- else
434
- find_by_(method, args, options)
435
- end
631
+ __send__($1, method, args, options)
436
632
  else
437
633
  super(method, *args)
438
634
  end
439
635
  end
440
- end
441
-
442
- def self.generate_id # :nodoc:
443
- UUID.timestamp_create().to_s
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
444
666
  end
445
667
 
446
668
  public
@@ -508,11 +730,15 @@ module RightAws
508
730
  def attributes=(attrs)
509
731
  old_id = @attributes['id']
510
732
  @attributes = uniq_values(attrs)
511
- @attributes['id'] = old_id if @attributes['id'].blank? && !old_id.blank?
733
+ @attributes['id'] = old_id if @attributes['id'].right_blank? && !old_id.right_blank?
512
734
  self.attributes
513
735
  end
514
736
 
515
- def connection
737
+ def columns
738
+ self.class.columns
739
+ end
740
+
741
+ def connection
516
742
  self.class.connection
517
743
  end
518
744
 
@@ -526,7 +752,8 @@ module RightAws
526
752
  # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
527
753
  #
528
754
  def [](attribute)
529
- @attributes[attribute.to_s]
755
+ raw = @attributes[attribute.to_s]
756
+ self.class.column?(attribute) && raw ? self.class.deserialize(attribute, raw.first) : raw
530
757
  end
531
758
 
532
759
  # Updates the attribute identified by +attribute+ with the specified +values+.
@@ -537,7 +764,14 @@ module RightAws
537
764
  #
538
765
  def []=(attribute, values)
539
766
  attribute = attribute.to_s
540
- @attributes[attribute] = attribute == 'id' ? values.to_s : values.to_a.uniq
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
541
775
  end
542
776
 
543
777
  # Reload attributes from SDB. Replaces in-memory attributes.
@@ -550,7 +784,7 @@ module RightAws
550
784
  old_id = id
551
785
  attrs = connection.get_attributes(domain, id)[:attributes]
552
786
  @attributes = {}
553
- unless attrs.blank?
787
+ unless attrs.right_blank?
554
788
  attrs.each { |attribute, values| @attributes[attribute] = values }
555
789
  @attributes['id'] = old_id
556
790
  end
@@ -576,7 +810,7 @@ module RightAws
576
810
  attrs_list.flatten.uniq.each do |attribute|
577
811
  attribute = attribute.to_s
578
812
  values = connection.get_attributes(domain, id, attribute)[:attributes][attribute]
579
- unless values.blank?
813
+ unless values.right_blank?
580
814
  @attributes[attribute] = result[attribute] = values
581
815
  else
582
816
  @attributes.delete(attribute)
@@ -606,7 +840,7 @@ module RightAws
606
840
  prepare_for_update
607
841
  attrs = @attributes.dup
608
842
  attrs.delete('id')
609
- connection.put_attributes(domain, id, attrs) unless attrs.blank?
843
+ connection.put_attributes(domain, id, attrs) unless attrs.right_blank?
610
844
  connection.put_attributes(domain, id, { 'id' => id }, :replace)
611
845
  mark_as_old
612
846
  @attributes
@@ -622,10 +856,10 @@ module RightAws
622
856
  prepare_for_update
623
857
  # if 'id' is present in attrs hash:
624
858
  # replace internal 'id' attribute and remove it from the attributes to be sent
625
- @attributes['id'] = attrs['id'] unless attrs['id'].blank?
859
+ @attributes['id'] = attrs['id'] unless attrs['id'].right_blank?
626
860
  attrs.delete('id')
627
861
  # add new values to all attributes from list
628
- connection.put_attributes(domain, id, attrs) unless attrs.blank?
862
+ connection.put_attributes(domain, id, attrs) unless attrs.right_blank?
629
863
  connection.put_attributes(domain, id, { 'id' => id }, :replace)
630
864
  attrs.each do |attribute, values|
631
865
  @attributes[attribute] ||= []
@@ -669,12 +903,12 @@ module RightAws
669
903
  prepare_for_update
670
904
  attrs = uniq_values(attrs)
671
905
  # if 'id' is present in attrs hash then replace internal 'id' attribute
672
- unless attrs['id'].blank?
906
+ unless attrs['id'].right_blank?
673
907
  @attributes['id'] = attrs['id']
674
908
  else
675
909
  attrs['id'] = id
676
910
  end
677
- connection.put_attributes(domain, id, attrs, :replace) unless attrs.blank?
911
+ connection.put_attributes(domain, id, attrs, :replace) unless attrs.right_blank?
678
912
  attrs.each { |attribute, values| attrs[attribute] = values }
679
913
  mark_as_old
680
914
  attrs
@@ -693,9 +927,17 @@ module RightAws
693
927
  raise_on_id_absence
694
928
  attrs = uniq_values(attrs)
695
929
  attrs.delete('id')
696
- unless attrs.blank?
930
+ unless attrs.right_blank?
697
931
  connection.delete_attributes(domain, id, attrs)
698
- attrs.each { |attribute, values| @attributes[attribute] -= values }
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
699
941
  end
700
942
  attrs
701
943
  end
@@ -714,7 +956,7 @@ module RightAws
714
956
  raise_on_id_absence
715
957
  attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
716
958
  attrs_list.delete('id')
717
- unless attrs_list.blank?
959
+ unless attrs_list.right_blank?
718
960
  connection.delete_attributes(domain, id, attrs_list)
719
961
  attrs_list.each { |attribute| @attributes.delete(attribute) }
720
962
  end
@@ -749,25 +991,117 @@ module RightAws
749
991
  @new_record = false
750
992
  end
751
993
 
752
- private
753
-
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
+
754
1009
  def raise_on_id_absence
755
1010
  raise ActiveSdbError.new('Unknown record id') unless id
756
1011
  end
757
1012
 
758
1013
  def prepare_for_update
759
- @attributes['id'] = self.class.generate_id if @attributes['id'].blank?
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
760
1018
  end
761
1019
 
762
1020
  def uniq_values(attributes=nil) # :nodoc:
763
1021
  attrs = {}
764
1022
  attributes.each do |attribute, values|
765
1023
  attribute = attribute.to_s
766
- attrs[attribute] = attribute == 'id' ? values.to_s : values.to_a.uniq
767
- attrs.delete(attribute) if values.blank?
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?
768
1033
  end
769
1034
  attrs
770
1035
  end
771
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
+
772
1106
  end
773
- end
1107
+ end