aws-sdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. data/.yardopts +6 -0
  2. data/LICENSE.txt +171 -0
  3. data/NOTICE.txt +2 -0
  4. data/README.rdoc +189 -0
  5. data/lib/aws-sdk.rb +14 -0
  6. data/lib/aws.rb +63 -0
  7. data/lib/aws/api_config.rb +45 -0
  8. data/lib/aws/api_config/.document +0 -0
  9. data/lib/aws/api_config/EC2-2011-02-28.yml +2314 -0
  10. data/lib/aws/api_config/SNS-2010-03-31.yml +171 -0
  11. data/lib/aws/api_config/SQS-2009-02-01.yml +161 -0
  12. data/lib/aws/api_config/SimpleDB-2009-04-15.yml +278 -0
  13. data/lib/aws/api_config/SimpleEmailService-2010-12-01.yml +147 -0
  14. data/lib/aws/api_config_transform.rb +32 -0
  15. data/lib/aws/async_handle.rb +90 -0
  16. data/lib/aws/authorize_v2.rb +37 -0
  17. data/lib/aws/authorize_v3.rb +37 -0
  18. data/lib/aws/base_client.rb +524 -0
  19. data/lib/aws/cacheable.rb +92 -0
  20. data/lib/aws/common.rb +228 -0
  21. data/lib/aws/configurable.rb +36 -0
  22. data/lib/aws/configuration.rb +272 -0
  23. data/lib/aws/configured_client_methods.rb +81 -0
  24. data/lib/aws/configured_grammars.rb +65 -0
  25. data/lib/aws/configured_option_grammars.rb +46 -0
  26. data/lib/aws/configured_xml_grammars.rb +47 -0
  27. data/lib/aws/default_signer.rb +38 -0
  28. data/lib/aws/ec2.rb +321 -0
  29. data/lib/aws/ec2/attachment.rb +149 -0
  30. data/lib/aws/ec2/attachment_collection.rb +57 -0
  31. data/lib/aws/ec2/availability_zone.rb +80 -0
  32. data/lib/aws/ec2/availability_zone_collection.rb +47 -0
  33. data/lib/aws/ec2/block_device_mappings.rb +53 -0
  34. data/lib/aws/ec2/client.rb +54 -0
  35. data/lib/aws/ec2/client/xml.rb +127 -0
  36. data/lib/aws/ec2/collection.rb +39 -0
  37. data/lib/aws/ec2/config_transform.rb +63 -0
  38. data/lib/aws/ec2/elastic_ip.rb +107 -0
  39. data/lib/aws/ec2/elastic_ip_collection.rb +85 -0
  40. data/lib/aws/ec2/errors.rb +29 -0
  41. data/lib/aws/ec2/filtered_collection.rb +65 -0
  42. data/lib/aws/ec2/has_permissions.rb +46 -0
  43. data/lib/aws/ec2/image.rb +245 -0
  44. data/lib/aws/ec2/image_collection.rb +235 -0
  45. data/lib/aws/ec2/instance.rb +515 -0
  46. data/lib/aws/ec2/instance_collection.rb +276 -0
  47. data/lib/aws/ec2/key_pair.rb +86 -0
  48. data/lib/aws/ec2/key_pair_collection.rb +102 -0
  49. data/lib/aws/ec2/permission_collection.rb +177 -0
  50. data/lib/aws/ec2/region.rb +81 -0
  51. data/lib/aws/ec2/region_collection.rb +55 -0
  52. data/lib/aws/ec2/request.rb +27 -0
  53. data/lib/aws/ec2/reserved_instances.rb +50 -0
  54. data/lib/aws/ec2/reserved_instances_collection.rb +44 -0
  55. data/lib/aws/ec2/reserved_instances_offering.rb +55 -0
  56. data/lib/aws/ec2/reserved_instances_offering_collection.rb +43 -0
  57. data/lib/aws/ec2/resource.rb +340 -0
  58. data/lib/aws/ec2/resource_tag_collection.rb +218 -0
  59. data/lib/aws/ec2/security_group.rb +246 -0
  60. data/lib/aws/ec2/security_group/ip_permission.rb +70 -0
  61. data/lib/aws/ec2/security_group/ip_permission_collection.rb +59 -0
  62. data/lib/aws/ec2/security_group_collection.rb +132 -0
  63. data/lib/aws/ec2/snapshot.rb +138 -0
  64. data/lib/aws/ec2/snapshot_collection.rb +90 -0
  65. data/lib/aws/ec2/tag.rb +88 -0
  66. data/lib/aws/ec2/tag_collection.rb +114 -0
  67. data/lib/aws/ec2/tagged_collection.rb +48 -0
  68. data/lib/aws/ec2/tagged_item.rb +87 -0
  69. data/lib/aws/ec2/volume.rb +190 -0
  70. data/lib/aws/ec2/volume_collection.rb +95 -0
  71. data/lib/aws/errors.rb +129 -0
  72. data/lib/aws/http/builtin_handler.rb +69 -0
  73. data/lib/aws/http/curb_handler.rb +123 -0
  74. data/lib/aws/http/handler.rb +77 -0
  75. data/lib/aws/http/httparty_handler.rb +61 -0
  76. data/lib/aws/http/request.rb +136 -0
  77. data/lib/aws/http/request_param.rb +63 -0
  78. data/lib/aws/http/response.rb +75 -0
  79. data/lib/aws/ignore_result_element.rb +38 -0
  80. data/lib/aws/indifferent_hash.rb +86 -0
  81. data/lib/aws/inflection.rb +46 -0
  82. data/lib/aws/lazy_error_classes.rb +64 -0
  83. data/lib/aws/meta_utils.rb +43 -0
  84. data/lib/aws/model.rb +57 -0
  85. data/lib/aws/naming.rb +32 -0
  86. data/lib/aws/option_grammar.rb +544 -0
  87. data/lib/aws/policy.rb +912 -0
  88. data/lib/aws/rails.rb +209 -0
  89. data/lib/aws/record.rb +79 -0
  90. data/lib/aws/record/attribute.rb +94 -0
  91. data/lib/aws/record/attribute_macros.rb +288 -0
  92. data/lib/aws/record/attributes/boolean.rb +49 -0
  93. data/lib/aws/record/attributes/datetime.rb +86 -0
  94. data/lib/aws/record/attributes/float.rb +48 -0
  95. data/lib/aws/record/attributes/integer.rb +68 -0
  96. data/lib/aws/record/attributes/sortable_float.rb +60 -0
  97. data/lib/aws/record/attributes/sortable_integer.rb +95 -0
  98. data/lib/aws/record/attributes/string.rb +69 -0
  99. data/lib/aws/record/base.rb +728 -0
  100. data/lib/aws/record/conversion.rb +38 -0
  101. data/lib/aws/record/dirty_tracking.rb +286 -0
  102. data/lib/aws/record/errors.rb +153 -0
  103. data/lib/aws/record/exceptions.rb +48 -0
  104. data/lib/aws/record/finder_methods.rb +262 -0
  105. data/lib/aws/record/naming.rb +31 -0
  106. data/lib/aws/record/scope.rb +157 -0
  107. data/lib/aws/record/validations.rb +653 -0
  108. data/lib/aws/record/validator.rb +237 -0
  109. data/lib/aws/record/validators/acceptance.rb +51 -0
  110. data/lib/aws/record/validators/block.rb +38 -0
  111. data/lib/aws/record/validators/confirmation.rb +43 -0
  112. data/lib/aws/record/validators/count.rb +108 -0
  113. data/lib/aws/record/validators/exclusion.rb +43 -0
  114. data/lib/aws/record/validators/format.rb +57 -0
  115. data/lib/aws/record/validators/inclusion.rb +56 -0
  116. data/lib/aws/record/validators/length.rb +107 -0
  117. data/lib/aws/record/validators/numericality.rb +138 -0
  118. data/lib/aws/record/validators/presence.rb +45 -0
  119. data/lib/aws/resource_cache.rb +39 -0
  120. data/lib/aws/response.rb +113 -0
  121. data/lib/aws/response_cache.rb +50 -0
  122. data/lib/aws/s3.rb +109 -0
  123. data/lib/aws/s3/access_control_list.rb +252 -0
  124. data/lib/aws/s3/acl_object.rb +266 -0
  125. data/lib/aws/s3/bucket.rb +320 -0
  126. data/lib/aws/s3/bucket_collection.rb +122 -0
  127. data/lib/aws/s3/bucket_version_collection.rb +85 -0
  128. data/lib/aws/s3/client.rb +999 -0
  129. data/lib/aws/s3/client/xml.rb +190 -0
  130. data/lib/aws/s3/data_options.rb +99 -0
  131. data/lib/aws/s3/errors.rb +43 -0
  132. data/lib/aws/s3/multipart_upload.rb +318 -0
  133. data/lib/aws/s3/multipart_upload_collection.rb +78 -0
  134. data/lib/aws/s3/object_collection.rb +159 -0
  135. data/lib/aws/s3/object_metadata.rb +67 -0
  136. data/lib/aws/s3/object_upload_collection.rb +83 -0
  137. data/lib/aws/s3/object_version.rb +141 -0
  138. data/lib/aws/s3/object_version_collection.rb +78 -0
  139. data/lib/aws/s3/paginated_collection.rb +94 -0
  140. data/lib/aws/s3/policy.rb +76 -0
  141. data/lib/aws/s3/prefix_and_delimiter_collection.rb +56 -0
  142. data/lib/aws/s3/prefixed_collection.rb +84 -0
  143. data/lib/aws/s3/presigned_post.rb +504 -0
  144. data/lib/aws/s3/request.rb +198 -0
  145. data/lib/aws/s3/s3_object.rb +794 -0
  146. data/lib/aws/s3/tree.rb +116 -0
  147. data/lib/aws/s3/tree/branch_node.rb +71 -0
  148. data/lib/aws/s3/tree/child_collection.rb +108 -0
  149. data/lib/aws/s3/tree/leaf_node.rb +99 -0
  150. data/lib/aws/s3/tree/node.rb +22 -0
  151. data/lib/aws/s3/tree/parent.rb +90 -0
  152. data/lib/aws/s3/uploaded_part.rb +82 -0
  153. data/lib/aws/s3/uploaded_part_collection.rb +86 -0
  154. data/lib/aws/service_interface.rb +60 -0
  155. data/lib/aws/simple_db.rb +202 -0
  156. data/lib/aws/simple_db/attribute.rb +159 -0
  157. data/lib/aws/simple_db/attribute_collection.rb +227 -0
  158. data/lib/aws/simple_db/client.rb +52 -0
  159. data/lib/aws/simple_db/client/options.rb +34 -0
  160. data/lib/aws/simple_db/client/xml.rb +68 -0
  161. data/lib/aws/simple_db/consistent_read_option.rb +42 -0
  162. data/lib/aws/simple_db/delete_attributes.rb +64 -0
  163. data/lib/aws/simple_db/domain.rb +118 -0
  164. data/lib/aws/simple_db/domain_collection.rb +116 -0
  165. data/lib/aws/simple_db/domain_metadata.rb +112 -0
  166. data/lib/aws/simple_db/errors.rb +46 -0
  167. data/lib/aws/simple_db/expect_condition_option.rb +45 -0
  168. data/lib/aws/simple_db/item.rb +84 -0
  169. data/lib/aws/simple_db/item_collection.rb +594 -0
  170. data/lib/aws/simple_db/item_data.rb +70 -0
  171. data/lib/aws/simple_db/put_attributes.rb +62 -0
  172. data/lib/aws/simple_db/request.rb +27 -0
  173. data/lib/aws/simple_email_service.rb +373 -0
  174. data/lib/aws/simple_email_service/client.rb +39 -0
  175. data/lib/aws/simple_email_service/client/options.rb +24 -0
  176. data/lib/aws/simple_email_service/client/xml.rb +38 -0
  177. data/lib/aws/simple_email_service/email_address_collection.rb +66 -0
  178. data/lib/aws/simple_email_service/errors.rb +29 -0
  179. data/lib/aws/simple_email_service/quotas.rb +64 -0
  180. data/lib/aws/simple_email_service/request.rb +27 -0
  181. data/lib/aws/sns.rb +69 -0
  182. data/lib/aws/sns/client.rb +37 -0
  183. data/lib/aws/sns/client/options.rb +24 -0
  184. data/lib/aws/sns/client/xml.rb +38 -0
  185. data/lib/aws/sns/errors.rb +29 -0
  186. data/lib/aws/sns/policy.rb +49 -0
  187. data/lib/aws/sns/request.rb +27 -0
  188. data/lib/aws/sns/subscription.rb +100 -0
  189. data/lib/aws/sns/subscription_collection.rb +84 -0
  190. data/lib/aws/sns/topic.rb +384 -0
  191. data/lib/aws/sns/topic_collection.rb +70 -0
  192. data/lib/aws/sns/topic_subscription_collection.rb +58 -0
  193. data/lib/aws/sqs.rb +70 -0
  194. data/lib/aws/sqs/client.rb +38 -0
  195. data/lib/aws/sqs/client/xml.rb +36 -0
  196. data/lib/aws/sqs/errors.rb +33 -0
  197. data/lib/aws/sqs/policy.rb +50 -0
  198. data/lib/aws/sqs/queue.rb +507 -0
  199. data/lib/aws/sqs/queue_collection.rb +105 -0
  200. data/lib/aws/sqs/received_message.rb +184 -0
  201. data/lib/aws/sqs/received_sns_message.rb +112 -0
  202. data/lib/aws/sqs/request.rb +44 -0
  203. data/lib/aws/xml_grammar.rb +923 -0
  204. data/rails/init.rb +15 -0
  205. metadata +298 -0
