aws-record 1.0.0.pre.8 → 1.0.0.pre.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-record.rb +11 -11
  3. data/lib/aws-record/record.rb +21 -0
  4. data/lib/aws-record/record/attribute.rb +35 -3
  5. data/lib/aws-record/record/attributes.rb +137 -16
  6. data/lib/aws-record/record/dirty_tracking.rb +48 -8
  7. data/lib/aws-record/record/item_operations.rb +96 -48
  8. data/lib/aws-record/record/marshalers/boolean_marshaler.rb +53 -0
  9. data/lib/aws-record/record/marshalers/date_marshaler.rb +61 -0
  10. data/lib/aws-record/record/marshalers/date_time_marshaler.rb +72 -0
  11. data/lib/aws-record/record/marshalers/float_marshaler.rb +52 -0
  12. data/lib/aws-record/record/marshalers/integer_marshaler.rb +52 -0
  13. data/lib/aws-record/record/marshalers/list_marshaler.rb +56 -0
  14. data/lib/aws-record/record/marshalers/map_marshaler.rb +56 -0
  15. data/lib/aws-record/record/marshalers/numeric_set_marshaler.rb +69 -0
  16. data/lib/aws-record/record/marshalers/string_marshaler.rb +52 -0
  17. data/lib/aws-record/record/marshalers/string_set_marshaler.rb +69 -0
  18. data/lib/aws-record/record/version.rb +1 -1
  19. metadata +12 -13
  20. data/lib/aws-record/record/attributes/boolean_marshaler.rb +0 -54
  21. data/lib/aws-record/record/attributes/date_marshaler.rb +0 -54
  22. data/lib/aws-record/record/attributes/date_time_marshaler.rb +0 -55
  23. data/lib/aws-record/record/attributes/float_marshaler.rb +0 -53
  24. data/lib/aws-record/record/attributes/integer_marshaler.rb +0 -53
  25. data/lib/aws-record/record/attributes/list_marshaler.rb +0 -66
  26. data/lib/aws-record/record/attributes/map_marshaler.rb +0 -66
  27. data/lib/aws-record/record/attributes/numeric_set_marshaler.rb +0 -72
  28. data/lib/aws-record/record/attributes/string_marshaler.rb +0 -60
  29. data/lib/aws-record/record/attributes/string_set_marshaler.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 529b219269ff7db06904ac8069cd9d03c5a39b2f
4
- data.tar.gz: de636bcd9279040598617f1cad6b9601f583a4fc
3
+ metadata.gz: e20ddd5caa633c5ac1a7acb5e0e6cacfeed52595
4
+ data.tar.gz: 7317f916341e270b2418d54753a7c0d20540cd7f
5
5
  SHA512:
6
- metadata.gz: 0466dcde49ea6a9f0a026b806fba07f3e4152d7d115c8c6f89bcf1955903cd92ada636e51b72c161a8cb3f14178948514bf1e5bc11aa72995541f69559b81255
7
- data.tar.gz: be394920aba460d6f28d1087077a13f17e25c4f07eb93f24e4ccdf1a7abb222f8d946db3766e6817966659c524d0e550a9273b95be657434bee5e82ba088697b
6
+ metadata.gz: be783b2361e461595023f044f6147d0bf6935c5a15bbfa585d9cf97b92f63faad4c4d585da6827ec4a93dc1e8581c8e1c7774a400463236695554647a6edb5b0
7
+ data.tar.gz: 6c22f85ad6a3ca6455d0ac8d04de0bc8849605818605130a430ee7789c47f9088d219f4d363790e6dc3224fc4b22c352b0447b4e61e4be141af7a8295a7f972a
@@ -28,17 +28,17 @@ module Aws
28
28
  autoload :TableMigration, 'aws-record/record/table_migration'
29
29
  autoload :VERSION, 'aws-record/record/version'
30
30
 
