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

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e20ddd5caa633c5ac1a7acb5e0e6cacfeed52595
4
- data.tar.gz: 7317f916341e270b2418d54753a7c0d20540cd7f
3
+ metadata.gz: 6c99f94d8f0401d3de3c1886e58cc8af973cd5ec
4
+ data.tar.gz: b824eac6e13b8d37927ec34eb4cb24d39cf15063
5
5
  SHA512:
6
- metadata.gz: be783b2361e461595023f044f6147d0bf6935c5a15bbfa585d9cf97b92f63faad4c4d585da6827ec4a93dc1e8581c8e1c7774a400463236695554647a6edb5b0
7
- data.tar.gz: 6c22f85ad6a3ca6455d0ac8d04de0bc8849605818605130a430ee7789c47f9088d219f4d363790e6dc3224fc4b22c352b0447b4e61e4be141af7a8295a7f972a
6
+ metadata.gz: c5e381404cc144a57792316da027c990f0d7b5600187e38afa9eb84c559fc1d0e896da300beee534f76eca4dbed21d90198db7b6740c3f261d2db5719a045865
7
+ data.tar.gz: 3c985a0bb11eed715c941f3138cc3195be3b34bad7b12f21d3e4ae0eccaf5de783015c4fab5bc099ea37c3e1bf64814c94b5644f9b8df8fc33a6661828ff496c
@@ -22,7 +22,10 @@ module Aws
22
22
  autoload :DirtyTracking, 'aws-record/record/dirty_tracking'
23
23
  autoload :Errors, 'aws-record/record/errors'
24
24
  autoload :ItemCollection, 'aws-record/record/item_collection'
25
+ autoload :ItemData, 'aws-record/record/item_data'
25
26
  autoload :ItemOperations, 'aws-record/record/item_operations'
27
+ autoload :KeyAttributes, 'aws-record/record/key_attributes'
28
+ autoload :ModelAttributes, 'aws-record/record/model_attributes'
26
29
  autoload :Query, 'aws-record/record/query'
27
30
  autoload :SecondaryIndexes, 'aws-record/record/secondary_indexes'
28
31
  autoload :TableMigration, 'aws-record/record/table_migration'
@@ -204,6 +204,12 @@ module Aws
204
204
  @track_mutations == false ? false : true
205
205
  end
206
206
 
207
+ def model_valid?
208
+ if @keys.hash_key.nil?
209
+ raise Errors::InvalidModel.new("Table models must include a hash key")
210
+ end
211
+ end
212
+
207
213
  private
208
214
  def _user_agent(custom)
209
215
  if custom
@@ -39,11 +39,6 @@ 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
42
  # @option options [Boolean] :persist_nil Optional attribute used to
48
43
  # indicate whether nil values should be persisted. If true, explicitly
49
44
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -56,7 +51,6 @@ module Aws
56
51
  @database_name = options[:database_attribute_name] || name.to_s
57
52
  @dynamodb_type = options[:dynamodb_type]
58
53
  @marshaler = options[:marshaler] || DefaultMarshaler
59
- @mutation_tracking = options[:mutation_tracking]
60
54
  @persist_nil = options[:persist_nil]
61
55
  dv = options[:default_value]
62
56
  @default_value = type_cast(dv) unless dv.nil?
@@ -85,12 +79,6 @@ module Aws
85
79
  @marshaler.serialize(cast_value)
86
80
  end
87
81
 
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
82
  # @return [Boolean] true if this attribute will actively persist nil
95
83
  # values, false otherwise. Default: false
96
84
  def persist_nil?
@@ -17,9 +17,9 @@ module Aws
17
17
 
18
18
  def self.included(sub_class)
19
19
  sub_class.extend(ClassMethods)
20
- sub_class.instance_variable_set("@keys", {})
21
- sub_class.instance_variable_set("@attributes", {})
22
- sub_class.instance_variable_set("@storage_attributes", {})
20
+ model_attributes = ModelAttributes.new(self)
21
+ sub_class.instance_variable_set("@attributes", model_attributes)
22
+ sub_class.instance_variable_set("@keys", KeyAttributes.new(model_attributes))
23
23
  end
24
24
 
25
25
  # @example Usage Example
@@ -40,7 +40,10 @@ module Aws
40
40
  # attribute values you wish to set.
41
41
  # @return [Aws::Record] An item instance for your model.
42
42
  def initialize(attr_values = {})
43
- @data = {}
43
+ opts = {
44
+ track_mutations: self.class.mutation_tracking_enabled?
45
+ }
46
+ @data = ItemData.new(self.class.attributes, opts)
44
47
  attr_values.each do |attr_name, attr_value|
45
48
  send("#{attr_name}=", attr_value)
46
49
  end
@@ -50,23 +53,9 @@ module Aws
50
53
  #
