right_aws 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/History.txt +53 -15
  2. data/Manifest.txt +16 -0
  3. data/README.txt +10 -9
  4. data/Rakefile +13 -15
  5. data/lib/acf/right_acf_interface.rb +224 -118
  6. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  7. data/lib/acf/right_acf_streaming_interface.rb +236 -0
  8. data/lib/acw/right_acw_interface.rb +249 -0
  9. data/lib/as/right_as_interface.rb +699 -0
  10. data/lib/awsbase/right_awsbase.rb +232 -51
  11. data/lib/awsbase/support.rb +4 -0
  12. data/lib/ec2/right_ec2.rb +33 -1375
  13. data/lib/ec2/right_ec2_ebs.rb +452 -0
  14. data/lib/ec2/right_ec2_images.rb +373 -0
  15. data/lib/ec2/right_ec2_instances.rb +755 -0
  16. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  17. data/lib/ec2/right_ec2_reserved_instances.rb +170 -0
  18. data/lib/ec2/right_ec2_security_groups.rb +280 -0
  19. data/lib/ec2/right_ec2_spot_instances.rb +399 -0
  20. data/lib/ec2/right_ec2_vpc.rb +571 -0
  21. data/lib/elb/right_elb_interface.rb +496 -0
  22. data/lib/rds/right_rds_interface.rb +998 -0
  23. data/lib/right_aws.rb +18 -4
  24. data/lib/s3/right_s3.rb +39 -7
  25. data/lib/s3/right_s3_interface.rb +77 -53
  26. data/lib/sdb/active_sdb.rb +203 -11
  27. data/lib/sdb/right_sdb_interface.rb +68 -45
  28. data/lib/sqs/right_sqs_gen2.rb +73 -16
  29. data/lib/sqs/right_sqs_gen2_interface.rb +131 -51
  30. data/lib/sqs/right_sqs_interface.rb +2 -4
  31. data/test/acf/test_right_acf.rb +10 -18
  32. data/test/rds/test_helper.rb +2 -0
  33. data/test/rds/test_right_rds.rb +120 -0
  34. data/test/s3/test_right_s3.rb +10 -8
  35. data/test/s3/test_right_s3_stubbed.rb +6 -4
  36. data/test/sdb/test_active_sdb.rb +70 -12
  37. data/test/sdb/test_right_sdb.rb +13 -7
  38. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  39. metadata +103 -14
@@ -39,6 +39,17 @@ require 'awsbase/benchmark_fix'
39
39
  require 'awsbase/support'
40
40
  require 'awsbase/right_awsbase'
41
41
  require 'ec2/right_ec2'
42
+ require 'ec2/right_ec2_images'
43
+ require 'ec2/right_ec2_instances'
44
+ require 'ec2/right_ec2_security_groups'
45
+ require 'ec2/right_ec2_spot_instances'
46
+ require 'ec2/right_ec2_ebs'
47
+ require 'ec2/right_ec2_reserved_instances'
48
+ require 'ec2/right_ec2_vpc'
49
+ require 'ec2/right_ec2_monitoring'
50
+ require 'elb/right_elb_interface'
51
+ require 'acw/right_acw_interface'
52
+ require 'as/right_as_interface'
42
53
  require 's3/right_s3_interface'
43
54
  require 's3/right_s3'
44
55
  require 'sqs/right_sqs_interface'
@@ -47,15 +58,18 @@ require 'sqs/right_sqs_gen2_interface'
47
58
  require 'sqs/right_sqs_gen2'
48
59
  require 'sdb/right_sdb_interface'
49
60
  require 'acf/right_acf_interface'
61
+ require 'acf/right_acf_streaming_interface'
62
+ require 'acf/right_acf_origin_access_identities'
63
+ require 'rds/right_rds_interface'
50
64
 
51
65
 
52
66
  module RightAws #:nodoc:
53
67
  module VERSION #:nodoc:
54
- MAJOR = 1
55
- MINOR = 10
56
- TINY = 0
68
+ MAJOR = 2 unless defined?(MAJOR)
69
+ MINOR = 0 unless defined?(MINOR)
70
+ TINY = 0 unless defined?(TINY)
57
71
 
58
- STRING = [MAJOR, MINOR, TINY].join('.')
72
+ STRING = [MAJOR, MINOR, TINY].join('.') unless defined?(STRING)
59
73
  end
60
74
  end
61
75
 