31
- module Attributes
32
- autoload :StringMarshaler, 'aws-record/record/attributes/string_marshaler'
33
- autoload :BooleanMarshaler, 'aws-record/record/attributes/boolean_marshaler'
34
- autoload :IntegerMarshaler, 'aws-record/record/attributes/integer_marshaler'
35
- autoload :FloatMarshaler, 'aws-record/record/attributes/float_marshaler'
36
- autoload :DateMarshaler, 'aws-record/record/attributes/date_marshaler'
37
- autoload :DateTimeMarshaler, 'aws-record/record/attributes/date_time_marshaler'
38
- autoload :ListMarshaler, 'aws-record/record/attributes/list_marshaler'
39
- autoload :MapMarshaler, 'aws-record/record/attributes/map_marshaler'
40
- autoload :StringSetMarshaler, 'aws-record/record/attributes/string_set_marshaler'
41
- autoload :NumericSetMarshaler, 'aws-record/record/attributes/numeric_set_marshaler'
31
+ module Marshalers
32
+ autoload :StringMarshaler, 'aws-record/record/marshalers/string_marshaler'
33
+ autoload :BooleanMarshaler, 'aws-record/record/marshalers/boolean_marshaler'
34
+ autoload :IntegerMarshaler, 'aws-record/record/marshalers/integer_marshaler'
35
+ autoload :FloatMarshaler, 'aws-record/record/marshalers/float_marshaler'
36
+ autoload :DateMarshaler, 'aws-record/record/marshalers/date_marshaler'
37
+ autoload :DateTimeMarshaler, 'aws-record/record/marshalers/date_time_marshaler'
38
+ autoload :ListMarshaler, 'aws-record/record/marshalers/list_marshaler'
39
+ autoload :MapMarshaler, 'aws-record/record/marshalers/map_marshaler'
40
+ autoload :StringSetMarshaler, 'aws-record/record/marshalers/string_set_marshaler'
41
+ autoload :NumericSetMarshaler, 'aws-record/record/marshalers/numeric_set_marshaler'
42
42
  end
43
43
 
44
44
  end
@@ -50,6 +50,7 @@ module Aws
50
50
  # # Attribute definitions go here...
51
51
  # end
52
52
  def self.included(sub_class)
53
+ @track_mutations = true
53
54
  sub_class.send(:extend, RecordClassMethods)
54
55
  sub_class.send(:include, Attributes)
55
56
  sub_class.send(:include, ItemOperations)
@@ -183,6 +184,26 @@ module Aws
183
184
  @dynamodb_client ||= configure_client
184
185
  end
185
186
 
187
+ # Turns off mutation tracking for all attributes in the model.
188
+ def disable_mutation_tracking
189
+ @track_mutations = false
190
+ end
191
+
192
+ # Turns on mutation tracking for all attributes in the model. Note that
193
+ # mutation tracking is on by default, so you generally would not need to
194
+ # call this. It is provided in case there is a need to dynamically turn
195
+ # this feature on and off, though that would be generally discouraged and
196
+ # could cause inaccurate mutation tracking at runtime.
197
+ def enable_mutation_tracking
198
+ @track_mutations = true
199
+ end
200
+
201
+ # @return [Boolean] true if mutation tracking is enabled at the model
202
+ # level, false otherwise.
203
+ def mutation_tracking_enabled?
204
+ @track_mutations == false ? false : true
205
+ end
206
+
186
207
  private
187
208
  def _user_agent(custom)
188
209
  if custom
@@ -21,7 +21,7 @@ module Aws
21
21
  # within the model class and item instances.
22
22
  class Attribute
23
23
 
24
- attr_reader :name, :database_name, :dynamodb_type
24
+ attr_reader :name, :database_name, :dynamodb_type, :default_value
25
25
 
26
26
  # @param [Symbol] name Name of the attribute. It should be a name that is
27
27
  # safe to use as a method.
@@ -39,11 +39,27 @@ module Aws
39
39
  # "M", "L". Optional if this attribute will never be used for a key or
40
40
  # secondary index, but most convenience methods for setting attributes
41
41
  # will provide this.
42
+ # @option options [Boolean] :mutation_tracking Optional attribute used to
43
+ # indicate whether mutations to values should be explicitly tracked when
44
+ # determining if a value is "dirty". Important for collection types
45
+ # which are often primarily modified by mutation of a single object
46
+ # reference. By default, is false.
47
+ # @option options [Boolean] :persist_nil Optional attribute used to
48
+ # indicate whether nil values should be persisted. If true, explicitly
49
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
50
+ # nil values will be ignored and not persisted. By default, is false.
51
+ # @option options [Object] :default_value Optional attribute used to
52
+ # define a "default value" to be used if the attribute's value on an
53
+ # item is nil or not set at persistence time.
42
54
  def initialize(name, options = {})