51
54
  # @return [Hash] Map of attribute names to raw values.
52
55
  def to_h
53
- @data.dup
56
+ @data.hash_copy
54
57
  end
55
58
 
56
- private
57
-
58
- # @private
59
- def read_attribute(name, attribute)
60
- raw = @data[name]
61
- attribute.type_cast(raw)
62
- end
63
-
64
- # @private
65
- def write_attribute(name, attribute, value)
66
- @data[name] = value
67
- end
68
-
69
-
70
59
  module ClassMethods
71
60
 
72
61
  # Define an attribute for your model, providing your own attribute type.
@@ -87,11 +76,6 @@ module Aws
87
76
  # "M", "L". Optional if this attribute will never be used for a key or
88
77
  # secondary index, but most convenience methods for setting attributes
89
78
  # 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
79
  # @option opts [Boolean] :persist_nil Optional attribute used to
96
80
  # indicate whether nil values should be persisted. If true, explicitly
97
81
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -104,21 +88,9 @@ module Aws
104
88
  # @option opts [Boolean] :range_key Set to true if this attribute is
105
89
  # the range key for the table.
106
90
  def attr(name, marshaler, opts = {})
107
- validate_attr_name(name)
108
-
109
- opts = opts.merge(marshaler: marshaler)
110
- attribute = Attribute.new(name, opts)
111
-
112
- storage_name = attribute.database_name
113
-
114
- check_for_naming_collisions(name, storage_name)
115
- check_if_reserved(name)
116
-
117
- @attributes[name] = attribute
118
- @storage_attributes[storage_name] = name
119
-
120
- define_attr_methods(name, attribute)
121
- key_attributes(name, opts)
91
+ @attributes.register_attribute(name, marshaler, opts)
92
+ _define_attr_methods(name)
93
+ _key_attributes(name, opts)
122
94
  end
123
95
 
124
96
  # Define a string-type attribute for your model.
@@ -130,11 +102,6 @@ module Aws
130
102
  # the hash key for the table.
131
103
  # @option opts [Boolean] :range_key Set to true if this attribute is
132
104
  # 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
105
  # @option opts [Boolean] :persist_nil Optional attribute used to
139
106
  # indicate whether nil values should be persisted. If true, explicitly
140
107
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -156,11 +123,6 @@ module Aws
156
123
  # the hash key for the table.
157
124
  # @option opts [Boolean] :range_key Set to true if this attribute is
158
125
  # 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
126
  # @option opts [Boolean] :persist_nil Optional attribute used to
165
127
  # indicate whether nil values should be persisted. If true, explicitly
166
128
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -182,11 +144,6 @@ module Aws
182
144
  # the hash key for the table.
183
145
  # @option opts [Boolean] :range_key Set to true if this attribute is
184
146
  # 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
147
  # @option opts [Boolean] :persist_nil Optional attribute used to
191
148
  # indicate whether nil values should be persisted. If true, explicitly
192
149
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -208,11 +165,6 @@ module Aws
208
165
  # the hash key for the table.
209
166
  # @option opts [Boolean] :range_key Set to true if this attribute is
210
167
  # 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
168
  # @option opts [Boolean] :persist_nil Optional attribute used to
217
169
  # indicate whether nil values should be persisted. If true, explicitly
218
170
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -234,11 +186,6 @@ module Aws
234
186
  # the hash key for the table.
235
187
  # @option opts [Boolean] :range_key Set to true if this attribute is
236
188
  # 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
189
  # @option opts [Boolean] :persist_nil Optional attribute used to
243
190
  # indicate whether nil values should be persisted. If true, explicitly
244
191
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -260,11 +207,6 @@ module Aws
260
207
  # the hash key for the table.
261
208
  # @option opts [Boolean] :range_key Set to true if this attribute is
262
209
  # 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
210
  # @option opts [Boolean] :persist_nil Optional attribute used to
269
211
  # indicate whether nil values should be persisted. If true, explicitly
270
212
  # set nil values will be saved to DynamoDB as a "null" type. If false,
@@ -304,17 +246,11 @@ module Aws
304
246
  # the hash key for the table.
305
247
  # @option opts [Boolean] :range_key Set to true if this attribute is
306
248
  # 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
249
  # @option opts [Object] :default_value Optional attribute used to
313
250
  # define a "default value" to be used if the attribute's value on an
314
251
  # item is nil or not set at persistence time.
315
252
  def list_attr(name, opts = {})
316
253
  opts[:dynamodb_type] = "L"
317
- opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
318
254
  attr(name, Marshalers::ListMarshaler.new(opts), opts)
319
255
  end
320
256
 
@@ -345,17 +281,11 @@ module Aws
345
281
  # the hash key for the table.
346
282
  # @option opts [Boolean] :range_key Set to true if this attribute is
