aws-sdk 1.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 (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