aws-sdk 1.2.6 → 1.3.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 (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