@@ -460,6 +460,30 @@ module RightAws
460
460
  get if !@data and exists?
461
461
  @data
462
462
  end
463
+
464
+ # Getter for the 'content-type' metadata
465
+ def content_type
466
+ @headers['content-type'] if @headers
467
+ end
468
+
469
+ # Helper to get and URI-decode a header metadata.
470
+ # Metadata have to be HTTP encoded (rfc2616) as we use the Amazon S3 REST api
471
+ # see http://docs.amazonwebservices.com/AmazonS3/latest/index.html?UsingMetadata.html
472
+ def decoded_meta_headers(key = nil)
473
+ if key
474
+ # Get one metadata value by its key
475
+ URI.decode(@meta_headers[key.to_s])
476
+ else
477
+ # Get a hash of all metadata with a decoded value
478
+ @decoded_meta_headers ||= begin
479
+ metadata = {}
480
+ @meta_headers.each do |key, value|
481
+ metadata[key.to_sym] = URI.decode(value)
482
+ end
483
+ metadata
484
+ end
485
+ end
486
+ end
463
487
 
464
488
  # Retrieve object data and attributes from Amazon.
465
489
  # Returns a +String+.
@@ -759,11 +783,11 @@ module RightAws
759
783
  @thing = thing
760
784
  @id = id
761
785
  @name = name
762
- @perms = perms.to_a
786
+ @perms = Array(perms)
763
787
  case action
764
- when :apply: apply
765
- when :refresh: refresh
766
- when :apply_and_refresh: apply; refresh
788
+ when :apply then apply
789
+ when :refresh then refresh
790
+ when :apply_and_refresh then apply; refresh
767
791
  end
768
792
  end
769
793
 
@@ -775,9 +799,13 @@ module RightAws
775
799
  false
776
800
  end
777
801
 
778
- # Return Grantee type (+String+): "Group" or "CanonicalUser".
802
+ # Return Grantee type (+String+): "Group", "AmazonCustomerByEmail" or "CanonicalUser".
779
803
  def type
780
- @id[/^http:/] ? "Group" : "CanonicalUser"
804
+ case @id
805
+ when /^http:/ then "Group"
806
+ when /@/ then "AmazonCustomerByEmail"
807
+ else "CanonicalUser"
808
+ end
781
809
  end
782
810
 
783
811
  # Return a name or an id.
@@ -872,7 +900,11 @@ module RightAws
872
900
  end
873
901
 
874
902
  def to_xml # :nodoc:
875
- id_str = @id[/^http/] ? "<URI>#{@id}</URI>" : "<ID>#{@id}</ID>"
903
+ id_str = case @id
904
+ when /^http/ then "<URI>#{@id}</URI>"
905
+ when /@/ then "<EmailAddress>#{@id}</EmailAddress>"
906
+ else "<ID>#{@id}</ID>"
907
+ end
876
908
  grants = ''
877
909
  @perms.each do |perm|
878
910
  grants << "<Grant>" +
@@ -47,6 +47,19 @@ module RightAws
47
47
  @@bench.service
48
48
  end
49
49
 
50
+ # Params supported:
51
+ # :no_subdomains => true # do not use bucket as a part of domain name but as a part of path
52
+ @@params = {}
53
+ def self.params
54
+ @@params
55
+ end
56
+
57
+ # get custom option
58
+ def param(name)
59
+ # - check explicitly defined param (@params)
60
+ # - otherwise check implicitly defined one (@@params)
61
+ @params.has_key?(name) ? @params[name] : @@params[name]
62
+ end
50
63
 
51
64
  # Creates new RightS3 instance.
52
65
  #
@@ -80,7 +93,11 @@ module RightAws
80
93
  s3_headers = {}
81
94
  headers.each do |key, value|
82
95
  key = key.downcase
83
- s3_headers[key] = value.to_s.strip if key[/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o]
96
+ value = case
97
+ when value.is_a?(Array) then value.join('')
98
+ else value.to_s
99
+ end
100
+ s3_headers[key] = value.strip if key[/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o]
84
101
  end
85
102
  s3_headers['content-type'] ||= ''
86
103
  s3_headers['content-md5'] ||= ''