347
283
  # 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
284
  # @option opts [Object] :default_value Optional attribute used to
354
285
  # define a "default value" to be used if the attribute's value on an
355
286
  # item is nil or not set at persistence time.
356
287
  def map_attr(name, opts = {})
357
288
  opts[:dynamodb_type] = "M"
358
- opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
359
289
  attr(name, Marshalers::MapMarshaler.new(opts), opts)
360
290
  end
361
291
 
@@ -376,17 +306,11 @@ module Aws
376
306
  # the hash key for the table.
377
307
  # @option opts [Boolean] :range_key Set to true if this attribute is
378
308
  # 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
309
  # @option opts [Object] :default_value Optional attribute used to
385
310
  # define a "default value" to be used if the attribute's value on an
386
311
  # item is nil or not set at persistence time.
387
312
  def string_set_attr(name, opts = {})
388
313
  opts[:dynamodb_type] = "SS"
389
- opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
390
314
  attr(name, Marshalers::StringSetMarshaler.new(opts), opts)
391
315
  end
392
316
 
@@ -407,118 +331,57 @@ module Aws
407
331
  # the hash key for the table.
408
332
  # @option opts [Boolean] :range_key Set to true if this attribute is
409
333
  # 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
334
  # @option opts [Object] :default_value Optional attribute used to
416
335
  # define a "default value" to be used if the attribute's value on an
417
336
  # item is nil or not set at persistence time.
418
337
  def numeric_set_attr(name, opts = {})
419
338
  opts[:dynamodb_type] = "NS"
420
- opts[:mutation_tracking] = true if opts[:mutation_tracking].nil?
421
339
  attr(name, Marshalers::NumericSetMarshaler.new(opts), opts)
422
340
  end
423
341
 
424
- # @return [Hash] hash of symbolized attribute names to attribute objects
425
- def attributes
426
- @attributes
427
- end
428
-
429
- # @return [Hash] hash of database names to attribute names
430
- def storage_attributes
431
- @storage_attributes
432
- end
433
-
434
- # @return [Aws::Record::Attribute,nil]
342
+ # @return [Symbol,nil]
435
343
  def hash_key
436
- @attributes[@keys[:hash]]
344
+ @keys.hash_key
437
345
  end
438
346
 
439
- # @return [Aws::Record::Attribute,nil]
347
+ # @return [Symbol,nil]
440
348
  def range_key
441
- @attributes[@keys[:range]]
349
+ @keys.range_key
442
350
  end
443
351
 
444
- # @return [Hash] A mapping of the :hash and :range keys to the attribute
445
- # name symbols associated with them.
446
- def keys
447
- @keys
352
+ # @api private
353
+ def attributes
354
+ @attributes
448
355
  end
449
356
 
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?
357
+ # @api private
358
+ def keys
359
+ @keys.keys
455
360
  end
456
361
 
457
362
  private
458
- def define_attr_methods(name, attribute)
363
+ def _define_attr_methods(name)
459
364
  define_method(name) do
460
- read_attribute(name, attribute)
365
+ @data.get_attribute(name)
461
366
  end
462
367
 
463
368
  define_method("#{name}=") do |value|
464
- write_attribute(name, attribute, value)
369
+ @data.set_attribute(name, value)
465
370
  end
466
371
  end
467
372
 
468
- def key_attributes(id, opts)
373
+ def _key_attributes(id, opts)
469
374
  if opts[:hash_key] == true && opts[:range_key] == true
470
375
  raise ArgumentError.new(
471
376
  "Cannot have the same attribute be a hash and range key."
472
377
  )
473
378
  elsif opts[:hash_key] == true
474
- define_key(id, :hash)
379
+ @keys.hash_key = id
475
380
  elsif opts[:range_key] == true
476
- define_key(id, :range)
477
- end
478
- end
479
-
480
- def define_key(id, type)
481
- @keys[type] = id
482
- end
483
-
484
- def validate_attr_name(name)
485
- unless name.is_a?(Symbol)
486
- raise ArgumentError.new("Must use symbolized :name attribute.")
487
- end
488
- if @attributes[name]
489
- raise Errors::NameCollision.new(
490
- "Cannot overwrite existing attribute #{name}"
491
- )
381
+ @keys.range_key = id
492
382
  end
493
383
  end
494
384
 