43
55
  @name = name
44
56
  @database_name = options[:database_attribute_name] || name.to_s
45
57
  @dynamodb_type = options[:dynamodb_type]
46
58
  @marshaler = options[:marshaler] || DefaultMarshaler
59
+ @mutation_tracking = options[:mutation_tracking]
60
+ @persist_nil = options[:persist_nil]
61
+ dv = options[:default_value]
62
+ @default_value = type_cast(dv) unless dv.nil?
47
63
  end
48
64
 
49
65
  # Attempts to type cast a raw value into the attribute's type. This call
@@ -52,7 +68,9 @@ module Aws
52
68
  # @return [Object] the type cast object. Return type is dependent on the
53
69
  # marshaler used. See your attribute's marshaler class for details.
54
70
  def type_cast(raw_value)
55
- @marshaler.type_cast(raw_value)
71
+ cast_value = @marshaler.type_cast(raw_value)
72
+ cast_value = default_value if cast_value.nil?
73
+ cast_value
56
74
  end
57
75
 
58
76
  # Attempts to serialize a raw value into the attribute's serialized
@@ -62,7 +80,21 @@ module Aws
62
80
  # @return [Object] the serialized object. Return type is dependent on the
63
81
  # marshaler used. See your attribute's marshaler class for details.
64
82
  def serialize(raw_value)
65
- @marshaler.serialize(raw_value)
83
+ cast_value = type_cast(raw_value)
84
+ cast_value = default_value if cast_value.nil?
85
+ @marshaler.serialize(cast_value)
86
+ end
87
+
88
+ # @return [Boolean] true if this attribute should do active mutation
89
+ # tracking, false otherwise. Default: false
90
+ def track_mutations?
91
+ @mutation_tracking ? true : false
92
+ end
93
+
94
+ # @return [Boolean] true if this attribute will actively persist nil
95
+ # values, false otherwise. Default: false
96
+ def persist_nil?
97
+ @persist_nil ? true : false
66
98
  end
67
99
 
68
100
  # @api private
@@ -87,6 +87,18 @@ module Aws
87
87
  # "M", "L". Optional if this attribute will never be used for a key or
88
88
  # secondary index, but most convenience methods for setting attributes
89
89
  # will provide this.
90
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
91
+ # indicate whether mutations to values should be explicitly tracked
92
+ # when determining if a value is "dirty". Important for collection
93
+ # types which are often primarily modified by mutation of a single
94
+ # object reference. By default, is false.
95
+ # @option opts [Boolean] :persist_nil Optional attribute used to
96
+ # indicate whether nil values should be persisted. If true, explicitly
97
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
98
+ # nil values will be ignored and not persisted. By default, is false.
99
+ # @option opts [Object] :default_value Optional attribute used to
100
+ # define a "default value" to be used if the attribute's value on an
101
+ # item is nil or not set at persistence time.
90
102
  # @option opts [Boolean] :hash_key Set to true if this attribute is
91
103
  # the hash key for the table.
92
104
  # @option opts [Boolean] :range_key Set to true if this attribute is
@@ -118,9 +130,21 @@ module Aws
118
130
  # the hash key for the table.
119
131
  # @option opts [Boolean] :range_key Set to true if this attribute is
120
132
  # the range key for the table.
133
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
134
+ # indicate if mutations to values should be explicitly tracked when
135
+ # determining if a value is "dirty". Important for collection types
136
+ # which are often primarily modified by mutation of a single object
137
+ # reference. By default, is false.
138
+ # @option opts [Boolean] :persist_nil Optional attribute used to
139
+ # indicate whether nil values should be persisted. If true, explicitly
140
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
141
+ # nil values will be ignored and not persisted. By default, is false.
142
+ # @option opts [Object] :default_value Optional attribute used to
143
+ # define a "default value" to be used if the attribute's value on an
144
+ # item is nil or not set at persistence time.
121
145
  def string_attr(name, opts = {})
