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
@@ -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