495
- def check_if_reserved(name)
496
- if instance_methods.include?(name)
497
- raise Errors::ReservedName.new(
498
- "Cannot name an attribute #{name}, that would collide with an"\
499
- " existing instance method."
500
- )
501
- end
502
- end
503
-
504
- def check_for_naming_collisions(name, storage_name)
505
- if @attributes[storage_name.to_sym]
506
- raise Errors::NameCollision.new(
507
- "Custom storage name #{storage_name} already exists as an"\
508
- " attribute name in #{@attributes}"
509
- )
510
- elsif @storage_attributes[name.to_s]
511
- raise Errors::NameCollision.new(
512
- "Attribute name #{name} already exists as a custom storage"\
513
- " name in #{@storage_attributes}"
514
- )
515
- elsif @storage_attributes[storage_name]
516
- raise Errors::NameCollision.new(
517
- "Custom storage name #{storage_name} already in use in"\
518
- " #{@storage_attributes}"
519
- )
520
- end
521
- end
522
385
  end
523
386
 
524
387
  end
@@ -19,15 +19,6 @@ module Aws
19
19
  sub_class.extend(DirtyTrackingClassMethods)
20
20
  end
21
21
 
22
- # @private
23
- #
24
- # @override initialize(*)
25
- def initialize(*)
26
- @dirty_data = {}
27
- @mutation_copies = {}
28
- super
29
- end
30
-
31
22
  # Returns +true+ if the specified attribute has any dirty changes, +false+ otherwise.
32
23
  #
33
24
  # @example
@@ -45,7 +36,7 @@ module Aws
45
36
  # @param [String, Symbol] name The name of the attribute to to check for dirty changes.
46
37
  # @return [Boolean] +true+ if the specified attribute has any dirty changes, +false+ otherwise.
47
38
  def attribute_dirty?(name)
48
- @dirty_data.has_key?(name) || _mutated?(name)
39
+ @data.attribute_dirty?(name)
49
40
  end
50
41
 
51
42
  # Returns the original value of the specified attribute.
@@ -64,11 +55,7 @@ module Aws
64
55
  # @param [String, Symbol] name The name of the attribute to retrieve the original value of.
65
56
  # @return [Object] The original value of the specified attribute.
66
57
  def attribute_was(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
58
+ @data.attribute_was(name)
72
59
  end
73
60
 
74
61
  # Mark that an attribute is changing. This is useful in situations where it is necessary to track that the value of an
@@ -106,44 +93,7 @@ module Aws
106
93
  # @param [String, Symbol] name The name of the attribute to mark as
107
94
  # changing.
108
95
  def attribute_dirty!(name)
109
- return if attribute_dirty?(name)
110
-
111
- current_value = @data[name]
112
-
113
- @dirty_data[name] =
114
- begin
115
- _deep_copy(current_value)
116
- rescue TypeError
117
- current_value
118
- end
119
- end
120
-
121
- # Marks the changes as applied by clearing the current changes and making
122
- # them accessible through +previous_changes+.
123
- #
124
- # # @example
125
- # class Model
126
- # include Aws::Record
127
- # integer_attr :id, hash_key: true
128
- # string_attr :name
129
- # end
130
- #
131
- # model.name # => 'Alex'
132
- # model.name = 'Nick'
133
- # model.dirty? # => true
134
- #
135
- # model.clean!
136
- # model.dirty? # false
137
- #
138
- def clean!
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
96
+ @data.attribute_dirty!(name)
147
97
  end
148
98
 
149
99
  # Returns an array with the name of the attributes with dirty changes.
@@ -162,13 +112,7 @@ module Aws
162
112
  #
163
113
  # @return [Array] The names of attributes with dirty changes.
164
114
  def dirty
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
115
+ @data.dirty
172
116
  end
173
117
 
174
118
  # Returns +true+ if any attributes have dirty changes, +false+ otherwise.
@@ -188,10 +132,7 @@ module Aws
188
132
  # @return [Boolean] +true+ if any attributes have dirty changes, +false+
189
133
  # otherwise.
190
134
  def dirty?
191
- return true if @dirty_data.size > 0
192
- @mutation_copies.any? do |name, value|
193
- @mutation_copies[name] != @data[name]
194
- end
135
+ @data.dirty?
195
136
  end
196
137
 
197
138
  # Fetches attributes for this instance of an item from Amazon DynamoDB
@@ -236,14 +177,7 @@ module Aws
236
177
  #
237
178
  # @param [String, Symbol] name The name of the attribute to restore
238
179
  def rollback_attribute!(name)
239
- return unless attribute_dirty?(name)
240
-
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
180
+ @data.rollback_attribute!(name)
247
181
  end
248
182
 
249
183
  # Restores all attributes to their original values.
@@ -265,6 +199,11 @@ module Aws
265
199
  Array(names).each { |name| rollback_attribute!(name) }
266
200
  end
267
201
 
202
+ # @api private
203
+ def clean!
204
+ @data.clean!
205
+ end
206
+
268
207
  # @private
269
208
  #
270
209
  # @override save(*)
@@ -272,36 +211,6 @@ module Aws
272
211
  super.tap { clean! }
273
212
  end
274
213
 
275
- private
276
-
277
- # @private
278
- #
279
- # @override write_attribute(*)
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)
286
- if value == attribute_was(name)
287
- @dirty_data.delete(name)
288
- else
289
- attribute_dirty!(name)
290
- end
291
- end
292
-
293
- def _mutated?(name)
294
- if @mutation_copies.has_key?(name)
295
- @data[name] != @mutation_copies[name]
296
- else
297
- false
298
- end
299
- end
300
-
301
- def _deep_copy(obj)
302
- Marshal.load(Marshal.dump(obj))
303
- end
304
-
305
214
  module DirtyTrackingClassMethods