122
146
  opts[:dynamodb_type] = "S"
123
- attr(name, Attributes::StringMarshaler, opts)
147
+ attr(name, Marshalers::StringMarshaler.new(opts), opts)
124
148
  end
125
149
 
126
150
  # Define a boolean-type attribute for your model.
@@ -132,9 +156,21 @@ module Aws
132
156
  # the hash key for the table.
133
157
  # @option opts [Boolean] :range_key Set to true if this attribute is
134
158
  # the range key for the table.
159
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
160
+ # indicate if mutations to values should be explicitly tracked when
161
+ # determining if a value is "dirty". Important for collection types
162
+ # which are often primarily modified by mutation of a single object
163
+ # reference. By default, is false.
164
+ # @option opts [Boolean] :persist_nil Optional attribute used to
165
+ # indicate whether nil values should be persisted. If true, explicitly
166
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
167
+ # nil values will be ignored and not persisted. By default, is false.
168
+ # @option opts [Object] :default_value Optional attribute used to
169
+ # define a "default value" to be used if the attribute's value on an
170
+ # item is nil or not set at persistence time.
135
171
  def boolean_attr(name, opts = {})
136
172
  opts[:dynamodb_type] = "BOOL"
137
- attr(name, Attributes::BooleanMarshaler, opts)
173
+ attr(name, Marshalers::BooleanMarshaler.new(opts), opts)
138
174
  end
139
175
 
140
176
  # Define a integer-type attribute for your model.
@@ -146,9 +182,21 @@ module Aws
146
182
  # the hash key for the table.
147
183
  # @option opts [Boolean] :range_key Set to true if this attribute is
148
184
  # the range key for the table.
185
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
186
+ # indicate if mutations to values should be explicitly tracked when
187
+ # determining if a value is "dirty". Important for collection types
188
+ # which are often primarily modified by mutation of a single object
189
+ # reference. By default, is false.
190
+ # @option opts [Boolean] :persist_nil Optional attribute used to
191
+ # indicate whether nil values should be persisted. If true, explicitly
192
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
193
+ # nil values will be ignored and not persisted. By default, is false.
194
+ # @option opts [Object] :default_value Optional attribute used to
195
+ # define a "default value" to be used if the attribute's value on an
196
+ # item is nil or not set at persistence time.
149
197
  def integer_attr(name, opts = {})
150
198
  opts[:dynamodb_type] = "N"
151
- attr(name, Attributes::IntegerMarshaler, opts)
199
+ attr(name, Marshalers::IntegerMarshaler.new(opts), opts)
152
200
  end
153
201
 
154
202
  # Define a float-type attribute for your model.
@@ -160,9 +208,21 @@ module Aws
160
208
  # the hash key for the table.
161
209
  # @option opts [Boolean] :range_key Set to true if this attribute is
162
210
  # the range key for the table.
211
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
212
+ # indicate if mutations to values should be explicitly tracked when
213
+ # determining if a value is "dirty". Important for collection types
214
+ # which are often primarily modified by mutation of a single object
215
+ # reference. By default, is false.
216
+ # @option opts [Boolean] :persist_nil Optional attribute used to
217
+ # indicate whether nil values should be persisted. If true, explicitly
218
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
219
+ # nil values will be ignored and not persisted. By default, is false.
220
+ # @option opts [Object] :default_value Optional attribute used to
221
+ # define a "default value" to be used if the attribute's value on an
222
+ # item is nil or not set at persistence time.
163
223
  def float_attr(name, opts = {})
164
224
  opts[:dynamodb_type] = "N"
165
- attr(name, Attributes::FloatMarshaler, opts)
225
+ attr(name, Marshalers::FloatMarshaler.new(opts), opts)
166
226
  end
167
227
 
168
228
  # Define a date-type attribute for your model.
@@ -174,9 +234,21 @@ module Aws
174
234
  # the hash key for the table.
175
235
  # @option opts [Boolean] :range_key Set to true if this attribute is
176
236
  # the range key for the table.
