aws-sdk 1.2.6 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/lib/aws.rb +2 -0
  2. data/lib/aws/api_config/DynamoDB-2011-12-05.yml +721 -0
  3. data/lib/aws/core.rb +10 -1
  4. data/lib/aws/core/client.rb +17 -12
  5. data/lib/aws/core/configuration.rb +13 -3
  6. data/lib/aws/core/configured_json_client_methods.rb +71 -0
  7. data/lib/aws/core/lazy_error_classes.rb +7 -2
  8. data/lib/aws/core/option_grammar.rb +67 -13
  9. data/lib/aws/core/resource.rb +9 -1
  10. data/lib/aws/core/session_signer.rb +95 -0
  11. data/lib/aws/dynamo_db.rb +169 -0
  12. data/lib/aws/dynamo_db/attribute_collection.rb +460 -0
  13. data/lib/aws/dynamo_db/batch_get.rb +206 -0
  14. data/lib/aws/dynamo_db/client.rb +119 -0
  15. data/lib/aws/dynamo_db/config.rb +20 -0
  16. data/lib/aws/dynamo_db/errors.rb +57 -0
  17. data/lib/aws/dynamo_db/expectations.rb +40 -0
  18. data/lib/aws/dynamo_db/item.rb +130 -0
  19. data/lib/aws/dynamo_db/item_collection.rb +837 -0
  20. data/lib/aws/{record/optimistic_locking.rb → dynamo_db/item_data.rb} +9 -12
  21. data/lib/aws/{record/attributes/boolean.rb → dynamo_db/keys.rb} +15 -23
  22. data/lib/aws/dynamo_db/primary_key_element.rb +47 -0
  23. data/lib/aws/dynamo_db/request.rb +78 -0
  24. data/lib/aws/{record/attributes/float.rb → dynamo_db/resource.rb} +10 -25
  25. data/lib/aws/dynamo_db/table.rb +418 -0
  26. data/lib/aws/dynamo_db/table_collection.rb +165 -0
  27. data/lib/aws/dynamo_db/types.rb +86 -0
  28. data/lib/aws/ec2/resource_tag_collection.rb +3 -1
  29. data/lib/aws/record.rb +36 -8
  30. data/lib/aws/record/abstract_base.rb +642 -0
  31. data/lib/aws/record/attributes.rb +384 -0
  32. data/lib/aws/record/dirty_tracking.rb +0 -1
  33. data/lib/aws/record/errors.rb +0 -8
  34. data/lib/aws/record/hash_model.rb +163 -0
  35. data/lib/aws/record/hash_model/attributes.rb +182 -0
  36. data/lib/aws/record/hash_model/finder_methods.rb +178 -0
  37. data/lib/aws/record/hash_model/scope.rb +108 -0
  38. data/lib/aws/record/model.rb +429 -0
  39. data/lib/aws/record/model/attributes.rb +377 -0
  40. data/lib/aws/record/model/finder_methods.rb +232 -0
  41. data/lib/aws/record/model/scope.rb +213 -0
  42. data/lib/aws/record/scope.rb +43 -169
  43. data/lib/aws/record/validations.rb +11 -11
  44. data/lib/aws/s3/client.rb +9 -6
  45. data/lib/aws/s3/object_collection.rb +1 -1
  46. data/lib/aws/simple_db/expect_condition_option.rb +1 -1
  47. data/lib/aws/simple_db/item_collection.rb +5 -3
  48. data/lib/aws/sts/client.rb +9 -0
  49. metadata +73 -30
  50. data/lib/aws/record/attribute.rb +0 -94
  51. data/lib/aws/record/attribute_macros.rb +0 -312
  52. data/lib/aws/record/attributes/date.rb +0 -89
  53. data/lib/aws/record/attributes/datetime.rb +0 -86
  54. data/lib/aws/record/attributes/integer.rb +0 -68
  55. data/lib/aws/record/attributes/sortable_float.rb +0 -60
  56. data/lib/aws/record/attributes/sortable_integer.rb +0 -95
  57. data/lib/aws/record/attributes/string.rb +0 -69
  58. data/lib/aws/record/base.rb +0 -828
  59. data/lib/aws/record/finder_methods.rb +0 -230
  60. data/lib/aws/record/scopes.rb +0 -55