306
215
 
307
216
  private
@@ -316,7 +225,7 @@ module Aws
316
225
  # @private
317
226
  #
318
227
  # @override define_attr_methods(*)
319
- def define_attr_methods(name, attribute)
228
+ def _define_attr_methods(name)
320
229
  super.tap do
321
230
  define_method("#{name}_dirty?") do
322
231
  attribute_dirty?(name)
@@ -43,9 +43,10 @@ module Aws
43
43
  items.each do |item|
44
44
  record = model.new
45
45
  data = record.instance_variable_get("@data")
46
- model.attributes.each do |name, attr|
47
- data[name] = attr.extract(item)
46
+ model.attributes.attributes.each do |name, attr|
47
+ data.set_attribute(name, attr.extract(item))
48
48
  end
49
+ data.clean!
49
50
  ret << record
50
51
  end
51
52
  ret
@@ -0,0 +1,123 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ module Aws
15
+ module Record
16
+
17
+ # @api private
18
+ class ItemData
19
+ def initialize(model_attributes, opts)
20
+ @data = {}
21
+ @clean_copies = {}
22
+ @dirty_flags = {}
23
+ @model_attributes = model_attributes
24
+ @track_mutations = opts[:track_mutations]
25
+ @track_mutations = true if opts[:track_mutations].nil?
26
+ end
27
+
28
+ def get_attribute(name)
29
+ @model_attributes.attribute_for(name).type_cast(@data[name])
30
+ end
31
+
32
+ def set_attribute(name, value)
33
+ @data[name] = value
34
+ end
35
+
36
+ def raw_value(name)
37
+ @data[name]
38
+ end
39
+
40
+ def clean!
41
+ @dirty_flags = {}
42
+ @model_attributes.attributes.each_key do |name|
43
+ populate_default_values
44
+ value = get_attribute(name)
45
+ if @track_mutations
46
+ @clean_copies[name] = _deep_copy(value)
47
+ else
48
+ @clean_copies[name] = value
49
+ end
50
+ end
51
+ end
52
+
53
+ def attribute_dirty?(name)
54
+ if @dirty_flags[name]
55
+ true
56
+ else
57
+ value = get_attribute(name)
58
+ value != @clean_copies[name]
59
+ end
60
+ end
61
+
62
+ def attribute_was(name)
63
+ @clean_copies[name]
64
+ end
65
+
66
+ def attribute_dirty!(name)
67
+ @dirty_flags[name] = true
68
+ end
69
+
70
+ def dirty
71
+ @model_attributes.attributes.keys.inject([]) do |acc, name|
72
+ acc << name if attribute_dirty?(name)
73
+ acc
74
+ end
75
+ end
76
+
77
+ def dirty?
78
+ dirty.empty? ? false : true
79
+ end
80
+
81
+ def rollback_attribute!(name)
82
+ if attribute_dirty?(name)
83
+ @dirty_flags.delete(name)
84
+ set_attribute(name, attribute_was(name))
85
+ end
86
+ get_attribute(name)
87
+ end
88
+
89
+ def hash_copy
90
+ @data.dup
91
+ end
92
+
93
+ def build_save_hash
94
+ @data.inject({}) do |acc, name_value_pair|
95
+ attr_name, raw_value = name_value_pair
96
+ attribute = @model_attributes.attribute_for(attr_name)
97
+ if !raw_value.nil? || attribute.persist_nil?
98
+ db_name = attribute.database_name
99
+ acc[db_name] = attribute.serialize(raw_value)
100
+ end
101
+ acc
102
+ end
103
+ end
104
+
105
+ def populate_default_values
106
+ @model_attributes.attributes.each do |name, attribute|
107
+ unless attribute.default_value.nil?
108
+ if @data[name].nil? && @data[name].nil?
109
+ @data[name] = attribute.default_value
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+ def _deep_copy(obj)
117
+ Marshal.load(Marshal.dump(obj))
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
@@ -154,33 +154,17 @@ module Aws
154
154
 
155
155
  def _build_item_for_save
156
156
  validate_key_values
