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

Sign up to get free protection for your applications and to get access to all the features.
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