@@ -0,0 +1,429 @@
1
+ # Copyright 2011-2012 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 'aws/record/abstract_base'
17
+ require 'aws/record/model/scope'
18
+ require 'aws/record/model/attributes'
19
+ require 'aws/record/model/finder_methods'
20
+
21
+ module AWS
22
+ module Record
23
+
24
+ # An ActiveRecord-like interface built ontop of Amazon SimpleDB.
25
+ #
26
+ # class Book < AWS::Record::Model
27
+ #
28
+ # string_attr :title
29
+ # string_attr :author
30
+ # integer :number_of_pages
31
+ #
32
+ # timestamps # adds a :created_at and :updated_at pair of timestamps
33
+ #
34
+ # end
35
+ #
36
+ # b = Book.new(:title => 'My Book', :author => 'Me', :pages => 1)
37
+ # b.save
38
+ #
39
+ # = Attribute Macros
40
+ #
41
+ # When extending AWS::Record::Model you should first consider what
42
+ # attributes your class should have. Unlike ActiveRecord, AWS::Record
43
+ # models are not backed by a database table/schema. You must choose what
44
+ # attributes (and what types) you need.
45
+ #
46
+ # * +string_attr+
47
+ # * +boolean_attr+
48
+ # * +integer_attr+
49
+ # * +float_attr+
50
+ # * +datetime_attr+
51
+ #
52
+ # For more information about the various attribute macros available,
53
+ # and what options they accept, see {AttributeMacros}.
54
+ #
55
+ # === Usage
56
+ #
57
+ # Normally you just call these methods inside your model class definition:
58
+ #
59
+ # class Book < AWS::Record::Model
60
+ # string_attr :title
61
+ # boolean_attr :has_been_read
62
+ # integer_attr :number_of_pages
63
+ # float_attr :weight_in_pounds
64
+ # datetime_attr :published_at
65
+ # end
66
+ #
67
+ # For each attribute macro a pair of setter/getter methods are added #
68
+ # to your class (and a few other useful methods).
69
+ #
70
+ # b = Book.new
71
+ # b.title = "My Book"
72
+ # b.has_been_read = true
73
+ # b.number_of_pages = 1000
74
+ # b.weight_in_pounds = 1.1
75
+ # b.published_at = Time.now
76
+ # b.save
77
+ #
78
+ # b.id #=> "0aa894ca-8223-4d34-831e-e5134b2bb71c"
79
+ # b.attributes
80
+ # #=> { 'title' => 'My Book', 'has_been_read' => true, ... }
81
+ #
82
+ # === Default Values
83
+ #
84
+ # All attribute macros accept the +:default_value+ option. This sets
85
+ # a value that is populated onto all new instnaces of the class.
86
+ #
87
+ # class Book < AWS::Record::Model
88
+ # string_attr :author, :deafult_value => 'Me'
89
+ # end
90
+ #
91
+ # Book.new.author #=> 'Me'
92
+ #
93
+ # === Multi-Valued (Set) Attributes
94
+ #
95
+ # AWS::Record permits storing multiple values with a single attribute.
96
+ #
97
+ # class Book < AWS::Record::Model
98
+ # string_attr :tags, :set => true
99
+ # end
100
+ #
101
+ # b = Book.new
102
+ # b.tags #=> #<Set: {}>
103
+ #
104
+ # b.tags = ['fiction', 'fantasy']
105
+ # b.tags #=> #<Set: {'fiction', 'fantasy'}>
106
+ #
107
+ # These multi-valued attributes are treated as sets, not arrays. This
108
+ # means:
109
+ #
110
+ # * values are unordered
111
+ # * duplicate values are automatically omitted
112
+ #
113
+ # Please consider these limitations when you choose to use the +:set+
114
+ # option with the attribute macros.
115
+ #
116
+ # = Validations
117
+ #
118
+ # It's important to validate models before there are persisted to keep
119
+ # your data clean. AWS::Record supports most of the ActiveRecord style
120
+ # validators.
121
+ #
122
+ # class Book < AWS::Record::Model
123
+ # string_attr :title
124
+ # validates_presence_of :title
125
+ # end
126
+ #
127
+ # b = Book.new
128
+ # b.valid? #=> false
129
+ # b.errors.full_messages #=> ['Title may not be blank']
130
+ #
131
+ # Validations are checked before saving a record. If any of the validators
132
+ # adds an error, the the save will fail.
133
+ #
134
+ # For more information about the available validation methods see
135
+ # {Validations}.
136
+ #
137
+ # = Finder Methods
138
+ #
139
+ # You can find records by their ID. Each record gets a UUID when it
140
+ # is saved for the first time. You can use this ID to fetch the record
141
+ # at a latter time:
142
+ #
143
+ # b = Book["0aa894ca-8223-4d34-831e-e5134b2bb71c"]
144
+ #
145
+ # b = Book.find("0aa894ca-8223-4d34-831e-e5134b2bb71c")
146
+ #
147
+ # If you try to find a record by ID that has no data an error will
148
+ # be raised.
149
+ #
150
+ # === All
151
+ #
152
+ # You can enumerate all of your records using +all+.
153
+ #
154
+ # Book.all.each do |book|
155
+ # puts book.id
156
+ # end
157
+ #
158
+ # Book.find(:all) do |book|
159
+ # puts book.id
160
+ # end
161
+ #
162
+ # Be careful when enumerating all. Depending on the number of records
163
+ # and number of attributes each record has, this can take a while,
164
+ # causing quite a few requests.
165
+ #
166
+ # === First
167
+ #
168
+ # If you only want a single record, you should use +first+.
169
+ #
170
+ # b = Book.first
171
+ #
172
+ # === Modifiers
173
+ #
174
+ # Frequently you do not want ALL records or the very first record. You
175
+ # can pass options to +find+, +all+ and +first+.
176
+ #
177
+ # my_books = Book.find(:all, :where => 'owner = "Me"')
178
+ #
179
+ # book = Book.first(:where => { :has_been_read => false })
180
+ #
181
+ # You can pass as find options:
182
+ #
183
+ # * +:where+ - Conditions that must be met to be returned
184
+ # * +:order+ - The order to sort matched records by
185
+ # * +:limit+ - The maximum number of records to return
186
+ #
187
+ # = Scopes
188
+ #
189
+ # More useful than writing query fragments all over the place is to
190
+ # name your most common conditions for reuse.
191
+ #
192
+ # class Book < AWS::Record::Model
193
+ #
194
+ # scope :mine, where(:owner => 'Me')
195
+ #
196
+ # scope :unread, where(:has_been_read => false)
197
+ #
198
+ # scope :by_popularity, order(:score, :desc)
199
+ #
200
+ # scope :top_10, by_popularity.limit(10)
201
+ #
202
+ # end
203
+ #
204
+ # # The following expression returns 10 books that belong
205
+ # # to me, that are unread sorted by popularity.
206
+ # next_good_reads = Book.mine.unread.top_10
207
+ #
208
+ # There are 3 standard scope methods:
209
+ #
210
+ # * +where+
211
+ # * +order+
212
+ # * +limit+
213
+ #
214
+ # === Conditions (where)
215
+ #
216
+ # Where accepts aruments in a number of forms:
217
+ #
218
+ # 1. As an sql-like fragment. If you need to escape values this form is
219
+ # not suggested.
220
+ #
221
+ # Book.where('title = "My Book"')
222
+ #
223
+ # 2. An sql-like fragment, with placeholders. This escapes quoted
224
+ # arguments properly to avoid injection.
225
+ #
226
+ # Book.where('title = ?', 'My Book')
227
+ #
228
+ # 3. A hash of key-value pairs. This is the simplest form, but also the
229
+ # least flexible. You can not use this form if you need more complex
230
+ # expressions that use or.
231
+ #
232
+ # Book.where(:title => 'My Book')
233
+ #
234
+ # === Order
235
+ #
236
+ # This orders the records as returned by AWS. Default ordering is ascending.
237
+ # Pass the value :desc as a second argument to sort in reverse ordering.
238
+ #
239
+ # Book.order(:title) # alphabetical ordering
240
+ # Book.order(:title, :desc) # reverse alphabetical ordering
241
+ #
242
+ # You may only order by a single attribute. If you call order twice in the
243
+ # chain, the last call gets presedence:
244
+ #
245
+ # Book.order(:title).order(:price)
246
+ #
247
+ # In this example the books will be ordered by :price and the order(:title)
248
+ # is lost.
249
+ #
250
+ # === Limit
251
+ #
252
+ # Just call +limit+ with an integer argument. This sets the maximum
253
+ # number of records to retrieve:
254
+ #
255
+ # Book.limit(2)
256
+ #
257
+ # === Delayed Execution
258
+ #
259
+ # It should be noted that all finds are lazy (except +first+). This
260
+ # means the value returned is not an array of records, rather a handle
261
+ # to a {Scope} object that will return records when you enumerate over them.
262
+ #
263
+ # This allows you to build an expression without making unecessary requests.
264
+ # In the following example no request is made until the call to
265
+ # each_with_index.
266
+ #
267
+ # all_books = Books.all
268
+ # ten_books = all_books.limit(10)
269
+ #
270
+ # ten_books.each_with_index do |book,n|
271
+ # puts "#{n + 1} : #{book.title}"
272
+ # end
273
+ #
274
+ class Model
275
+
276
+ extend AbstractBase
277
+
278
+ class << self
279
+
280
+ # Creates the SimpleDB domain that is configured for this class.
281
+ #
282
+ # class Product < AWS::Record::Model
283
+ # end
284
+ #
285
+ # Product.create_table #=> creates the SimpleDB domain 'Product'
286
+ #
287
+ # If you shard you data across multiple domains, you can specify the
288
+ # shard name:
289
+ #
290
+ # # create two domains, with the given names
291
+ # Product.create_domain :shard_name => 'products-1'
292
+ # Product.create_domain :shard_name => 'products-2'
293
+ #
294
+ # If you share a single AWS account with multiple applications, you
295
+ # can provide a domain prefix to group domains and to avoid name
296
+ # collisions:
297
+ #
298
+ # AWS::Record.domain_prefix = 'myapp-'
299
+ #
300
+ # # creates the domain 'myapp-Product'
301
+ # Product.create_domain
302
+ #
303
+ # # creates the domain 'myapp-products-1'
304
+ # Product.create_domain :shard_name => 'products-1'
305
+ #
306
+ # @param [Hash] options Hash of options passed to
307
+ # {SimpleDB::DomainCollection#create}.
308
+ #
309
+ # @option options [String] :shard_name Defaults to the class name. The
310
+ # shard name will be prefixed with {AWS::Record.domain_prefix},
311
+ # and that becomes the domain name.
312
+ #
313
+ # @return [SimpleDB::Domain]
314
+ #
315
+ def create_domain shard_name = nil
316
+ sdb.domains.create(sdb_domain_name(shard_name))
317
+ end
318
+
319
+ # @return [AWS::SimpleDB::Domain]
320
+ # @private
321
+ def sdb_domain shard_name = nil
322
+ sdb.domains[sdb_domain_name(shard_name)]
323
+ end
324
+
325
+ protected
326
+ def sdb_domain_name shard_name = nil
327
+ "#{AWS::Record.domain_prefix}#{self.shard_name(shard_name)}"
328
+ end
329
+
330
+ protected
331
+ def sdb
332
+ AWS::SimpleDB.new
333
+ end
334
+
335
+ end
336
+
337
+ # @return [SimpleDB::Item] Returns a reference to the item as stored in
338
+ # simple db.
339
+ # @private
340
+ private
341
+ def sdb_item
342
+ sdb_domain.items[id]
343
+ end
344
+
345
+ # @return [SimpleDB::Domain] Returns the domain this record is
346
+ # persisted to or will be persisted to.
347
+ private
348
+ def sdb_domain
349
+ self.class.sdb_domain(shard)
350
+ end
351
+
352
+ # This function accepts a hash of item data (as returned from
353
+ # AttributeCollection#to_h or ItemData#attributes) and returns only
354
+ # the key/value pairs that are configured attribues for this class.
355
+ # @private
356
+ private
357
+ def deserialize_item_data item_data
358
+
359
+ marked_for_deletion = item_data['_delete_'] || []
360
+
361
+ data = {}
362
+ item_data.each_pair do |attr_name,values|
363
+
364
+ attribute = self.class.attributes[attr_name]
365
+
366
+ next unless attribute
367
+ next if marked_for_deletion.include?(attr_name)
368
+
369
+ if attribute.set?
370
+ data[attr_name] = values.map{|v| attribute.deserialize(v) }
371
+ else
372
+ data[attr_name] = attribute.deserialize(values.first)
373
+ end
374
+
375
+ end
376
+ data
377
+ end
378
+
379
+ # @private
380
+ protected
381
+ def create_storage
382
+ to_add = serialize_attributes
383
+ sdb_item.attributes.add(to_add.merge(opt_lock_conditions))
384
+ end
385
+
386
+ # @private
387
+ private
388
+ def update_storage
389
+
390
+ to_update = {}
391
+ to_delete = []
392
+
393
+ # serialized_attributes will raise error if the entire record is blank
394
+ attribute_values = serialize_attributes
395
+
396
+ changed.each do |attr_name|
397
+ if values = attribute_values[attr_name]
398
+ to_update[attr_name] = values
399
+ else
400
+ to_delete << attr_name
401
+ end
402
+ end
403
+
404
+ to_update.merge!(opt_lock_conditions)
405
+
406
+ if to_delete.empty?
407
+ sdb_item.attributes.replace(to_update)
408
+ else
409
+ sdb_item.attributes.replace(to_update.merge('_delete_' => to_delete))
410
+ sdb_item.attributes.delete(to_delete + ['_delete_'])
411
+ end
412
+
413
+ end
414
+
415
+ # @return [true]
416
+ # @private
417
+ private
418
+ def delete_storage
419
+ sdb_item.delete(opt_lock_conditions)
420
+ @_deleted = true
421
+ end
422
+
423
+ end
424
+
425
+ # for backwards compatability with the old AWS::Record::Base
426
+ Base = Model
427
+
428
+ end
429
+ end
@@ -0,0 +1,377 @@
1
+ # Copyright 2011-2012 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
+ require 'aws/record/attributes.rb'
15
+
16
+ module AWS
17
+ module Record
18
+ class Model
19
+
20
+ module Attributes
21
+
22
+ class BooleanAttr < Record::Attributes::BooleanAttr
23
+ def self.serialize boolean, options = {}
24
+ super.to_s
25
+ end
26
+ end
27
+
28
+ class IntegerAttr < Record::Attributes::IntegerAttr
29
+ def self.serialize integer, options = {}
30
+ super.to_s
31
+ end
32
+ end
33
+
34
+ class FloatAttr < Record::Attributes::FloatAttr
35
+ def self.serialize float, options = {}
36
+ super.to_s
37
+ end
38
+ end
39
+
40
+ class SortableIntegerAttr < IntegerAttr
41
+
42
+ def initialize name, options = {}
43
+ range = options[:range]
44
+ raise ArgumentError, "missing required option :range" unless range
45
+ raise ArgumentError, ":range should be a integer range" unless
46
+ range.is_a?(Range) and range.first.is_a?(Integer)
47
+ super(name, options)
48
+ end
49
+
50
+ # Returns a serialized representation of the integer value suitable for
51
+ # storing in SimpleDB.
52
+ #
53
+ # attribute.serialize(123)
54
+ # #=> '123'
55
+ #
56
+ # # padded to the correct number of digits
57
+ # attribute.serialize('123', :range => (0..10_000)
58
+ # #=> '00123'
59
+ #
60
+ # # offset applied to make all values positive
61
+ # attribute.serialize('-55', :range => (-100..10_000)
62
+ # #=> '00045'
63
+ #
64
+ # @param [Integer] integer The number to serialize.
65
+ # @param [Hash] options
66
+ # @option options [required,Range] :range A range that represents the
67
+ # minimum and maximum values this integer can be.
68
+ # The returned value will have an offset applied (if min is
69
+ # less than 0) and will be zero padded.
70
+ # @return [String] A serialized representation of the integer.
71
+ def self.serialize integer, options = {}
72
+ expect(Integer, integer) do
73
+ check_range(integer, options)
74
+ offset_and_precision(options) do |offset,precision|
75
+ "%0#{precision}d" % (integer.to_i + offset)
76
+ end
77
+ end
78
+ end
79
+
80
+ def self.deserialize string_value, options = {}
81
+ offset_and_precision(options) do |offset,precision|
82
+ string_value.to_i - offset
83
+ end
84
+ end
85
+
86
+ protected
87
+ def self.offset_and_precision options, &block
88
+
89
+ min = options[:range].first
90
+ max = options[:range].last
91
+
92
+ offset = min < 0 ? min * -1 : 0
93
+ precision = (max + offset).to_s.length
94
+
95
+ yield(offset, precision)
96
+
97
+ end
98
+
99
+ def self.check_range number, options
100
+ unless options[:range].include?(number)
101
+ msg = "unable to serialize `#{number}`, falls outside " +
102
+ "the range #{options[:range]}"
103
+ raise msg
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ class SortableFloatAttr < FloatAttr
110
+
111
+ def initialize name, options = {}
112
+ range = options[:range]
113
+ raise ArgumentError, "missing required option :range" unless range
114
+ raise ArgumentError, ":range should be an integer range" unless
115
+ range.is_a?(Range) and range.first.is_a?(Integer)
116
+ super(name, options)
117
+ end
118
+
119
+ def self.serialize float, options = {}
120
+ expect(Float, float) do
121
+ left, right = float.to_s.split('.')
122
+ left = SortableIntegerAttr.serialize(left.to_i, options)
123
+ SortableIntegerAttr.check_range(float, options)
124
+ "#{left}.#{right}"
125
+ end
126
+ end
127
+
128
+ def self.deserialize string_value, options = {}
129
+ left, right = float.to_s.split('.')
130
+ left = SortableIntegerAttr.deserialize(left, options)
131
+ "#{left}.#{right}".to_f
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ class << self
139
+
140
+ # Adds a string attribute to this class.
141
+ #
142
+ # @example A standard string attribute
143
+ #
144
+ # class Recipe < AWS::Record::Model
145
+ # string_attr :name
146
+ # end
147
+ #
148
+ # recipe = Recipe.new(:name => "Buttermilk Pancakes")
149
+ # recipe.name #=> 'Buttermilk Pancakes'
150
+ #
151
+ # @example A string attribute with +:set+ set to true
152
+ #
153
+ # class Recipe < AWS::Record::Model
154
+ # string_attr :tags, :set => true
155
+ # end
156
+ #
157
+ # recipe = Recipe.new(:tags => %w(popular dessert))
158
+ # recipe.tags #=> #<Set: {"popular", "desert"}>
159
+ #
160
+ # @param [Symbol] name The name of the attribute.
161
+ # @param [Hash] options
162
+ # @option options [Boolean] :set (false) When true this attribute
163
+ # can have multiple values.
164
+ def string_attr name, options = {}
165
+ add_attribute(Record::Attributes::StringAttr.new(name, options))
166
+ end
167
+
168
+ # Adds an integer attribute to this class.
169
+ #
170
+ # class Recipe < AWS::Record::Model
171
+ # integer_attr :servings
172
+ # end
173
+ #
174
+ # recipe = Recipe.new(:servings => '10')
175
+ # recipe.servings #=> 10
176
+ #
177
+ # @param [Symbol] name The name of the attribute.
178
+ # @param [Hash] options
179
+ # @option options [Boolean] :set (false) When true this attribute
180
+ # can have multiple values.
181
+ def integer_attr name, options = {}
182
+ add_attribute(Attributes::IntegerAttr.new(name, options))
183
+ end
184
+
185
+ # Adds a sortable integer attribute to this class.
186
+ #
187
+ # class Person < AWS::Record::Model
188
+ # sortable_integer_attr :age, :range => 0..150
189
+ # end
190
+ #
191
+ # person = Person.new(:age => 10)
192
+ # person.age #=> 10
193
+ #
194
+ # === Validations
195
+ #
196
+ # It is recomended to apply a validates_numericality_of with
197
+ # minimum and maximum value constraints. If a value is assigned
198
+ # to a sortable integer that falls outside of the +:range: it will
199
+ # raise a runtime error when the record is saved.
200
+ #
201
+ # === Difference Between Sortable an Regular Integer Attributes
202
+ #
203
+ # Because SimpleDB does not support numeric types, all values must
204
+ # be converted to strings. This complicates sorting by numeric values.
205
+ # To accomplish sorting numeric attributes the values must be
206
+ # zero padded and have an offset applied to eliminate negative values.
207
+ #
208
+ # @param [Symbol] name The name of the attribute.
209
+ # @param [Hash] options
210
+ # @option options [Range] :range A numeric range the represents the
211
+ # minimum and maximum values this attribute should accept.
212
+ # @option options [Boolean] :set (false) When true this attribute
213
+ # can have multiple values.
214
+ def sortable_integer_attr name, options = {}
215
+ add_attribute(Attributes::SortableIntegerAttr.new(name, options))
216
+ end
217
+
218
+ # Adds a float attribute to this class.
219
+ #
220
+ # class Listing < AWS::Record::Model
221
+ # float_attr :score
222
+ # end
223
+ #
224
+ # listing = Listing.new(:score => '123.456')
225
+ # listing.score # => 123.456
226
+ #
227
+ # @param [Symbol] name The name of the attribute.
228
+ # @param [Hash] options
229
+ # @option options [Boolean] :set (false) When true this attribute
230
+ # can have multiple values.
231
+ def float_attr name, options = {}
232
+ add_attribute(Attributes::FloatAttr.new(name, options))
233
+ end
234
+
235
+ # Adds sortable float attribute to this class.
236
+ #
237
+ # Persisted values are stored (and sorted) as strings. This makes it
238
+ # more difficult to sort numbers because they don't sort
239
+ # lexicographically unless they have been offset to be positive and
240
+ # then zero padded.
241
+ #
242
+ # === Postive Floats
243
+ #
244
+ # To store floats in a sort-friendly manor:
245
+ #
246
+ # sortable_float_attr :score, :range => (0..10)
247
+ #
248
+ # This will cause values like 5.5 to persist as a string like '05.5' so
249
+ # that they can be sorted lexicographically.
250
+ #
251
+ # === Negative Floats
252
+ #
253
+ # If you need to store negative sortable floats, increase your +:range+
254
+ # to include a negative value.
255
+ #
256
+ # sortable_float_attr :position, :range => (-10..10)
257
+ #
258
+ # AWS::Record will add 10 to all values and zero pad them
259
+ # (e.g. -10.0 will be represented as '00.0' and 10 will be represented as
260
+ # '20.0'). This will allow the values to be compared lexicographically.
261
+ #
262
+ # @note If you change the +:range+ after some values have been persisted
263
+ # you must also manually migrate all of the old values to have the
264
+ # correct padding & offset or they will be interpreted differently.
265
+ #
266
+ # @param [Symbol] name The name of the attribute.
267
+ # @param [Hash] options
268
+ # @option options [Range] :range The range of numbers this attribute
269
+ # should represent. The min and max values of this range will determine
270
+ # how many digits of precision are required and how much of an offset
271
+ # is required to make the numbers sort lexicographically.
272
+ # @option options [Boolean] :set (false) When true this attribute
273
+ # can have multiple values.
274
+ def sortable_float_attr name, options = {}
275
+ add_attribute(Attributes::SortableFloatAttr.new(name, options))
276
+ end
277
+
278
+ # Adds a boolean attribute to this class.
279
+ #
280
+ # @example
281
+ #
282
+ # class Book < AWS::Record::Model
283
+ # boolean_attr :read
284
+ # end
285
+ #
286
+ # b = Book.new
287
+ # b.read? # => false
288
+ # b.read = true
289
+ # b.read? # => true
290
+ #
291
+ # listing = Listing.new(:score => '123.456'
292
+ # listing.score # => 123.456
293
+ #
294
+ # @param [Symbol] name The name of the attribute.
295
+ def boolean_attr name, options = {}
296
+
297
+ attr = add_attribute(Attributes::BooleanAttr.new(name, options))
298
+
299
+ # add the boolean question mark method
300
+ define_method("#{attr.name}?") do
301
+ !!__send__(attr.name)
302
+ end
303
+
304
+ end
305
+
306
+ # Adds a datetime attribute to this class.
307
+ #
308
+ # @example A standard datetime attribute
309
+ #
310
+ # class Recipe < AWS::Record::Model
311
+ # datetime_attr :invented
312
+ # end
313
+ #
314
+ # recipe = Recipe.new(:invented => Time.now)
315
+ # recipe.invented #=> <DateTime ...>
316
+ #
317
+ # If you add a datetime_attr for +:created_at+ and/or +:updated_at+ those
318
+ # will be automanaged.
319
+ #
320
+ # @param [Symbol] name The name of the attribute.
321
+ #
322
+ # @param [Hash] options
323
+ #
324
+ # @option options [Boolean] :set (false) When true this attribute
325
+ # can have multiple date times.
326
+ #
327
+ def datetime_attr name, options = {}
328
+ add_attribute(Record::Attributes::DateTimeAttr.new(name, options))
329
+ end
330
+
331
+ # Adds a date attribute to this class.
332
+ #
333
+ # @example A standard date attribute
334
+ #
335
+ # class Person < AWS::Record::Model
336
+ # date_attr :birthdate
337
+ # end
338
+ #
339
+ # baby = Person.new
340
+ # baby.birthdate = Time.now
341
+ # baby.birthdate #=> <Date: ....>
342
+ #
343
+ # @param [Symbol] name The name of the attribute.
344
+ #
345
+ # @param [Hash] options
346
+ #
347
+ # @option options [Boolean] :set (false) When true this attribute
348
+ # can have multiple dates.
349
+ #
350
+ def date_attr name, options = {}
351
+ add_attribute(Record::Attributes::DateAttr.new(name, options))
352
+ end
353
+
354
+ # A convenience method for adding the standard two datetime attributes
355
+ # +:created_at+ and +:updated_at+.
356
+ #
357
+ # @example
358
+ #
359
+ # class Recipe < AWS::Record::Model
360
+ # timestamps
361
+ # end
362
+ #
363
+ # recipe = Recipe.new
364
+ # recipe.save
365
+ # recipe.created_at #=> <DateTime ...>
366
+ # recipe.updated_at #=> <DateTime ...>
367
+ #
368
+ def timestamps
369
+ c = datetime_attr :created_at
370
+ u = datetime_attr :updated_at
371
+ [c, u]
372
+ end
373
+
374
+ end
375
+ end
376
+ end
377
+ end