157
- attributes = self.class.attributes
158
- _populate_default_values(attributes)
159
- @data.inject({}) do |acc, name_value_pair|
160
- attr_name, raw_value = name_value_pair
161
- attribute = attributes[attr_name]
162
- if !raw_value.nil? || attribute.persist_nil?
163
- db_name = attribute.database_name
164
- acc[db_name] = attribute.serialize(raw_value)
165
- end
166
- acc
167
- end
168
- end
169
-
170
- def _populate_default_values(attributes)
171
- attributes.each do |attr_name, attribute|
172
- if !attribute.default_value.nil? && @data[attribute.name].nil?
173
- @data[attr_name] = attribute.default_value
174
- end
175
- end
157
+ @data.populate_default_values
158
+ @data.build_save_hash
176
159
  end
177
160
 
178
161
  def key_values
179
162
  validate_key_values
180
163
  attributes = self.class.attributes
181
164
  self.class.keys.inject({}) do |acc, (_, attr_name)|
182
- db_name = attributes[attr_name].database_name
183
- acc[db_name] = attributes[attr_name].serialize(@data[attr_name])
165
+ db_name = attributes.storage_name_for(attr_name)
166
+ acc[db_name] = attributes.attribute_for(attr_name).
167
+ serialize(@data.raw_value(attr_name))
184
168
  acc
185
169
  end
186
170
  end
@@ -196,7 +180,7 @@ module Aws
196
180
 
197
181
  def missing_key_values
198
182
  self.class.keys.inject([]) do |acc, key|
199
- acc << key.last if @data[key.last].nil?
183
+ acc << key.last if @data.raw_value(key.last).nil?
200
184
  acc
201
185
  end
202
186
  end
@@ -211,13 +195,14 @@ module Aws
211
195
  def prevent_overwrite_expression
212
196
  conditions = []
213
197
  expression_attribute_names = {}
198
+ keys = self.class.instance_variable_get("@keys")
214
199
  # Hash Key
215
200
  conditions << "attribute_not_exists(#H)"
216
- expression_attribute_names["#H"] = self.class.hash_key.database_name
201
+ expression_attribute_names["#H"] = keys.hash_key_attribute.database_name
217
202
  # Range Key
218
203
  if self.class.range_key
219
204
  conditions << "attribute_not_exists(#R)"
220
- expression_attribute_names["#R"] = self.class.range_key.database_name
205
+ expression_attribute_names["#R"] = keys.range_key_attribute.database_name
221
206
  end