237
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
238
+ # indicate if mutations to values should be explicitly tracked when
239
+ # determining if a value is "dirty". Important for collection types
240
+ # which are often primarily modified by mutation of a single object
241
+ # reference. By default, is false.
242
+ # @option opts [Boolean] :persist_nil Optional attribute used to
243
+ # indicate whether nil values should be persisted. If true, explicitly
244
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
245
+ # nil values will be ignored and not persisted. By default, is false.
246
+ # @option options [Object] :default_value Optional attribute used to
247
+ # define a "default value" to be used if the attribute's value on an
248
+ # item is nil or not set at persistence time.
177
249
  def date_attr(name, opts = {})
178
250
  opts[:dynamodb_type] = "S"
179
- attr(name, Attributes::DateMarshaler, opts)
251
+ attr(name, Marshalers::DateMarshaler.new(opts), opts)
180
252
  end
181
253
 
182
254
  # Define a datetime-type attribute for your model.
@@ -188,9 +260,21 @@ module Aws
188
260
  # the hash key for the table.
189
261
  # @option opts [Boolean] :range_key Set to true if this attribute is
190
262
  # the range key for the table.
263
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
264
+ # indicate if mutations to values should be explicitly tracked when
265
+ # determining if a value is "dirty". Important for collection types
266
+ # which are often primarily modified by mutation of a single object
267
+ # reference. By default, is false.
268
+ # @option opts [Boolean] :persist_nil Optional attribute used to
269
+ # indicate whether nil values should be persisted. If true, explicitly
270
+ # set nil values will be saved to DynamoDB as a "null" type. If false,
271
+ # nil values will be ignored and not persisted. By default, is false.
272
+ # @option opts [Object] :default_value Optional attribute used to
273
+ # define a "default value" to be used if the attribute's value on an
274
+ # item is nil or not set at persistence time.
191
275
  def datetime_attr(name, opts = {})
192
276
  opts[:dynamodb_type] = "S"
193
- attr(name, Attributes::DateTimeMarshaler, opts)
277
+ attr(name, Marshalers::DateTimeMarshaler.new(opts), opts)
194
278
  end
195
279
 
196
280
  # Define a list-type attribute for your model.
@@ -216,16 +300,22 @@ module Aws
216
300
  # @param [Symbol] name Name of this attribute. It should be a name that
217
301
  # is safe to use as a method.
218
302
  # @param [Hash] opts
219
- # @option opts [Boolean] :nil_as_empty_list Set to true if this
220
- # attribute should interpret nil values as an empty list. If false,
221
- # nil values will remain nil.
222
303
  # @option opts [Boolean] :hash_key Set to true if this attribute is
223
304
  # the hash key for the table.
224
305
  # @option opts [Boolean] :range_key Set to true if this attribute is
225
306
  # the range key for the table.
307
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
308
+ # indicate if mutations to values should be explicitly tracked when
309
+ # determining if a value is "dirty". Important for collection types
310
+ # which are often primarily modified by mutation of a single object
311
+ # reference. By default, is true.
312
+ # @option opts [Object] :default_value Optional attribute used to
313
+ # define a "default value" to be used if the attribute's value on an
314
+ # item is nil or not set at persistence time.
226
315
  def list_attr(name, opts = {})
227
316
  opts[:dynamodb_type] = "L"
228
- attr(name, Attributes::ListMarshaler, opts)
317
+ opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
318
+ attr(name, Marshalers::ListMarshaler.new(opts), opts)
229
319
  end
230
320
 
231
321
  # Define a map-type attribute for your model.
@@ -251,16 +341,22 @@ module Aws
251
341
  # @param [Symbol] name Name of this attribute. It should be a name that
252
342
  # is safe to use as a method.
253
343
  # @param [Hash] opts
254
- # @option opts [Boolean] :nil_as_empty_map Set to true if this
255
- # attribute should interpret nil values as an empty hash. If false,
256
- # nil values will remain nil.
257
344
  # @option opts [Boolean] :hash_key Set to true if this attribute is
258
345
  # the hash key for the table.
259
346
  # @option opts [Boolean] :range_key Set to true if this attribute is
260
347
  # the range key for the table.