@@ -120,7 +137,7 @@ module RightAws
120
137
  headers[:url].to_s[%r{^([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
121
138
  bucket_name, key_path, params_list = $1, $2, $3
122
139
  # select request model
123
- if is_dns_bucket?(bucket_name)
140
+ if !param(:no_subdomains) && is_dns_bucket?(bucket_name)
124
141
  # fix a path
125
142
  server = "#{bucket_name}.#{server}"
126
143
  key_path ||= '/'
@@ -164,12 +181,9 @@ module RightAws
164
181
  # Sends request to Amazon and parses the response.
165
182
  # Raises AwsError if any banana happened.
166
183
  def request_info(request, parser, &block) # :nodoc:
167
- thread = @params[:multi_thread] ? Thread.current : Thread.main
168
- thread[:s3_connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
169
- request_info_impl(thread[:s3_connection], @@bench, request, parser, &block)
184
+ request_info_impl(:s3_connection, @@bench, request, parser, &block)
170
185
  end
171
186
 
172
-
173
187
  # Returns an array of customer's buckets. Each item is a +hash+.
174
188
  #
175
189
  # s3.list_all_my_buckets #=>
@@ -194,8 +208,14 @@ module RightAws
194
208
  #
195
209
  def create_bucket(bucket, headers={})
196
210
  data = nil
197
- unless headers[:location].blank?
198
- data = "<CreateBucketConfiguration><LocationConstraint>#{headers[:location].to_s.upcase}</LocationConstraint></CreateBucketConfiguration>"
211
+ location = case headers[:location].to_s
212
+ when 'us','US' then ''
213
+ when 'eu' then 'EU'
214
+ else headers[:location].to_s
215
+ end
216
+
217
+ unless location.blank?
218
+ data = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
199
219
  end
200
220
  req_hash = generate_rest_request('PUT', headers.merge(:url=>bucket, :data => data))
201
221
  request_info(req_hash, RightHttp2xxParser.new)
@@ -245,7 +265,7 @@ module RightAws
245
265
  AwsUtils.allow_only([:bucket,:xmldoc, :headers], params)
246
266
  params[:headers] = {} unless params[:headers]
247
267
  req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}?logging", :data => params[:xmldoc]))
248
- request_info(req_hash, S3TrueParser.new)
268
+ request_info(req_hash, RightHttp2xxParser.new)
249
269
  rescue
250
270
  on_exception
251
271
  end
@@ -724,7 +744,7 @@ module RightAws
724
744
  else
725
745
  result[:grantees][key] =
726
746
  { :display_name => grantee[:display_name] || grantee[:uri].to_s[/[^\/]*$/],
727
- :permissions => grantee[:permissions].to_a,
747
+ :permissions => Array(grantee[:permissions]),
728
748
  :attributes => grantee[:attributes] }
729
749
  end
730
750
  end
@@ -880,7 +900,7 @@ module RightAws
880
900
  # s3.put_link('my_awesome_bucket',key, object) #=> url string
881
901
  #
882
902
  def put_link(bucket, key, data=nil, expires=nil, headers={})
883
- generate_link('PUT', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}", :data=>data), expires)
903
+ generate_link('PUT', headers.merge(:url=>"#{bucket}/#{CGI::escape key}", :data=>data), expires)
884
904
  rescue
885
905
  on_exception
886
906
  end
@@ -898,7 +918,7 @@ module RightAws
898
918
  #
899
919
  # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html
900
920
  def get_link(bucket, key, expires=nil, headers={})
901
- generate_link('GET', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
921
+ generate_link('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
902
922
  rescue
903
923
  on_exception
904
924
  end
@@ -908,7 +928,7 @@ module RightAws
908
928
  # s3.head_link('my_awesome_bucket',key) #=> url string
909
929
  #
910
930
  def head_link(bucket, key, expires=nil, headers={})
911
- generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
931
+ generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
912
932
  rescue
913
933
  on_exception
914
934
  end
@@ -918,7 +938,7 @@ module RightAws
918
938
  # s3.delete_link('my_awesome_bucket',key) #=> url string
919
939
  #
920
940
  def delete_link(bucket, key, expires=nil, headers={})
921
- generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
941
+ generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
922
942
  rescue
923
943
  on_exception
924
944
  end
@@ -929,7 +949,7 @@ module RightAws
929
949
  # s3.get_acl_link('my_awesome_bucket',key) #=> url string
930
950
  #
931
951
  def get_acl_link(bucket, key='', headers={})
932
- return generate_link('GET', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}?acl"))
952
+ return generate_link('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}?acl"))
933
953
  rescue
934
954
  on_exception
935
955
  end
@@ -939,7 +959,7 @@ module RightAws
939
959
  # s3.put_acl_link('my_awesome_bucket',key) #=> url string
940
960
  #
941
961
  def put_acl_link(bucket, key='', headers={})
942
- return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}?acl"))
962
+ return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{CGI::escape key}?acl"))
943
963
  rescue
944
964
  on_exception
945
965
  end
@@ -978,11 +998,11 @@ module RightAws
978
998
  end
979
999
  def tagend(name)
980
1000
  case name
981
- when 'ID' ; @owner[:owner_id] = @text
982
- when 'DisplayName' ; @owner[:owner_display_name] = @text
983
- when 'Name' ; @current_bucket[:name] = @text
984
- when 'CreationDate'; @current_bucket[:creation_date] = @text
985
- when 'Bucket' ; @result << @current_bucket.merge(@owner)
1001
+ when 'ID' then @owner[:owner_id] = @text
1002
+ when 'DisplayName' then @owner[:owner_display_name] = @text
1003
+ when 'Name' then @current_bucket[:name] = @text
1004
+ when 'CreationDate'then @current_bucket[:creation_date] = @text
1005
+ when 'Bucket' then @result << @current_bucket.merge(@owner)
986
1006
  end
987
1007
  end
988
1008
  end
@@ -999,21 +1019,23 @@ module RightAws
999
1019
  def tagend(name)
1000
1020
  case name
1001
1021
  # service info
1002
- when 'Name' ; @service['name'] = @text
1003
- when 'Prefix' ; @service['prefix'] = @text
1004
- when 'Marker' ; @service['marker'] = @text
1005
- when 'MaxKeys' ; @service['max-keys'] = @text
1006
- when 'Delimiter' ; @service['delimiter'] = @text
1007
- when 'IsTruncated' ; @service['is_truncated'] = (@text =~ /false/ ? false : true)
1022
+ when 'Name' then @service['name'] = @text
1023
+ when 'Prefix' then @service['prefix'] = @text
1024
+ when 'Marker' then @service['marker'] = @text
1025
+ when 'MaxKeys' then @service['max-keys'] = @text
1026
+ when 'Delimiter' then @service['delimiter'] = @text
1027
+ when 'IsTruncated' then @service['is_truncated'] = (@text =~ /false/ ? false : true)
1008
1028
  # key data
1009
- when 'Key' ; @current_key[:key] = @text
1010
- when 'LastModified'; @current_key[:last_modified] = @text
1011
- when 'ETag' ; @current_key[:e_tag] = @text
1012
- when 'Size' ; @current_key[:size] = @text.to_i
1013
- when 'StorageClass'; @current_key[:storage_class] = @text
1014
- when 'ID' ; @current_key[:owner_id] = @text
1015
- when 'DisplayName' ; @current_key[:owner_display_name] = @text
1016
- when 'Contents' ; @current_key[:service] = @service; @result << @current_key
1029
+ when 'Key' then @current_key[:key] = @text
1030
+ when 'LastModified'then @current_key[:last_modified] = @text
1031
+ when 'ETag' then @current_key[:e_tag] = @text
1032
+ when 'Size' then @current_key[:size] = @text.to_i
1033
+ when 'StorageClass'then @current_key[:storage_class] = @text
1034
+ when 'ID' then @current_key[:owner_id] = @text
1035
+ when 'DisplayName' then @current_key[:owner_display_name] = @text
1036
+ when 'Contents'
1037
+ @current_key[:service] = @service
1038
+ @result << @current_key
1017
1039
  end
1018
1040
  end
1019
1041
  end
@@ -1035,27 +1057,29 @@ module RightAws
1035
1057
  def tagend(name)
1036
1058
  case name
1037
1059
  # service info
1038
- when 'Name' ; @result[:name] = @text
1060
+ when 'Name' then @result[:name] = @text
1039
1061
  # Amazon uses the same tag for the search prefix and for the entries
1040
1062
  # in common prefix...so use our simple flag to see which element
1041
1063
  # we are parsing
1042
- when 'Prefix' ; @in_common_prefixes ? @common_prefixes << @text : @result[:prefix] = @text
1043
- when 'Marker' ; @result[:marker] = @text
1044
- when 'MaxKeys' ; @result[:max_keys] = @text
1045
- when 'Delimiter' ; @result[:delimiter] = @text
1046
- when 'IsTruncated' ; @result[:is_truncated] = (@text =~ /false/ ? false : true)
1047
- when 'NextMarker' ; @result[:next_marker] = @text
1064
+ when 'Prefix' then @in_common_prefixes ? @common_prefixes << @text : @result[:prefix] = @text
1065
+ when 'Marker' then @result[:marker] = @text
1066
+ when 'MaxKeys' then @result[:max_keys] = @text
1067
+ when 'Delimiter' then @result[:delimiter] = @text
1068
+ when 'IsTruncated' then @result[:is_truncated] = (@text =~ /false/ ? false : true)
1069
+ when 'NextMarker' then @result[:next_marker] = @text
1048
1070
  # key data
1049
- when 'Key' ; @current_key[:key] = @text
1050
- when 'LastModified'; @current_key[:last_modified] = @text
1051
- when 'ETag' ; @current_key[:e_tag] = @text
1052
- when 'Size' ; @current_key[:size] = @text.to_i
1053
- when 'StorageClass'; @current_key[:storage_class] = @text
1054
- when 'ID' ; @current_key[:owner_id] = @text
1055
- when 'DisplayName' ; @current_key[:owner_display_name] = @text
1056
- when 'Contents' ; @result[:contents] << @current_key
1071
+ when 'Key' then @current_key[:key] = @text
1072
+ when 'LastModified'then @current_key[:last_modified] = @text
1073
+ when 'ETag' then @current_key[:e_tag] = @text
1074
+ when 'Size' then @current_key[:size] = @text.to_i
1075
+ when 'StorageClass'then @current_key[:storage_class] = @text
1076
+ when 'ID' then @current_key[:owner_id] = @text
1077
+ when 'DisplayName' then @current_key[:owner_display_name] = @text
1078
+ when 'Contents' then @result[:contents] << @current_key
1057
1079
  # Common Prefix stuff
1058
- when 'CommonPrefixes' ; @result[:common_prefixes] = @common_prefixes; @in_common_prefixes = false
1080
+ when 'CommonPrefixes'
1081
+ @result[:common_prefixes] = @common_prefixes
1082
+ @in_common_prefixes = false
1059
1083
  end
1060
1084
  end
1061
1085
  end
@@ -1130,8 +1154,8 @@ module RightAws
1130
1154
  end
1131
1155
  def tagend(name)
1132
1156
  case name
1133
- when 'LastModified' : @result[:last_modified] = @text
1134
- when 'ETag' : @result[:e_tag] = @text
1157
+ when 'LastModified' then @result[:last_modified] = @text
1158
+ when 'ETag' then @result[:e_tag] = @text
1135
1159
  end
1136
1160
  end
1137
1161
  end
@@ -92,6 +92,59 @@ module RightAws
92
92
  # # remove domain
93
93
  # Client.delete_domain
94
94
  #
95
+ # # Dynamic attribute accessors
96
+ #
97
+ # class KdClient < RightAws::ActiveSdb::Base
98
+ # end
99
+ #
100
+ # client = KdClient.select(:all, :order => 'expiration').first
101
+ # pp client.attributes #=>
102
+ # {"name"=>["Putin"],
103
+ # "post"=>["president"],
104
+ # "country"=>["Russia"],
105
+ # "expiration"=>["2008"],
106
+ # "id"=>"376d2e00-75b0-11dd-9557-001bfc466dd7",
107
+ # "gender"=>["male"]}
108
+ #
109
+ # pp client.name #=> ["Putin"]
110
+ # pp client.country #=> ["Russia"]
111
+ # pp client.post #=> ["president"]
112
+ #
113
+ # # Columns and simple typecasting
114
+ #
115
+ # class Person < RightAws::ActiveSdb::Base
116
+ # columns do
117
+ # name
118
+ # email
119
+ # score :Integer
120
+ # is_active :Boolean
121
+ # registered_at :DateTime
122
+ # created_at :DateTime, :default => lambda{ Time.now }
123
+ # end
124
+ # end
125
+ # Person::create( :name => 'Yetta E. Andrews', :email => 'nulla.facilisis@metus.com', :score => 100, :is_active => true, :registered_at => Time.local(2000, 1, 1) )
126
+ #
127
+ # person = Person.find_by_email 'nulla.facilisis@metus.com'
128
+ # person.reload
129
+ #
130
+ # pp person.attributes #=>
131
+ # {"name"=>["Yetta E. Andrews"],
132
+ # "created_at"=>["2010-04-02T20:51:58+0400"],
133
+ # "id"=>"0ee24946-3e60-11df-9d4c-0025b37efad0",
134
+ # "registered_at"=>["2000-01-01T00:00:00+0300"],
135
+ # "is_active"=>["T"],
136
+ # "score"=>["100"],
137
+ # "email"=>["nulla.facilisis@metus.com"]}
138
+ # pp person.name #=> "Yetta E. Andrews"
139
+ # pp person.name.class #=> String
140
+ # pp person.registered_at.to_s #=> "2000-01-01T00:00:00+03:00"
141
+ # pp person.registered_at.class #=> DateTime
142
+ # pp person.is_active #=> true
143
+ # pp person.is_active.class #=> TrueClass
144
+ # pp person.score #=> 100
145
+ # pp person.score.class #=> Fixnum
146
+ # pp person.created_at.to_s #=> "2010-04-02T20:51:58+04:00"
147
+ #
95
148
  class ActiveSdb
96
149
 
97
150
  module ActiveSdbConnect
@@ -242,7 +295,31 @@ module RightAws
242
295
  def delete_domain
243
296
  connection.delete_domain(domain)
244
297
  end
245
-
298
+
299
+ def columns(&block)
300
+ @columns ||= ColumnSet.new
301
+ @columns.instance_eval(&block) if block
302
+ @columns
303
+ end
304
+
305
+ def column?(col_name)
306
+ columns.include?(col_name)
307
+ end
308
+
309
+ def type_of(col_name)
310
+ columns.type_of(col_name)
311
+ end
312
+
313
+ def serialize(attribute, value)
314
+ s = serialization_for_type(type_of(attribute))
315
+ s ? s.serialize(value) : value.to_s
316
+ end
317
+
318
+ def deserialize(attribute, value)
319
+ s = serialization_for_type(type_of(attribute))
320
+ s ? s.deserialize(value) : value
321
+ end
322
+
246
323
  # Perform a find request.
247
324
  #
248
325
  # Single record:
@@ -364,7 +441,11 @@ module RightAws
364
441
  end
365
442
 
366
443
  def generate_id # :nodoc:
367
- UUID.timestamp_create().to_s
444
+ if UUID::VERSION::STRING < '2.0.0'
445
+ UUID.timestamp_create().to_s
446
+ else
447
+ UUIDTools::UUID.timestamp_create().to_s
448
+ end
368
449
  end
369
450
 
370
451
  protected
@@ -376,7 +457,7 @@ module RightAws
376
457
  # detect amount of records requested
377
458
  bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
378
459
  # flatten ids
379
- args = args.to_a.flatten
460
+ args = Array(args).flatten
380
461
  args.each { |id| cond << "id=#{self.connection.escape(id)}" }
381
462
  ids_cond = "(#{cond.join(' OR ')})"
382
463
  # user defined :conditions to string (if it was defined)
@@ -518,7 +599,7 @@ module RightAws
518
599
  # detect amount of records requested
519
600
  bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
520
601
  # flatten ids
521
- args = args.to_a.flatten
602
+ args = Array(args).flatten
522
603
  args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
523
604
  ids_cond = "[#{cond.join(' OR ')}]"
524
605
  # user defined :conditions to string (if it was defined)
@@ -590,6 +671,13 @@ module RightAws
590
671
  end
591
672
  end
592
673
 
674
+ def serialization_for_type(type)
675
+ @serializations ||= {}
676
+ unless @serializations.has_key? type
677
+ @serializations[type] = ::RightAws::ActiveSdb.const_get("#{type}Serialization") rescue false
678
+ end
679
+ @serializations[type]
680
+ end
593
681
  end
594
682
 
595
683
  public
@@ -661,7 +749,11 @@ module RightAws
661
749
  self.attributes
662
750
  end
663
751
 
664
- def connection
752
+ def columns
753
+ self.class.columns
754
+ end
755
+
756
+ def connection
665
757
  self.class.connection
666
758
  end
667
759
 
@@ -675,7 +767,8 @@ module RightAws
675
767
  # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
676
768
  #
677
769
  def [](attribute)
678
- @attributes[attribute.to_s]
770
+ raw = @attributes[attribute.to_s]
771
+ self.class.column?(attribute) && raw ? self.class.deserialize(attribute, raw.first) : raw
679
772
  end
680
773
 
681
774
  # Updates the attribute identified by +attribute+ with the specified +values+.
@@ -686,7 +779,14 @@ module RightAws
686
779
  #
687
780
  def []=(attribute, values)
688
781
  attribute = attribute.to_s
689
- @attributes[attribute] = attribute == 'id' ? values.to_s : values.to_a.uniq
782
+ @attributes[attribute] = case
783
+ when attribute == 'id'
784
+ values.to_s
785
+ when self.class.column?(attribute)
786
+ self.class.serialize(attribute, values)
787
+ else
788
+ Array(values).uniq
789
+ end
690
790
  end
691
791
 
692
792
  # Reload attributes from SDB. Replaces in-memory attributes.
@@ -906,25 +1006,117 @@ module RightAws
906
1006
  @new_record = false
907
1007
  end
908
1008
 
909
- private
910
-
1009
+ # support accessing attribute values via method call
1010
+ def method_missing(method_sym, *args)
1011
+ method_name = method_sym.to_s
1012
+ setter = method_name[-1,1] == '='
1013
+ method_name.chop! if setter
1014
+
1015
+ if @attributes.has_key?(method_name) || self.class.column?(method_name)
1016
+ setter ? self[method_name] = args.first : self[method_name]
1017
+ else
1018
+ super
1019
+ end
1020
+ end
1021
+
1022
+ private
1023
+
911
1024
  def raise_on_id_absence
912
1025
  raise ActiveSdbError.new('Unknown record id') unless id
913
1026
  end
914
1027
 
915
1028
  def prepare_for_update
916
1029
  @attributes['id'] = self.class.generate_id if @attributes['id'].blank?
1030
+ columns.all.each do |col_name|
1031
+ self[col_name] ||= columns.default(col_name)
1032
+ end
917
1033
  end
918
1034
 
919
1035
  def uniq_values(attributes=nil) # :nodoc:
920
1036
  attrs = {}
921
1037
  attributes.each do |attribute, values|
922
1038
  attribute = attribute.to_s
923
- attrs[attribute] = attribute == 'id' ? values.to_s : values.to_a.uniq
1039
+ attrs[attribute] = case
1040
+ when attribute == 'id'
1041
+ values.to_s
1042
+ when self.class.column?(attribute)
1043
+ values.is_a?(String) ? values : self.class.serialize(attribute, values)
1044
+ else
1045
+ Array(values).uniq
1046
+ end
924
1047
  attrs.delete(attribute) if values.blank?
925
1048
  end
926
1049
  attrs
927
1050
  end
928
1051
  end
1052
+
1053
+ class ColumnSet
1054
+ attr_accessor :columns
1055
+ def initialize
1056
+ @columns = {}
1057
+ end
1058
+
1059
+ def all
1060
+ @columns.keys
1061
+ end
1062
+
1063
+ def column(col_name)
1064
+ @columns[col_name.to_s]
1065
+ end
1066
+ alias_method :include?, :column
1067
+
1068
+ def type_of(col_name)
1069
+ column(col_name) && column(col_name)[:type]
1070
+ end
1071
+
1072
+ def default(col_name)
1073
+ return nil unless include?(col_name)
1074
+ default = column(col_name)[:default]
1075
+ default.respond_to?(:call) ? default.call : default
1076
+ end
1077
+
1078
+ def method_missing(method_sym, *args)
1079
+ data_type = args.shift || :String
1080
+ options = args.shift || {}
1081
+ @columns[method_sym.to_s] = options.merge( :type => data_type )
1082
+ end
1083
+ end
1084
+
1085
+ class DateTimeSerialization
1086
+ class << self
1087
+ def serialize(date)
1088
+ date.strftime('%Y-%m-%dT%H:%M:%S%z')
1089
+ end
1090
+
1091
+ def deserialize(string)
1092
+ r = DateTime.parse(string) rescue nil
1093
+ end
1094
+ end
1095
+ end
1096
+
1097
+ class BooleanSerialization
1098
+ class << self
1099
+ def serialize(boolean)
1100
+ boolean ? 'T' : 'F'
1101
+ end
1102
+
1103
+ def deserialize(string)
1104
+ string == 'T'
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ class IntegerSerialization
1110
+ class << self
1111
+ def serialize(int)
1112
+ int.to_s
1113
+ end
1114
+
1115
+ def deserialize(string)
1116
+ string.to_i
1117
+ end
1118
+ end
1119
+ end
1120
+
929
1121
  end
930
- end
1122
+ end