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

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