348
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
349
+ # indicate if mutations to values should be explicitly tracked when
350
+ # determining if a value is "dirty". Important for collection types
351
+ # which are often primarily modified by mutation of a single object
352
+ # reference. By default, is true.
353
+ # @option opts [Object] :default_value Optional attribute used to
354
+ # define a "default value" to be used if the attribute's value on an
355
+ # item is nil or not set at persistence time.
261
356
  def map_attr(name, opts = {})
262
357
  opts[:dynamodb_type] = "M"
263
- attr(name, Attributes::MapMarshaler, opts)
358
+ opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
359
+ attr(name, Marshalers::MapMarshaler.new(opts), opts)
264
360
  end
265
361
 
266
362
  # Define a string set attribute for your model.
@@ -280,9 +376,18 @@ module Aws
280
376
  # the hash key for the table.
281
377
  # @option opts [Boolean] :range_key Set to true if this attribute is
282
378
  # the range key for the table.
379
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
380
+ # indicate if mutations to values should be explicitly tracked when
381
+ # determining if a value is "dirty". Important for collection types
382
+ # which are often primarily modified by mutation of a single object
383
+ # reference. By default, is true.
384
+ # @option opts [Object] :default_value Optional attribute used to
385
+ # define a "default value" to be used if the attribute's value on an
386
+ # item is nil or not set at persistence time.
283
387
  def string_set_attr(name, opts = {})
284
388
  opts[:dynamodb_type] = "SS"
285
- attr(name, Attributes::StringSetMarshaler, opts)
389
+ opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
390
+ attr(name, Marshalers::StringSetMarshaler.new(opts), opts)
286
391
  end
287
392
 
288
393
  # Define a numeric set attribute for your model.
@@ -302,9 +407,18 @@ module Aws
302
407
  # the hash key for the table.
303
408
  # @option opts [Boolean] :range_key Set to true if this attribute is
304
409
  # the range key for the table.
410
+ # @option opts [Boolean] :mutation_tracking Optional attribute used to
411
+ # indicate if mutations to values should be explicitly tracked when
412
+ # determining if a value is "dirty". Important for collection types
413
+ # which are often primarily modified by mutation of a single object
414
+ # reference. By default, is true.
415
+ # @option opts [Object] :default_value Optional attribute used to
416
+ # define a "default value" to be used if the attribute's value on an
417
+ # item is nil or not set at persistence time.
305
418
  def numeric_set_attr(name, opts = {})
306
419
  opts[:dynamodb_type] = "NS"
307
- attr(name, Attributes::NumericSetMarshaler, opts)
420
+ opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
421
+ attr(name, Marshalers::NumericSetMarshaler.new(opts), opts)
308
422
  end
309
423
 
310
424
  # @return [Hash] hash of symbolized attribute names to attribute objects
@@ -333,6 +447,13 @@ module Aws
333
447
  @keys
334
448
  end
335
449
 
450
+ # @param [Symbol] attr_sym The symbolized name of the attribute.
451
+ # @return [Boolean] true if object mutations are tracked for dirty
452
+ # checking of that attribute, false if mutations are not tracked.
453
+ def track_mutations?(attr_sym)
454
+ mutation_tracking_enabled? && attributes[attr_sym].track_mutations?
455
+ end
456
+
336
457
  private
337
458
  def define_attr_methods(name, attribute)
338
459
  define_method(name) do
@@ -24,6 +24,7 @@ module Aws
24
24
  # @override initialize(*)
25
25
  def initialize(*)
26
26
  @dirty_data = {}
27
+ @mutation_copies = {}
27
28
  super
28
29
  end
29
30
 
@@ -44,7 +45,7 @@ module Aws
44
45
  # @param [String, Symbol] name The name of the attribute to to check for dirty changes.
45
46
  # @return [Boolean] +true+ if the specified attribute has any dirty changes, +false+ otherwise.
46
47
  def attribute_dirty?(name)
47
- @dirty_data.has_key?(name)
48
+ @dirty_data.has_key?(name) || _mutated?(name)
48
49
  end
49
50
 
50
51
  # Returns the original value of the specified attribute.
@@ -63,7 +64,11 @@ module Aws
63
64
  # @param [String, Symbol] name The name of the attribute to retrieve the original value of.
