right_aws 1.10.0 → 2.0.0

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