@@ -0,0 +1,728 @@
1
+ # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ # todo move these to included modules (like validations and naming)
15
+
16
+ require 'set'
17
+ require 'uuidtools'
18
+ require 'aws/indifferent_hash'
19
+
20
+ require 'aws/record/naming'
21
+ require 'aws/record/attribute_macros'
22
+ require 'aws/record/finder_methods'
23
+ require 'aws/record/validations'
24
+ require 'aws/record/dirty_tracking'
25
+ require 'aws/record/conversion'
26
+ require 'aws/record/errors'
27
+ require 'aws/record/exceptions'
28
+
29
+ module AWS
30
+ module Record
31
+
32
+ # An ActiveRecord-like interface built ontop of AWS.
33
+ #
34
+ # class Book < AWS::Record::Base
35
+ #
36
+ # string_attr :title
37
+ # string_attr :author
38
+ # integer :number_of_pages
39
+ #
40
+ # timestamps # adds a :created_at and :updated_at pair of timestamps
41
+ #
42
+ # end
43
+ #
44
+ # b = Book.new(:title => 'My Book', :author => 'Me', :pages => 1)
45
+ # b.save
46
+ #
47
+ # = Attribute Macros
48
+ #
49
+ # When extending AWS::Record::Base you should first consider what
50
+ # attributes your class should have. Unlike ActiveRecord, AWS::Record
51
+ # models are not backed by a database table/schema. You must choose what
52
+ # attributes (and what types) you need.
53
+ #
54
+ # * +string_attr+
55
+ # * +boolean_attr+
56
+ # * +integer_attr+
57
+ # * +float_attr+
58
+ # * +datetime_attr+
59
+ #
60
+ # For more information about the various attribute macros available,
61
+ # and what options they accept, see {AttributeMacros}.
62
+ #
63
+ # === Usage
64
+ #
65
+ # Normally you just call these methods inside your model class definition:
66
+ #
67
+ # class Book < AWS::Record::Base
68
+ # string_attr :title
69
+ # boolean_attr :has_been_read
70
+ # integer_attr :number_of_pages
71
+ # float_attr :weight_in_pounds
72
+ # datetime_attr :published_at
73
+ # end
74
+ #
75
+ # For each attribute macro a pair of setter/getter methods are added #
76
+ # to your class (and a few other useful methods).
77
+ #
78
+ # b = Book.new
79
+ # b.title = "My Book"
80
+ # b.has_been_read = true
81
+ # b.number_of_pages = 1000
82
+ # b.weight_in_pounds = 1.1
83
+ # b.published_at = Time.now
84
+ # b.save
85
+ #
86
+ # b.id #=> "0aa894ca-8223-4d34-831e-e5134b2bb71c"
87
+ # b.attributes
88
+ # #=> { 'title' => 'My Book', 'has_been_read' => true, ... }
89
+ #
90
+ # === Default Values
91
+ #
92
+ # All attribute macros accept the +:default_value+ option. This sets
93
+ # a value that is populated onto all new instnaces of the class.
94
+ #
95
+ # class Book < AWS::Record::Base
96
+ # string_attr :author, :deafult_value => 'Me'
97
+ # end
98
+ #
99
+ # Book.new.author #=> 'Me'
100
+ #
101
+ # === Multi-Valued (Set) Attributes
102
+ #
103
+ # AWS::Record permits storing multiple values with a single attribute.
104
+ #
105
+ # class Book < AWS::Record::Base
106
+ # string_attr :tags, :set => true
107
+ # end
108
+ #
109
+ # b = Book.new
110
+ # b.tags #=> #<Set: {}>
111
+ #
112
+ # b.tags = ['fiction', 'fantasy']
113
+ # b.tags #=> #<Set: {'fiction', 'fantasy'}>
114
+ #
115
+ # These multi-valued attributes are treated as sets, not arrays. This
116
+ # means:
117
+ #
118
+ # * values are unordered
119
+ # * duplicate values are automatically omitted
120
+ #
121
+ # Please consider these limitations when you choose to use the +:set+
122
+ # option with the attribute macros.
123
+ #
124
+ # = Validations
125
+ #
126
+ # It's important to validate models before there are persisted to keep
127
+ # your data clean. AWS::Record supports most of the ActiveRecord style
128
+ # validators.
129
+ #
130
+ # class Book < AWS::Record::Base
131
+ # string_attr :title
132
+ # validates_presence_of :title
133
+ # end
134
+ #
135
+ # b = Book.new
136
+ # b.valid? #=> false
137
+ # b.errors.full_messages #=> ['Title may not be blank']
138
+ #
139
+ # Validations are checked before saving a record. If any of the validators
140
+ # adds an error, the the save will fail.
141
+ #
142
+ # For more information about the available validation methods see
143
+ # {Validations}.
144
+ #
145
+ # = Finder Methods
146
+ #
147
+ # You can find records by their ID. Each record gets a UUID when it
148
+ # is saved for the first time. You can use this ID to fetch the record
149
+ # at a latter time:
150
+ #
151
+ # b = Book["0aa894ca-8223-4d34-831e-e5134b2bb71c"]
152
+ #
153
+ # b = Book.find("0aa894ca-8223-4d34-831e-e5134b2bb71c")
154
+ #
155
+ # If you try to find a record by ID that has no data an error will
156
+ # be raised.
157
+ #
158
+ # === All
159
+ #
160
+ # You can enumerate all of your records using +all+.
161
+ #
162
+ # Book.all.each do |book|
163
+ # puts book.id
164
+ # end
165
+ #
166
+ # Book.find(:all) do |book|
167
+ # puts book.id
168
+ # end
169
+ #
170
+ # Be careful when enumerating all. Depending on the number of records
171
+ # and number of attributes each record has, this can take a while,
172
+ # causing quite a few requests.
173
+ #
174
+ # === First
175
+ #
176
+ # If you only want a single record, you should use +first+.
177
+ #
178
+ # b = Book.first
179
+ #
180
+ # === Modifiers
181
+ #
182
+ # Frequently you do not want ALL records or the very first record. You
183
+ # can pass options to +find+, +all+ and +first+.
184
+ #
185
+ # my_books = Book.find(:all, :where => 'owner = "Me"')
186
+ #
187
+ # book = Book.first(:where => { :has_been_read => false })
188
+ #
189
+ # You can pass as find options:
190
+ #
191
+ # * +:where+ - Conditions that must be met to be returned
192
+ # * +:order+ - The order to sort matched records by
193
+ # * +:limit+ - The maximum number of records to return
194
+ #
195
+ # = Scopes
196
+ #
197
+ # More useful than writing query fragments all over the place is to
198
+ # name your most common conditions for reuse.
199
+ #
200
+ # class Book < AWS::Record::Base
201
+ #
202
+ # scope :mine, where(:owner => 'Me')
203
+ #
204
+ # scope :unread, where(:has_been_read => false)
205
+ #
206
+ # scope :by_popularity, order(:score, :desc)
207
+ #
208
+ # scope :top_10, by_popularity.limit(10)
209
+ #
210
+ # end
211
+ #
212
+ # # The following expression returns 10 books that belong
213
+ # # to me, that are unread sorted by popularity.
214
+ # next_good_reads = Book.mine.unread.top_10
215
+ #
216
+ # There are 3 standard scope methods:
217
+ #
218
+ # * +where+
219
+ # * +order+
220
+ # * +limit+
221
+ #
222
+ # === Conditions (where)
223
+ #
224
+ # Where accepts aruments in a number of forms:
225
+ #
226
+ # 1. As an sql-like fragment. If you need to escape values this form is
227
+ # not suggested.
228
+ #
229
+ # Book.where('title = "My Book"')
230
+ #
231
+ # 2. An sql-like fragment, with placeholders. This escapes quoted
232
+ # arguments properly to avoid injection.
233
+ #
234
+ # Book.where('title = ?', 'My Book')
235
+ #
236
+ # 3. A hash of key-value pairs. This is the simplest form, but also the
237
+ # least flexible. You can not use this form if you need more complex
238
+ # expressions that use or.
239
+ #
240
+ # Book.where(:title => 'My Book')
241
+ #
242
+ # === Order
243
+ #
244
+ # This orders the records as returned by AWS. Default ordering is ascending.
245
+ # Pass the value :desc as a second argument to sort in reverse ordering.
246
+ #
247
+ # Book.order(:title) # alphabetical ordering
248
+ # Book.order(:title, :desc) # reverse alphabetical ordering
249
+ #
250
+ # You may only order by a single attribute. If you call order twice in the
251
+ # chain, the last call gets presedence:
252
+ #
253
+ # Book.order(:title).order(:price)
254
+ #
255
+ # In this example the books will be ordered by :price and the order(:title)
256
+ # is lost.
257
+ #
258
+ # === Limit
259
+ #
260
+ # Just call +limit+ with an integer argument. This sets the maximum
261
+ # number of records to retrieve:
262
+ #
263
+ # Book.limit(2)
264
+ #
265
+ # === Delayed Execution
266
+ #
267
+ # It should be noted that all finds are lazy (except +first+). This
268
+ # means the value returned is not an array of records, rather a handle
269
+ # to a {Scope} object that will return records when you enumerate over them.
270
+ #
271
+ # This allows you to build an expression without making unecessary requests.
272
+ # In the following example no request is made until the call to
273
+ # each_with_index.
274
+ #
275
+ # all_books = Books.all
276
+ # ten_books = all_books.limit(10)
277
+ #
278
+ # ten_books.each_with_index do |book,n|
279
+ # puts "#{n + 1} : #{book.title}"
280
+ # end
281
+ #
282
+ class Base
283
+
284
+ # for rails 3+ active model compatability
285
+ extend Naming
286
+ include Naming
287
+
288
+ extend Validations
289
+ extend AttributeMacros
290
+ extend FinderMethods
291
+ include Conversion
292
+ include DirtyTracking
293
+
294
+ # Constructs a new record for this class/domain.
295
+ #
296
+ # @param [Hash] attributes A set of attribute values to seed this record
297
+ # with. The attributes are bulk assigned.
298
+ # @return [Base] Returns a new record that has not been persisted yet.
299
+ def initialize attributes = {}
300
+ @_data = {}
301
+ assign_default_values
302
+ bulk_assign(attributes)
303
+ end
304
+
305
+ # The id for each record is auto-generated. The default strategy
306
+ # generates uuid strings.
307
+ # @return [String] Returns the id string (uuid) for this record. Retuns
308
+ # nil if this is a new record that has not been persisted yet.
309
+ def id
310
+ @_id
311
+ end
312
+
313
+ # @return [Hash] A hash with attribute names as hash keys (strings) and
314
+ # attribute values (of mixed types) as hash values.
315
+ def attributes
316
+ attributes = IndifferentHash.new
317
+ attributes['id'] = id if persisted?
318
+ self.class.attributes.keys.inject(attributes) do |hash,attr_name|
319
+ hash[attr_name] = __send__(attr_name)
320
+ hash
321
+ end
322
+ end
323
+
324
+ # Persistence indicates if the record has been saved previously or not.
325
+ #
326
+ # @example
327
+ # @recipe = Recipe.new(:name => 'Buttermilk Pancackes')
328
+ # @recipe.persisted? #=> false
329
+ # @recipe.save!
330
+ # @recipe.persisted? #=> true
331
+ #
332
+ # @return [Boolean] Returns true if this record has been persisted.
333
+ def persisted?
334
+ !!@_persisted
335
+ end
336
+
337
+ # @return [Boolean] Returns true if this record has not been persisted
338
+ # to SimpleDB.
339
+ def new_record?
340
+ !persisted?
341
+ end
342
+
343
+ # @return [Boolean] Returns true if this record has no validation errors.
344
+ def valid?
345
+ validate
346
+ errors.empty?
347
+ end
348
+
349
+ # Creates new records, updates existing records.
350
+ # @return [Boolean] Returns true if the record saved without errors,
351
+ # false otherwise.
352
+ def save
353
+ if valid?
354
+ persisted? ? update : create
355
+ clear_changes!
356
+ true
357
+ else
358
+ false
359
+ end
360
+ end
361
+
362
+ # Creates new records, updates exsting records. If there is a validation
363
+ # error then an exception is raised.
364
+ # @raise [InvalidRecordError] Raised when the record has validation
365
+ # errors and can not be saved.
366
+ # @return [true] Returns true after a successful save.
367
+ def save!
368
+ raise InvalidRecordError.new(self) unless save
369
+ true
370
+ end
371
+
372
+ # Bulk assigns the attributes and then saves the record.
373
+ # @param [Hash] attribute_hash A hash of attribute names (keys) and
374
+ # attribute values to assign to this record.
375
+ # @return (see #save)
376
+ def update_attributes attribute_hash
377
+ bulk_assign(attribute_hash)
378
+ save
379
+ end
380
+
381
+ # Bulk assigns the attributes and then saves the record. Raises
382
+ # an exception (AWS::Record::InvalidRecordError) if the record is not
383
+ # valid.
384
+ # @param (see #update_attributes)
385
+ # @return [true]
386
+ def update_attributes! attribute_hash
387
+ if update_attributes(attribute_hash)
388
+ true
389
+ else
390
+ raise InvalidRecordError.new(self)
391
+ end
392
+ end
393
+
394
+ # Deletes the record.
395
+ # @return (see #delete_item)
396
+ def delete
397
+ if persisted?
398
+ if deleted?
399
+ raise 'unable to delete, this object has already been deleted'
400
+ else
401
+ delete_item
402
+ end
403
+ else
404
+ raise 'unable to delete, this object has not been saved yet'
405
+ end
406
+ end
407
+
408
+ # @return [Boolean] Returns true if this instance object has been deleted.
409
+ def deleted?
410
+ persisted? ? !!@_deleted : false
411
+ end
412
+
413
+ # If you define a custom setter, you use #[]= to set the value
414
+ # on the record.
415
+ #
416
+ # class Book < AWS::Record::Base
417
+ #
418
+ # string_attr :name
419
+ #
420
+ # # replace the default #author= method
421
+ # def author= name
422
+ # self['author'] = name.blank? ? 'Anonymous' : name
423
+ # end
424
+ #
425
+ # end
426
+ #
427
+ # @param [String,Symbol] The attribute name to set a value for
428
+ # @param attribute_value The value to assign.
429
+ protected
430
+ def []= attribute_name, new_value
431
+ self.class.attribute_for(attribute_name) do |attribute|
432
+
433
+ if_tracking_changes do
434
+ original_value = type_cast(attribute, attribute_was(attribute.name))
435
+ incoming_value = type_cast(attribute, new_value)
436
+ if original_value == incoming_value
437
+ clear_change!(attribute.name)
438
+ else
439
+ attribute_will_change!(attribute.name)
440
+ end
441
+ end
442
+
443
+ @_data[attribute.name] = new_value
444
+
445
+ end
446
+ end
447
+
448
+ # Returns the typecasted value for the named attribute.
449
+ #
450
+ # book = Book.new(:title => 'My Book')
451
+ # book['title'] #=> 'My Book'
452
+ # book.title #=> 'My Book'
453
+ #
454
+ # === Intended Use
455
+ #
456
+ # This method's primary use is for getting/setting the value for
457
+ # an attribute inside a custom method:
458
+ #
459
+ # class Book < AWS::Record::Base
460
+ #
461
+ # string_attr :title
462
+ #
463
+ # def title
464
+ # self['title'] ? self['title'].upcase : nil
465
+ # end
466
+ #
467
+ # end
468
+ #
469
+ # book = Book.new(:title => 'My Book')
470
+ # book.title #=> 'MY BOOK'
471
+ #
472
+ # @param [String,Symbol] attribute_name The name of the attribute to fetch
473
+ # a value for.
474
+ # @return The current type-casted value for the named attribute.
475
+ protected
476
+ def [] attribute_name
477
+ self.class.attribute_for(attribute_name) do |attribute|
478
+ type_cast(attribute, @_data[attribute.name])
479
+ end
480
+ end
481
+
482
+ # @return [SimpleDB::Item] Returns a reference to the item as stored in
483
+ # simple db.
484
+ # @private
485
+ private
486
+ def sdb_item
487
+ self.class.sdb_domain.items[id]
488
+ end
489
+
490
+ # @private
491
+ private
492
+ def assign_default_values
493
+ # populate default attribute values
494
+ ignore_changes do
495
+ self.class.attributes.values.each do |attribute|
496
+ begin
497
+ # copy default values down so methods like #gsub! don't
498
+ # modify the default values for other objects
499
+ @_data[attribute.name] = attribute.default_value.clone
500
+ rescue TypeError
501
+ @_data[attribute.name] = attribute.default_value
502
+ end
503
+ end
504
+ end
505
+ end
506
+
507
+ # @return [true]
508
+ # @private
509
+ private
510
+ def delete_item
511
+ options = {}
512
+ add_optimistic_lock_expectation(options)
513
+ sdb_item.delete(options)
514
+ @_deleted = true
515
+ end
516
+
517
+ # @private
518
+ private
519
+ def bulk_assign hash
520
+ hash.each_pair do |attribute_name, attribute_value|
521
+ __send__("#{attribute_name}=", attribute_value)
522
+ end
523
+ end
524
+
525
+ # @private
526
+ # @todo need to do something about partial hyrdation of attributes
527
+ private
528
+ def hydrate id, data
529
+ @_id = id
530
+
531
+ # New objects are populated with default values, but we don't
532
+ # want these values to hang around when hydrating persisted values
533
+ # (those values may have been blanked out before save).
534
+ self.class.attributes.values.each do |attribute|
535
+ @_data[attribute.name] = nil
536
+ end
537
+
538
+ ignore_changes do
539
+ bulk_assign(deserialize_item_data(data))
540
+ end
541
+
542
+ @_persisted = true
543
+
544
+ end
545
+
546
+ # This function accepts a hash of item data (as returned from
547
+ # AttributeCollection#to_h or ItemData#attributes) and returns only
548
+ # the key/value pairs that are configured attribues for this class.
549
+ # @private
550
+ private
551
+ def deserialize_item_data item_data
552
+
553
+ marked_for_deletion = item_data['_delete_'] || []
554
+
555
+ data = {}
556
+ item_data.each_pair do |attr_name,values|
557
+
558
+ attribute = self.class.attributes[attr_name]
559
+
560
+ next unless attribute
561
+ next if marked_for_deletion.include?(attr_name)
562
+
563
+ if attribute.set?
564
+ data[attr_name] = values.map{|v| attribute.deserialize(v) }
565
+ else
566
+ data[attr_name] = attribute.deserialize(values.first)
567
+ end
568
+
569
+ end
570
+ data
571
+ end
572
+
573
+ # @private
574
+ private
575
+ def create
576
+
577
+ populate_id
578
+ touch_timestamps('created_at', 'updated_at')
579
+ increment_optimistic_lock_value
580
+
581
+ to_add = serialize_attributes
582
+
583
+ add_optimistic_lock_expectation(to_add)
584
+ sdb_item.attributes.add(to_add)
585
+
586
+ @_persisted = true
587
+
588
+ end
589
+
590
+ # @private
591
+ private
592
+ def update
593
+
594
+ return unless changed?
595
+
596
+ touch_timestamps('updated_at')
597
+ increment_optimistic_lock_value
598
+
599
+ to_update = {}
600
+ to_delete = []
601
+
602
+ # serialized_attributes will raise error if the entire record is blank
603
+ attribute_values = serialize_attributes
604
+
605
+ changed.each do |attr_name|
606
+ if values = attribute_values[attr_name]
607
+ to_update[attr_name] = values
608
+ else
609
+ to_delete << attr_name
610
+ end
611
+ end
612
+
613
+ add_optimistic_lock_expectation(to_update)
614
+
615
+ if to_delete.empty?
616
+ sdb_item.attributes.replace(to_update)
617
+ else
618
+ sdb_item.attributes.replace(to_update.merge('_delete_' => to_delete))
619
+ sdb_item.attributes.delete(to_delete + ['_delete_'])
620
+ end
621
+
622
+ end
623
+
624
+ # @private
625
+ private
626
+ def serialize_attributes
627
+
628
+ hash = {}
629
+ self.class.attributes.each_pair do |attribute_name,attribute|
630
+ values = serialize(attribute, @_data[attribute_name])
631
+ unless values.empty?
632
+ hash[attribute_name] = values
633
+ end
634
+ end
635
+
636
+ # simple db does not support persisting items without attribute values
637
+ raise EmptyRecordError.new(self) if hash.empty?
638
+
639
+ hash
640
+
641
+ end
642
+
643
+ # @private
644
+ private
645
+ def increment_optimistic_lock_value
646
+ if_locks_optimistically do |lock_attr_name|
647
+ if value = self[lock_attr_name]
648
+ self[lock_attr_name] += 1
649
+ else
650
+ self[lock_attr_name] = 1
651
+ end
652
+ end
653
+ end
654
+
655
+ # @private
656
+ private
657
+ def add_optimistic_lock_expectation options
658
+ if_locks_optimistically do |lock_attr_name|
659
+ was = attribute_was(lock_attr_name)
660
+ if was
661
+ options[:if] = { lock_attr_name => was.to_s }
662
+ else
663
+ options[:unless] = lock_attr_name
664
+ end
665
+ end
666
+ end
667
+
668
+ private
669
+ def if_locks_optimistically &block
670
+ if opt_lock_attr = self.class.optimistic_locking_attr
671
+ yield(opt_lock_attr.name)
672
+ end
673
+ end
674
+
675
+ # @private
676
+ private
677
+ def populate_id
678
+ @_id = UUIDTools::UUID.random_create.to_s
679
+ end
680
+
681
+ # @private
682
+ private
683
+ def touch_timestamps *attributes
684
+ time = Time.now
685
+ attributes.each do |attr_name|
686
+ if self.class.attributes[attr_name] and !attribute_changed?(attr_name)
687
+ __send__("#{attr_name}=", time)
688
+ end
689
+ end
690
+ end
691
+
692
+ # @private
693
+ private
694
+ def type_cast attribute, raw
695
+ if attribute.set?
696
+ values = Record.as_array(raw).inject([]) do |values,value|
697
+ values << attribute.type_cast(value)
698
+ values
699
+ end
700
+ Set.new(values.compact)
701
+ else
702
+ attribute.type_cast(raw)
703
+ end
704
+ end
705
+
706
+ # @private
707
+ private
708
+ def serialize attribute, raw
709
+ type_casted = type_cast(attribute, raw)
710
+ Record.as_array(type_casted).inject([]) do |values, value|
711
+ values << attribute.serialize(value)
712
+ values
713
+ end
714
+ end
715
+
716
+ # @private
717
+ private
718
+ def self.attribute_for attribute_name, &block
719
+ unless attributes[attribute_name.to_s]
720
+ raise UndefinedAttributeError.new(attribute_name.to_s)
721
+ end
722
+ yield(attributes[attribute_name.to_s])
723
+ end
724
+
725
+ end
726
+
727
+ end
728
+ end