right_aws 1.9.0 → 3.1.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 (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