222
207
  {
223
208
  condition_expression: conditions.join(" and "),
@@ -228,7 +213,7 @@ module Aws
228
213
  def _dirty_changes_for_update
229
214
  attributes = self.class.attributes
230
215
  ret = dirty.inject({}) do |acc, attr_name|
231
- acc[attr_name] = @data[attr_name]
216
+ acc[attr_name] = @data.raw_value(attr_name)
232
217
  acc
233
218
  end
234
219
  ret
@@ -252,14 +237,15 @@ module Aws
252
237
  # not include all table keys.
253
238
  def find(opts)
254
239
  key = {}
255
- @keys.each_value do |attr_sym|
240
+ @keys.keys.each_value do |attr_sym|
256
241
  unless opts[attr_sym]
257
242
  raise Errors::KeyMissing.new(
258
243
  "Missing required key #{attr_sym} in #{opts}"
259
244
  )
260
245
  end
261
- attr_name = attributes[attr_sym].database_name
262
- key[attr_name] = attributes[attr_sym].serialize(opts[attr_sym])
246
+ attr_name = attributes.storage_name_for(attr_sym)
247
+ key[attr_name] = attributes.attribute_for(attr_sym).
248
+ serialize(opts[attr_sym])
263
249
  end
264
250
  request_opts = {
265
251
  table_name: table_name,
@@ -298,14 +284,14 @@ module Aws
298
284
  def update(opts)
299
285
  key = {}
300
286
  updates = {}
301
- @keys.each_value do |attr_sym|
287
+ @keys.keys.each_value do |attr_sym|
302
288
  unless value = opts.delete(attr_sym)
303
289
  raise Errors::KeyMissing.new(
304
290
  "Missing required key #{attr_sym} in #{opts}"
305
291
  )
306
292
  end
307
- attr_name = attributes[attr_sym].database_name
308
- key[attr_name] = attributes[attr_sym].serialize(value)
293
+ attr_name = attributes.storage_name_for(attr_sym)
294
+ key[attr_name] = attributes.attribute_for(attr_sym).serialize(value)
309
295
  end
310
296
  request_opts = {
311
297
  table_name: table_name,
@@ -335,8 +321,8 @@ module Aws
335
321
  name_sub_token = name_sub_token.succ
336
322
  value_sub_token = value_sub_token.succ
337
323
 
338
- attribute = attributes[attr_sym]
339
- attr_name = attribute.database_name
324
+ attribute = attributes.attribute_for(attr_sym)
325
+ attr_name = attributes.storage_name_for(attr_sym)
340
326
  exp_attr_names[name_sub] = attr_name
341
327
  if _update_type_remove?(attribute, value)
342
328
  remove_expressions << "#{name_sub}"
@@ -362,8 +348,8 @@ module Aws
362
348
  def build_item_from_resp(resp)
363
349
  record = new
364
350
  data = record.instance_variable_get("@data")
365
- attributes.each do |name, attr|
366
- data[name] = attr.extract(resp.item)
351
+ attributes.attributes.each do |name, attr|
352
+ data.set_attribute(name, attr.extract(resp.item))
367
353
  end
368
354
  record
369
355
  end
@@ -0,0 +1,54 @@
1
+ # Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ module Aws
15
+ module Record
16
+
17
+ # @api private
18
+ class KeyAttributes
19
+ attr_reader :keys
20
+
21
+ def initialize(model_attributes)
22
+ @keys = {}
23
+ @model_attributes = model_attributes
24
+ end
25
+
26
+ def hash_key
27
+ @hash_key
28
+ end
29
+
30
+ def hash_key_attribute
31
+ @model_attributes.attribute_for(hash_key)
32
+ end
33
+
34
+ def range_key
35
+ @range_key
36
+ end
37
+
38
+ def range_key_attribute
39
+ @model_attributes.attribute_for(range_key)
40
+ end
41
+
42
+ def hash_key=(value)
43
+ @keys[:hash] = value
44
+ @hash_key = value
45
+ end
46
+
47
+ def range_key=(value)
48
+ @keys[:range] = value
49
+ @range_key = value
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,99 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ module Aws
15
+ module Record
16
+
17
+ # @api private
18
+ class ModelAttributes
19
+ attr_reader :attributes, :storage_attributes
20
+
21
+ def initialize(model_class)
22
+ @model_class = model_class
23
+ @attributes = {}
24
+ @storage_attributes = {}
25
+ end
26
+
27
+ def register_attribute(name, marshaler, opts)
28
+ attribute = Attribute.new(name, opts.merge(marshaler: marshaler))
29
+ _new_attr_validation(name, attribute)
30
+ @attributes[name] = attribute
31
+ @storage_attributes[attribute.database_name] = name
32
+ attribute
33
+ end
34
+
35
+ def attribute_for(name)
36
+ @attributes[name]
37
+ end
38
+
39
+ def storage_name_for(name)
40
+ attribute_for(name).database_name
41
+ end
42
+
43
+ def present?(name)
44
+ attribute_for(name) ? true : false
45
+ end
46
+
47
+ def db_to_attribute_name(storage_name)
48
+ @storage_attributes[storage_name]
49
+ end
50
+
51
+ private
52
+ def _new_attr_validation(name, attribute)
53
+ _validate_attr_name(name)
54
+ _check_for_naming_collisions(name, attribute.database_name)
55
+ _check_if_reserved(name)
56
+ end
57
+
58
+ def _validate_attr_name(name)
59
+ unless name.is_a?(Symbol)
60
+ raise ArgumentError.new("Must use symbolized :name attribute.")
61
+ end
62
+ if @attributes[name]
63
+ raise Errors::NameCollision.new(
64
+ "Cannot overwrite existing attribute #{name}"
65
+ )
66
+ end
67
+ end
68
+
69
+ def _check_if_reserved(name)
70
+ if @model_class.instance_methods.include?(name)
71
+ raise Errors::ReservedName.new(
72
+ "Cannot name an attribute #{name}, that would collide with an"\
73
+ " existing instance method."
74
+ )
75
+ end
76
+ end
77
+
78
+ def _check_for_naming_collisions(name, storage_name)
79
+ if @attributes[storage_name.to_sym]
80
+ raise Errors::NameCollision.new(
81
+ "Custom storage name #{storage_name} already exists as an"\
82
+ " attribute name in #{@attributes}"
83
+ )
84
+ elsif @storage_attributes[name.to_s]
85
+ raise Errors::NameCollision.new(
86
+ "Attribute name #{name} already exists as a custom storage"\
87
+ " name in #{@storage_attributes}"
88
+ )
89
+ elsif @storage_attributes[storage_name]
90
+ raise Errors::NameCollision.new(
91
+ "Custom storage name #{storage_name} already in use in"\
92
+ " #{@storage_attributes}"
93
+ )
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
@@ -37,7 +37,7 @@ module Aws
37
37
  # are copied from the table to the index. See shape details in the
38
38
  # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Types/Projection.html AWS SDK for Ruby V2 docs}.
39
39
  def local_secondary_index(name, opts)
40
- opts[:hash_key] = hash_key.name
40
+ opts[:hash_key] = hash_key
41
41
  _validate_required_lsi_keys(opts)
42
42
  local_secondary_indexes[name] = opts
43
43
  end
@@ -102,12 +102,12 @@ module Aws
102
102
  def _si_key_schema(opts)
103
103
  key_schema = [{
104
104
  key_type: "HASH",
105
- attribute_name: @attributes[opts[:hash_key]].database_name
105
+ attribute_name: @attributes.storage_name_for(opts[:hash_key])
106
106
  }]
107
107
  if opts[:range_key]
108
108
  key_schema << {
109
109
  key_type: "RANGE",
110
- attribute_name: @attributes[opts[:range_key]].database_name
110
+ attribute_name: @attributes.storage_name_for(opts[:range_key])
111
111
  }
112
112
  end
113
113
  key_schema
@@ -139,13 +139,13 @@ module Aws
139
139
 
140
140
  def _validate_attributes_exist(*attr_names)
141
141
  missing = attr_names.select do |attr_name|
142
- @attributes[attr_name].nil?
142
+ !@attributes.present?(attr_name)
143
143
  end
144
144
  unless missing.empty?
145
145
  raise ArgumentError.new(
146
146
  "#{missing.join(", ")} not present in model attributes."\
147
147
  " Please ensure that attributes are defined in the model"\
148
- " class BEFORE defining a index on those attributes."
148
+ " class BEFORE defining an index on those attributes."
149
149
  )
150
150
  end
151
151
  end
@@ -133,7 +133,7 @@ module Aws
133
133
  private
134
134
  def _assert_model_valid(model)
135
135
  _assert_required_include(model)
136
- _assert_keys(model)
136
+ model.model_valid?
137
137
  end
138
138
 
139
139
  def _assert_required_include(model)
@@ -142,12 +142,6 @@ module Aws
142
142
  end
143
143
  end
144
144
 
145
- def _assert_keys(model)
146
- if model.hash_key.nil?
147
- raise Errors::InvalidModel.new("Table models must include a hash key")
148
- end
149
- end
150
-
151
145
  def _attribute_definitions
152
146
  _keys.map do |type, attr|
153
147
  {
@@ -158,6 +152,7 @@ module Aws
158
152
  end
159
153
 
160
154
  def _append_to_attribute_definitions(secondary_indexes, create_opts)
155
+ attributes = @model.attributes
161
156
  attr_def = create_opts[:attribute_definitions]
162
157
  secondary_indexes.each do |si|
163
158
  si[:key_schema].each do |key_schema|
@@ -165,9 +160,9 @@ module Aws
165
160
  a[:attribute_name] == key_schema[:attribute_name]
166
161
  }
167
162
  unless exists
168
- attr = @model.attributes[
169
- @model.storage_attributes[key_schema[:attribute_name]]
170
- ]
163
+ attr = attributes.attribute_for(
164
+ attributes.db_to_attribute_name(key_schema[:attribute_name])
165
+ )
171
166
  attr_def << {
172
167
  attribute_name: attr.database_name,
173
168
  attribute_type: attr.dynamodb_type
@@ -208,7 +203,7 @@ module Aws
208
203
 
209
204
  def _keys
210
205
  @model.keys.inject({}) do |acc, (type, name)|
211
- acc[type] = @model.attributes[name]
206
+ acc[type] = @model.attributes.attribute_for(name)
212
207
  acc
213
208
  end
214
209
  end
@@ -13,6 +13,6 @@
13
13
 
14
14
  module Aws
15
15
  module Record
16
- VERSION = '1.0.0.pre.9'
16
+ VERSION = '1.0.0.pre.10'
17
17
  end
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.9
4
+ version: 1.0.0.pre.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-22 00:00:00.000000000 Z
11
+ date: 2016-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-resources
@@ -38,7 +38,9 @@ files:
38
38
  - lib/aws-record/record/dirty_tracking.rb
39
39
  - lib/aws-record/record/errors.rb
40
40
  - lib/aws-record/record/item_collection.rb
41
+ - lib/aws-record/record/item_data.rb
41
42
  - lib/aws-record/record/item_operations.rb
43
+ - lib/aws-record/record/key_attributes.rb
42
44
  - lib/aws-record/record/marshalers/boolean_marshaler.rb
43
45
  - lib/aws-record/record/marshalers/date_marshaler.rb
44
46
  - lib/aws-record/record/marshalers/date_time_marshaler.rb
@@ -49,6 +51,7 @@ files:
49
51
  - lib/aws-record/record/marshalers/numeric_set_marshaler.rb
50
52
  - lib/aws-record/record/marshalers/string_marshaler.rb
51
53
  - lib/aws-record/record/marshalers/string_set_marshaler.rb
54
+ - lib/aws-record/record/model_attributes.rb
52
55
  - lib/aws-record/record/query.rb
53
56
  - lib/aws-record/record/secondary_indexes.rb
54
57
  - lib/aws-record/record/table_migration.rb