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
@@ -1,68 +0,0 @@
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/attribute'
15
-
16
- module AWS
17
- module Record
18
-
19
- # @private
20
- class IntegerAttribute < Attribute
21
-
22
- # Returns value cast to an integer. Empty strings are cast to
23
- # nil by default. Type casting is done by calling #to_i on the value.
24
- #
25
- # int_attribute.type_cast('123')
26
- # #=> 123
27
- #
28
- # int_attribute.type_cast('')
29
- # #=> nil
30
- #
31
- # @param [Mixed] value The value to type cast to an integer.
32
- # @return [Integer,nil] Returns the type casted integer or nil
33
- def self.type_cast raw_value, options = {}
34
- case raw_value
35
- when nil then nil
36
- when '' then nil
37
- when Integer then raw_value
38
- else
39
- raw_value.respond_to?(:to_i) ?
40
- raw_value.to_i :
41
- raw_value.to_s.to_i
42
- end
43
- end
44
-
45
- # Returns a serialized representation of the integer value suitable for
46
- # storing in SimpleDB.
47
- #
48
- # attribute.serialize(123)
49
- # #=> '123'
50
- #
51
- # @param [Integer] integer The number to serialize.
52
- # @param [Hash] options
53
- # @return [String] A serialized representation of the integer.
54
- def self.serialize integer, options = {}
55
- expect(Integer, integer) do
56
- integer.to_s
57
- end
58
- end
59
-
60
- # @private
61
- def self.allow_set?
62
- true
63
- end
64
-
65
- end
66
-
67
- end
68
- end
@@ -1,60 +0,0 @@
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/attribute'
15
-
16
- module AWS
17
- module Record
18
-
19
- # @private
20
- class SortableFloatAttribute < FloatAttribute
21
-
22
- def initialize name, options = {}
23
-
24
- range = options[:range]
25
- raise ArgumentError, "missing required option :range" unless range
26
- raise ArgumentError, ":range should be an integer range" unless
27
- range.is_a?(Range) and range.first.is_a?(Integer)
28
-
29
- super(name, options)
30
-
31
- end
32
-
33
- def self.serialize float, options = {}
34
- expect(Float, float) do
35
-
36
- left, right = float.to_s.split('.')
37
-
38
- left = SortableIntegerAttribute.serialize(left.to_i, options)
39
-
40
- SortableIntegerAttribute.check_range(float, options)
41
-
42
- "#{left}.#{right}"
43
-
44
- end
45
- end
46
-
47
- def self.deserialize string_value, options = {}
48
-
49
- left, right = float.to_s.split('.')
50
-
51
- left = SortableIntegerAttribute.deserialize(left, options)
52
-
53
- "#{left}.#{right}".to_f
54
-
55
- end
56
-
57
- end
58
-
59
- end
60
- end
@@ -1,95 +0,0 @@
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/integer'
15
-
16
- module AWS
17
- module Record
18
-
19
- # @private
20
- class SortableIntegerAttribute < IntegerAttribute
21
-
22
- def initialize name, options = {}
23
-
24
- range = options[:range]
25
- raise ArgumentError, "missing required option :range" unless range
26
- raise ArgumentError, ":range should be a integer range" unless
27
- range.is_a?(Range) and range.first.is_a?(Integer)
28
-
29
- super(name, options)
30
-
31
- end
32
-
33
- # Returns a serialized representation of the integer value suitable for
34
- # storing in SimpleDB.
35
- #
36
- # attribute.serialize(123)
37
- # #=> '123'
38
- #
39
- # # padded to the correct number of digits
40
- # attribute.serialize('123', :range => (0..10_000)
41
- # #=> '00123'
42
- #
43
- # # offset applied to make all values positive
44
- # attribute.serialize('-55', :range => (-100..10_000)
45
- # #=> '00045'
46
- #
47
- # @param [Integer] integer The number to serialize.
48
- # @param [Hash] options
49
- # @option options [required,Range] :range A range that represents the
50
- # minimum and maximum values this integer can be.
51
- # The returned value will have an offset applied (if min is
52
- # less than 0) and will be zero padded.
53
- # @return [String] A serialized representation of the integer.
54
- def self.serialize integer, options = {}
55
- expect(Integer, integer) do
56
- check_range(integer, options)
57
- offset_and_precision(options) do |offset,precision|
58
- "%0#{precision}d" % (integer.to_i + offset)
59
- end
60
- end
61
- end
62
-
63
- def self.deserialize string_value, options = {}
64
- offset_and_precision(options) do |offset,precision|
65
- string_value.to_i - offset
66
- end
67
- end
68
-
69
- # @private
70
- protected
71
- def self.offset_and_precision options, &block
72
-
73
- min = options[:range].first
74
- max = options[:range].last
75
-
76
- offset = min < 0 ? min * -1 : 0
77
- precision = (max + offset).to_s.length
78
-
79
- yield(offset, precision)
80
-
81
- end
82
-
83
- # @private
84
- def self.check_range number, options
85
- unless options[:range].include?(number)
86
- msg = "unable to serialize `#{number}`, falls outside " +
87
- "the range #{options[:range]}"
88
- raise msg
89
- end
90
- end
91
-
92
- end
93
-
94
- end
95
- end
@@ -1,69 +0,0 @@
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/attribute'
15
-
16
- module AWS
17
- module Record
18
-
19
- # @private
20
- class StringAttribute < Attribute
21
-
22
- # Returns the value cast to a string. Empty strings are returned as
23
- # nil by default. Type casting is done by calling #to_s on the value.
24
- #
25
- # string_attr.type_cast(123)
26
- # # => '123'
27
- #
28
- # string_attr.type_cast('')
29
- # # => nil
30
- #
31
- # string_attr.type_cast('', :preserve_empty_strings => true)
32
- # # => ''
33
- #
34
- # @param [Mixed] value
35
- # @param [Hash] options
36
- # @option options [Boolean] :preserve_empty_strings (false) When true,
37
- # empty strings are preserved and not cast to nil.
38
- # @return [String,nil] The type casted value.
39
- def self.type_cast raw_value, options = {}
40
- case raw_value
41
- when nil then nil
42
- when '' then options[:preserve_empty_strings] ? '' : nil
43
- when String then raw_value
44
- else raw_value.to_s
45
- end
46
- end
47
-
48
- # Returns a serialized representation of the string value suitable for
49
- # storing in SimpleDB.
50
- # @param [String] string
51
- # @param [Hash] options
52
- # @return [String] The serialized string.
53
- def self.serialize string, options = {}
54
- unless string.is_a?(String)
55
- msg = "expected a String value, got #{string.class}"
56
- raise ArgumentError, msg
57
- end
58
- string
59
- end
60
-
61
- # @private
62
- def self.allow_set?
63
- true
64
- end
65
-
66
- end
67
-
68
- end
69
- end
@@ -1,828 +0,0 @@
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/simple_db'
17
-
18
- require 'set'
19
- require 'uuidtools'
20
-
21
- require 'aws/record/naming'
22
- require 'aws/record/attribute_macros'
23
- require 'aws/record/scopes'
24
- require 'aws/record/finder_methods'
25
- require 'aws/record/optimistic_locking'
26
- require 'aws/record/validations'
27
- require 'aws/record/dirty_tracking'
28
- require 'aws/record/conversion'
29
- require 'aws/record/errors'
30
- require 'aws/record/exceptions'
31
-
32
- module AWS
33
- module Record
34
-
35
- # An ActiveRecord-like interface built ontop of AWS.
36
- #
37
- # class Book < AWS::Record::Base
38
- #
39
- # string_attr :title
40
- # string_attr :author
41
- # integer :number_of_pages
42
- #
43
- # timestamps # adds a :created_at and :updated_at pair of timestamps
44
- #
45
- # end
46
- #
47
- # b = Book.new(:title => 'My Book', :author => 'Me', :pages => 1)
48
- # b.save
49
- #
50
- # = Attribute Macros
51
- #
52
- # When extending AWS::Record::Base you should first consider what
53
- # attributes your class should have. Unlike ActiveRecord, AWS::Record
54
- # models are not backed by a database table/schema. You must choose what
55
- # attributes (and what types) you need.
56
- #
57
- # * +string_attr+
58
- # * +boolean_attr+
59
- # * +integer_attr+
60
- # * +float_attr+
61
- # * +datetime_attr+
62
- #
63
- # For more information about the various attribute macros available,
64
- # and what options they accept, see {AttributeMacros}.
65
- #
66
- # === Usage
67
- #
68
- # Normally you just call these methods inside your model class definition:
69
- #
70
- # class Book < AWS::Record::Base
71
- # string_attr :title
72
- # boolean_attr :has_been_read
73
- # integer_attr :number_of_pages
74
- # float_attr :weight_in_pounds
75
- # datetime_attr :published_at
76
- # end
77
- #
78
- # For each attribute macro a pair of setter/getter methods are added #
79
- # to your class (and a few other useful methods).
80
- #
81
- # b = Book.new
82
- # b.title = "My Book"
83
- # b.has_been_read = true
84
- # b.number_of_pages = 1000
85
- # b.weight_in_pounds = 1.1
86
- # b.published_at = Time.now
87
- # b.save
88
- #
89
- # b.id #=> "0aa894ca-8223-4d34-831e-e5134b2bb71c"
90
- # b.attributes
91
- # #=> { 'title' => 'My Book', 'has_been_read' => true, ... }
92
- #
93
- # === Default Values
94
- #
95
- # All attribute macros accept the +:default_value+ option. This sets
96
- # a value that is populated onto all new instnaces of the class.
97
- #
98
- # class Book < AWS::Record::Base
99
- # string_attr :author, :deafult_value => 'Me'
100
- # end
101
- #
102
- # Book.new.author #=> 'Me'
103
- #
104
- # === Multi-Valued (Set) Attributes
105
- #
106
- # AWS::Record permits storing multiple values with a single attribute.
107
- #
108
- # class Book < AWS::Record::Base
109
- # string_attr :tags, :set => true
110
- # end
111
- #
112
- # b = Book.new
113
- # b.tags #=> #<Set: {}>
114
- #
115
- # b.tags = ['fiction', 'fantasy']
116
- # b.tags #=> #<Set: {'fiction', 'fantasy'}>
117
- #
118
- # These multi-valued attributes are treated as sets, not arrays. This
119
- # means:
120
- #
121
- # * values are unordered
122
- # * duplicate values are automatically omitted
123
- #
124
- # Please consider these limitations when you choose to use the +:set+
125
- # option with the attribute macros.
126
- #
127
- # = Validations
128
- #
129
- # It's important to validate models before there are persisted to keep
130
- # your data clean. AWS::Record supports most of the ActiveRecord style
131
- # validators.
132
- #
133
- # class Book < AWS::Record::Base
134
- # string_attr :title
135
- # validates_presence_of :title
136
- # end
137
- #
138
- # b = Book.new
139
- # b.valid? #=> false
140
- # b.errors.full_messages #=> ['Title may not be blank']
141
- #
142
- # Validations are checked before saving a record. If any of the validators
143
- # adds an error, the the save will fail.
144
- #
145
- # For more information about the available validation methods see
146
- # {Validations}.
147
- #
148
- # = Finder Methods
149
- #
150
- # You can find records by their ID. Each record gets a UUID when it
151
- # is saved for the first time. You can use this ID to fetch the record
152
- # at a latter time:
153
- #
154
- # b = Book["0aa894ca-8223-4d34-831e-e5134b2bb71c"]
155
- #
156
- # b = Book.find("0aa894ca-8223-4d34-831e-e5134b2bb71c")
157
- #
158
- # If you try to find a record by ID that has no data an error will
159
- # be raised.
160
- #
161
- # === All
162
- #
163
- # You can enumerate all of your records using +all+.
164
- #
165
- # Book.all.each do |book|
166
- # puts book.id
167
- # end
168
- #
169
- # Book.find(:all) do |book|
170
- # puts book.id
171
- # end
172
- #
173
- # Be careful when enumerating all. Depending on the number of records
174
- # and number of attributes each record has, this can take a while,
175
- # causing quite a few requests.
176
- #
177
- # === First
178
- #
179
- # If you only want a single record, you should use +first+.
180
- #
181
- # b = Book.first
182
- #
183
- # === Modifiers
184
- #
185
- # Frequently you do not want ALL records or the very first record. You
186
- # can pass options to +find+, +all+ and +first+.
187
- #
188
- # my_books = Book.find(:all, :where => 'owner = "Me"')
189
- #
190
- # book = Book.first(:where => { :has_been_read => false })
191
- #
192
- # You can pass as find options:
193
- #
194
- # * +:where+ - Conditions that must be met to be returned
195
- # * +:order+ - The order to sort matched records by
196
- # * +:limit+ - The maximum number of records to return
197
- #
198
- # = Scopes
199
- #
200
- # More useful than writing query fragments all over the place is to
201
- # name your most common conditions for reuse.
202
- #
203
- # class Book < AWS::Record::Base
204
- #
205
- # scope :mine, where(:owner => 'Me')
206
- #
207
- # scope :unread, where(:has_been_read => false)
208
- #
209
- # scope :by_popularity, order(:score, :desc)
210
- #
211
- # scope :top_10, by_popularity.limit(10)
212
- #
213
- # end
214
- #
215
- # # The following expression returns 10 books that belong
216
- # # to me, that are unread sorted by popularity.
217
- # next_good_reads = Book.mine.unread.top_10
218
- #
219
- # There are 3 standard scope methods:
220
- #
221
- # * +where+
222
- # * +order+
223
- # * +limit+
224
- #
225
- # === Conditions (where)
226
- #
227
- # Where accepts aruments in a number of forms:
228
- #
229
- # 1. As an sql-like fragment. If you need to escape values this form is
230
- # not suggested.
231
- #
232
- # Book.where('title = "My Book"')
233
- #
234
- # 2. An sql-like fragment, with placeholders. This escapes quoted
235
- # arguments properly to avoid injection.
236
- #
237
- # Book.where('title = ?', 'My Book')
238
- #
239
- # 3. A hash of key-value pairs. This is the simplest form, but also the
240
- # least flexible. You can not use this form if you need more complex
241
- # expressions that use or.
242
- #
243
- # Book.where(:title => 'My Book')
244
- #
245
- # === Order
246
- #
247
- # This orders the records as returned by AWS. Default ordering is ascending.
248
- # Pass the value :desc as a second argument to sort in reverse ordering.
249
- #
250
- # Book.order(:title) # alphabetical ordering
251
- # Book.order(:title, :desc) # reverse alphabetical ordering
252
- #
253
- # You may only order by a single attribute. If you call order twice in the
254
- # chain, the last call gets presedence:
255
- #
256
- # Book.order(:title).order(:price)
257
- #
258
- # In this example the books will be ordered by :price and the order(:title)
259
- # is lost.
260
- #
261
- # === Limit
262
- #
263
- # Just call +limit+ with an integer argument. This sets the maximum
264
- # number of records to retrieve:
265
- #
266
- # Book.limit(2)
267
- #
268
- # === Delayed Execution
269
- #
270
- # It should be noted that all finds are lazy (except +first+). This
271
- # means the value returned is not an array of records, rather a handle
272
- # to a {Scope} object that will return records when you enumerate over them.
273
- #
274
- # This allows you to build an expression without making unecessary requests.
275
- # In the following example no request is made until the call to
276
- # each_with_index.
277
- #
278
- # all_books = Books.all
279
- # ten_books = all_books.limit(10)
280
- #
281
- # ten_books.each_with_index do |book,n|
282
- # puts "#{n + 1} : #{book.title}"
283
- # end
284
- #
285
- class Base
286
-
287
- # for rails 3+ active model compatability
288
- extend Naming
289
- include Naming
290
-
291
- extend Validations
292
- extend AttributeMacros
293
- extend FinderMethods
294
- extend OptimisticLocking
295
- extend Scopes
296
-
297
- include Conversion
298
- include DirtyTracking
299
-
300
- # Constructs a new record for this class/domain.
301
- #
302
- # @param [Hash] attributes A set of attribute values to seed this record
303
- # with. The attributes are bulk assigned.
304
- #
305
- # @return [Base] Returns a new record that has not been persisted yet.
306
- #
307
- def initialize attributes = {}
308
-
309
- opts = attributes.dup
310
-
311
- @_data = {}
312
-
313
- @_domain = attributes.delete(:domain)
314
- @_domain ||= attributes.delete('domain')
315
- @_domain = self.class.domain_name(@_domain)
316
-
317
- assign_default_values
318
-
319
- bulk_assign(attributes)
320
-
321
- end
322
-
323
- # The id for each record is auto-generated. The default strategy
324
- # generates uuid strings.
325
- # @return [String] Returns the id string (uuid) for this record. Retuns
326
- # nil if this is a new record that has not been persisted yet.
327
- def id
328
- @_id
329
- end
330
-
331
- # @return [String] Returns the name of the SimpleDB domain this record
332
- # is persisted to or will be persisted to.
333
- def domain
334
- @_domain
335
- end
336
-
337
- # @return [Hash] A hash with attribute names as hash keys (strings) and
338
- # attribute values (of mixed types) as hash values.
339
- def attributes
340
- attributes = Core::IndifferentHash.new
341
- attributes['id'] = id if persisted?
342
- self.class.attributes.keys.inject(attributes) do |hash,attr_name|
343
- hash[attr_name] = __send__(attr_name)
344
- hash
345
- end
346
- end
347
-
348
- # Acts like {#update} but does not call {#save}.
349
- #
350
- # record.attributes = { :name => 'abc', :age => 20 }
351
- #
352
- # @param [Hash] attributes A hash of attributes to set on this record
353
- # without calling save.
354
- #
355
- # @return [Hash] Returns the attribute hash that was passed in.
356
- #
357
- def attributes= attributes
358
- bulk_assign(attributes)
359
- end
360
-
361
- # Persistence indicates if the record has been saved previously or not.
362
- #
363
- # @example
364
- # @recipe = Recipe.new(:name => 'Buttermilk Pancackes')
365
- # @recipe.persisted? #=> false
366
- # @recipe.save!
367
- # @recipe.persisted? #=> true
368
- #
369
- # @return [Boolean] Returns true if this record has been persisted.
370
- def persisted?
371
- !!@_persisted
372
- end
373
-
374
- # @return [Boolean] Returns true if this record has not been persisted
375
- # to SimpleDB.
376
- def new_record?
377
- !persisted?
378
- end
379
-
380
- # @return [Boolean] Returns true if this record has no validation errors.
381
- def valid?
382
- run_validations
383
- errors.empty?
384
- end
385
-
386
- # Creates new records, updates existing records.
387
- # @return [Boolean] Returns true if the record saved without errors,
388
- # false otherwise.
389
- def save
390
- if valid?
391
- persisted? ? update : create
392
- clear_changes!
393
- true
394
- else
395
- false
396
- end
397
- end
398
-
399
- # Creates new records, updates exsting records. If there is a validation
400
- # error then an exception is raised.
401
- # @raise [InvalidRecordError] Raised when the record has validation
402
- # errors and can not be saved.
403
- # @return [true] Returns true after a successful save.
404
- def save!
405
- raise InvalidRecordError.new(self) unless save
406
- true
407
- end
408
-
409
- # Bulk assigns the attributes and then saves the record.
410
- # @param [Hash] attribute_hash A hash of attribute names (keys) and
411
- # attribute values to assign to this record.
412
- # @return (see #save)
413
- def update_attributes attribute_hash
414
- bulk_assign(attribute_hash)
415
- save
416
- end
417
-
418
- # Bulk assigns the attributes and then saves the record. Raises
419
- # an exception (AWS::Record::InvalidRecordError) if the record is not
420
- # valid.
421
- # @param (see #update_attributes)
422
- # @return [true]
423
- def update_attributes! attribute_hash
424
- if update_attributes(attribute_hash)
425
- true
426
- else
427
- raise InvalidRecordError.new(self)
428
- end
429
- end
430
-
431
- # Deletes the record.
432
- # @return (see #delete_item)
433
- def delete
434
- if persisted?
435
- if deleted?
436
- raise 'unable to delete, this object has already been deleted'
437
- else
438
- delete_item
439
- end
440
- else
441
- raise 'unable to delete, this object has not been saved yet'
442
- end
443
- end
444
-
445
- # @return [Boolean] Returns true if this instance object has been deleted.
446
- def deleted?
447
- persisted? ? !!@_deleted : false
448
- end
449
-
450
- class << self
451
-
452
- # @return [Hash<String,Attribute>] Returns a hash of all of the
453
- # configured attributes for this class.
454
- def attributes
455
- @attributes ||= {}
456
- end
457
-
458
- # Allows you to override the default domain name for this record.
459
- # The defualt domain name is the class name.
460
- # @param [String] The domain name that should be used for this class.
461
- def set_domain_name name
462
- @_domain_name = name
463
- end
464
-
465
- # Returns the domain name this record class persists data into.
466
- # The default domain name is the class name with the optional
467
- # domain_prefix).
468
- # @param [String] name Defaults to the name of this class.
469
- # @return [String] Returns the full prefixed domain name for this class.
470
- def domain_name name = nil
471
-
472
- name = @_domain_name if name.nil?
473
- name = self.name if name.nil?
474
- name = name.name if name.is_a?(SimpleDB::Domain)
475
-
476
- "#{Record.domain_prefix}#{name}"
477
-
478
- end
479
-
480
- # Creates the SimpleDB domain that is configured for this class.
481
- # @param [String] name Name of the domain to create. Defaults to
482
- # the name of this class. The +name+ will be prefixed with
483
- # domain_prefix if one is set.
484
- #
485
- # @return [AWS::SimpleDB::Domain]
486
- #
487
- def create_domain name = nil
488
- sdb.domains.create(domain_name(name))
489
- end
490
-
491
- # @return [AWS::SimpleDB::Domain] Returns a reference to the domain
492
- # this class will save data to.
493
- # @private
494
- def sdb_domain name = nil
495
- sdb.domains[domain_name(name)]
496
- end
497
-
498
- protected
499
- def sdb
500
- AWS::SimpleDB.new
501
- end
502
-
503
- end
504
-
505
- # If you define a custom setter, you use #[]= to set the value
506
- # on the record.
507
- #
508
- # class Book < AWS::Record::Base
509
- #
510
- # string_attr :name
511
- #
512
- # # replace the default #author= method
513
- # def author= name
514
- # self['author'] = name.blank? ? 'Anonymous' : name
515
- # end
516
- #
517
- # end
518
- #
519
- # @param [String,Symbol] The attribute name to set a value for
520
- # @param attribute_value The value to assign.
521
- protected
522
- def []= attribute_name, new_value
523
- self.class.attribute_for(attribute_name) do |attribute|
524
-
525
- if_tracking_changes do
526
- original_value = type_cast(attribute, attribute_was(attribute.name))
527
- incoming_value = type_cast(attribute, new_value)
528
- if original_value == incoming_value
529
- clear_change!(attribute.name)
530
- else
531
- attribute_will_change!(attribute.name)
532
- end
533
- end
534
-
535
- @_data[attribute.name] = new_value
536
-
537
- end
538
- end
539
-
540
- # Returns the typecasted value for the named attribute.
541
- #
542
- # book = Book.new(:title => 'My Book')
543
- # book['title'] #=> 'My Book'
544
- # book.title #=> 'My Book'
545
- #
546
- # === Intended Use
547
- #
548
- # This method's primary use is for getting/setting the value for
549
- # an attribute inside a custom method:
550
- #
551
- # class Book < AWS::Record::Base
552
- #
553
- # string_attr :title
554
- #
555
- # def title
556
- # self['title'] ? self['title'].upcase : nil
557
- # end
558
- #
559
- # end
560
- #
561
- # book = Book.new(:title => 'My Book')
562
- # book.title #=> 'MY BOOK'
563
- #
564
- # @param [String,Symbol] attribute_name The name of the attribute to fetch
565
- # a value for.
566
- # @return The current type-casted value for the named attribute.
567
- protected
568
- def [] attribute_name
569
- self.class.attribute_for(attribute_name) do |attribute|
570
- type_cast(attribute, @_data[attribute.name])
571
- end
572
- end
573
-
574
- # @return [SimpleDB::Item] Returns a reference to the item as stored in
575
- # simple db.
576
- # @private
577
- private
578
- def sdb_item
579
- sdb_domain.items[id]
580
- end
581
-
582
- # @return [SimpleDB::Domain] Returns the domain this record is
583
- # persisted to or will be persisted to.
584
- private
585
- def sdb_domain
586
- self.class.sdb_domain(domain)
587
- end
588
-
589
- # @private
590
- private
591
- def assign_default_values
592
- # populate default attribute values
593
- ignore_changes do
594
- self.class.attributes.values.each do |attribute|
595
- begin
596
- # copy default values down so methods like #gsub! don't
597
- # modify the default values for other objects
598
- @_data[attribute.name] = attribute.default_value.clone
599
- rescue TypeError
600
- @_data[attribute.name] = attribute.default_value
601
- end
602
- end
603
- end
604
- end
605
-
606
- # @return [true]
607
- # @private
608
- private
609
- def delete_item
610
- options = {}
611
- add_optimistic_lock_expectation(options)
612
- sdb_item.delete(options)
613
- @_deleted = true
614
- end
615
-
616
- # @private
617
- private
618
- def bulk_assign hash
619
- hash.each_pair do |attribute_name, attribute_value|
620
- __send__("#{attribute_name}=", attribute_value)
621
- end
622
- end
623
-
624
- # @private
625
- # @todo need to do something about partial hyrdation of attributes
626
- private
627
- def hydrate id, data
628
-
629
- @_id = id
630
-
631
- # New objects are populated with default values, but we don't
632
- # want these values to hang around when hydrating persisted values
633
- # (those values may have been blanked out before save).
634
- self.class.attributes.values.each do |attribute|
635
- @_data[attribute.name] = nil
636
- end
637
-
638
- ignore_changes do
639
- bulk_assign(deserialize_item_data(data))
640
- end
641
-
642
- @_persisted = true
643
-
644
- end
645
-
646
- # This function accepts a hash of item data (as returned from
647
- # AttributeCollection#to_h or ItemData#attributes) and returns only
648
- # the key/value pairs that are configured attribues for this class.
649
- # @private
650
- private
651
- def deserialize_item_data item_data
652
-
653
- marked_for_deletion = item_data['_delete_'] || []
654
-
655
- data = {}
656
- item_data.each_pair do |attr_name,values|
657
-
658
- attribute = self.class.attributes[attr_name]
659
-
660
- next unless attribute
661
- next if marked_for_deletion.include?(attr_name)
662
-
663
- if attribute.set?
664
- data[attr_name] = values.map{|v| attribute.deserialize(v) }
665
- else
666
- data[attr_name] = attribute.deserialize(values.first)
667
- end
668
-
669
- end
670
- data
671
- end
672
-
673
- # @private
674
- private
675
- def create
676
-
677
- populate_id
678
- touch_timestamps('created_at', 'updated_at')
679
- increment_optimistic_lock_value
680
-
681
- to_add = serialize_attributes
682
-
683
- add_optimistic_lock_expectation(to_add)
684
- sdb_item.attributes.add(to_add)
685
-
686
- @_persisted = true
687
-
688
- end
689
-
690
- # @private
691
- private
692
- def update
693
-
694
- return unless changed?
695
-
696
- touch_timestamps('updated_at')
697
- increment_optimistic_lock_value
698
-
699
- to_update = {}
700
- to_delete = []
701
-
702
- # serialized_attributes will raise error if the entire record is blank
703
- attribute_values = serialize_attributes
704
-
705
- changed.each do |attr_name|
706
- if values = attribute_values[attr_name]
707
- to_update[attr_name] = values
708
- else
709
- to_delete << attr_name
710
- end
711
- end
712
-
713
- add_optimistic_lock_expectation(to_update)
714
-
715
- if to_delete.empty?
716
- sdb_item.attributes.replace(to_update)
717
- else
718
- sdb_item.attributes.replace(to_update.merge('_delete_' => to_delete))
719
- sdb_item.attributes.delete(to_delete + ['_delete_'])
720
- end
721
-
722
- end
723
-
724
- # @private
725
- private
726
- def serialize_attributes
727
-
728
- hash = {}
729
- self.class.attributes.each_pair do |attribute_name,attribute|
730
- values = serialize(attribute, @_data[attribute_name])
731
- unless values.empty?
732
- hash[attribute_name] = values
733
- end
734
- end
735
-
736
- # simple db does not support persisting items without attribute values
737
- raise EmptyRecordError.new(self) if hash.empty?
738
-
739
- hash
740
-
741
- end
742
-
743
- # @private
744
- private
745
- def increment_optimistic_lock_value
746
- if_locks_optimistically do |lock_attr_name|
747
- if value = self[lock_attr_name]
748
- self[lock_attr_name] += 1
749
- else
750
- self[lock_attr_name] = 1
751
- end
752
- end
753
- end
754
-
755
- # @private
756
- private
757
- def add_optimistic_lock_expectation options
758
- if_locks_optimistically do |lock_attr_name|
759
- was = attribute_was(lock_attr_name)
760
- if was
761
- options[:if] = { lock_attr_name => was.to_s }
762
- else
763
- options[:unless] = lock_attr_name
764
- end
765
- end
766
- end
767
-
768
- private
769
- def if_locks_optimistically &block
770
- if opt_lock_attr = self.class.optimistic_locking_attr
771
- yield(opt_lock_attr.name)
772
- end
773
- end
774
-
775
- # @private
776
- private
777
- def populate_id
778
- @_id = UUIDTools::UUID.random_create.to_s
779
- end
780
-
781
- # @private
782
- private
783
- def touch_timestamps *attributes
784
- time = Time.now
785
- attributes.each do |attr_name|
786
- if self.class.attributes[attr_name] and !attribute_changed?(attr_name)
787
- __send__("#{attr_name}=", time)
788
- end
789
- end
790
- end
791
-
792
- # @private
793
- private
794
- def type_cast attribute, raw
795
- if attribute.set?
796
- values = Record.as_array(raw).inject([]) do |values,value|
797
- values << attribute.type_cast(value)
798
- values
799
- end
800
- Set.new(values.compact)
801
- else
802
- attribute.type_cast(raw)
803
- end
804
- end
805
-
806
- # @private
807
- private
808
- def serialize attribute, raw
809
- type_casted = type_cast(attribute, raw)
810
- Record.as_array(type_casted).inject([]) do |values, value|
811
- values << attribute.serialize(value)
812
- values
813
- end
814
- end
815
-
816
- # @private
817
- private
818
- def self.attribute_for attribute_name, &block
819
- unless attributes[attribute_name.to_s]
820
- raise UndefinedAttributeError.new(attribute_name.to_s)
821
- end
822
- yield(attributes[attribute_name.to_s])
823
- end
824
-
825
- end
826
-
827
- end
828
- end