64
65
  # @return [Object] The original value of the specified attribute.
65
66
  def attribute_was(name)
66
- attribute_dirty?(name) ? @dirty_data[name] : @data[name]
67
+ if @mutation_copies.has_key?(name)
68
+ @mutation_copies[name]
69
+ else
70
+ attribute_dirty?(name) ? @dirty_data[name] : @data[name]
71
+ end
67
72
  end
68
73
 
69
74
  # Mark that an attribute is changing. This is useful in situations where it is necessary to track that the value of an
@@ -107,7 +112,7 @@ module Aws
107
112
 
108
113
  @dirty_data[name] =
109
114
  begin
110
- current_value.clone
115
+ _deep_copy(current_value)
111
116
  rescue TypeError
112
117
  current_value
113
118
  end
@@ -132,6 +137,13 @@ module Aws
132
137
  #
133
138
  def clean!
134
139
  @dirty_data.clear
140
+ self.class.attributes.each do |name, attribute|
141
+ if self.class.track_mutations?(name)
142
+ if @data[name]
143
+ @mutation_copies[name] = _deep_copy(@data[name])
144
+ end
145
+ end
146
+ end
135
147
  end
136
148
 
137
149
  # Returns an array with the name of the attributes with dirty changes.
@@ -150,7 +162,13 @@ module Aws
150
162
  #
151
163
  # @return [Array] The names of attributes with dirty changes.
152
164
  def dirty
153
- @dirty_data.keys
165
+ ret = @dirty_data.keys.dup
166
+ @mutation_copies.each do |key, value|
167
+ if @data[key] != value
168
+ ret << key unless ret.include?(key)
169
+ end
170
+ end
171
+ ret
154
172
  end
155
173
 
156
174
  # Returns +true+ if any attributes have dirty changes, +false+ otherwise.
@@ -170,7 +188,10 @@ module Aws
170
188
  # @return [Boolean] +true+ if any attributes have dirty changes, +false+
171
189
  # otherwise.
172
190
  def dirty?
173
- @dirty_data.size > 0
191
+ return true if @dirty_data.size > 0
192
+ @mutation_copies.any? do |name, value|
193
+ @mutation_copies[name] != @data[name]
194
+ end
174
195
  end
175
196
 
176
197
  # Fetches attributes for this instance of an item from Amazon DynamoDB
@@ -188,7 +209,7 @@ module Aws
188
209
 
189
210
  record = self.class.find(primary_key)
190
211
 
191
- unless record.nil?
212
+ unless record.nil?
192
213
  @data = record.instance_variable_get("@data")
193
214
  else
194
215
  raise Errors::NotFound.new("No record found")
@@ -217,7 +238,12 @@ module Aws
217
238
  def rollback_attribute!(name)
218
239
  return unless attribute_dirty?(name)
219
240
 
220
- @data[name] = @dirty_data.delete(name)
241
+ if @mutation_copies.has_key?(name)
242
+ @data[name] = @mutation_copies[name]
243
+ @dirty_data.delete(name) if @dirty_data.has_key?(name)
244
+ else
245
+ @data[name] = @dirty_data.delete(name)
246
+ end
221
247
  end
222
248
 
223
249
  # Restores all attributes to their original values.
@@ -252,15 +278,29 @@ module Aws
252
278
  #
253
279
  # @override write_attribute(*)
254
280
  def write_attribute(name, attribute, value)
281
+ _dirty_write_attribute(name, attribute, value)
282
+ super
283
+ end
284
+
285
+ def _dirty_write_attribute(name, attribute, value)
255
286
  if value == attribute_was(name)
256
287
  @dirty_data.delete(name)
257
288
  else
258
289
  attribute_dirty!(name)
259
290
  end
291
+ end
260
292
 
261
- super
293
+ def _mutated?(name)
294
+ if @mutation_copies.has_key?(name)
295
+ @data[name] != @mutation_copies[name]
296
+ else
297
+ false
298
+ end
262
299
  end
263
300
 
301
+ def _deep_copy(obj)
302
+ Marshal.load(Marshal.dump(obj))
303
+ end
264
304
 
265
305
  module DirtyTrackingClassMethods
266
306