dynamoid 3.7.0 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +206 -238
- data/LICENSE.txt +2 -2
- data/README.md +43 -18
- data/lib/dynamoid/adapter.rb +5 -8
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +10 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +20 -14
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +2 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +45 -52
- data/lib/dynamoid/associations/single_association.rb +28 -9
- data/lib/dynamoid/criteria/chain.rb +10 -6
- data/lib/dynamoid/criteria/key_fields_detector.rb +1 -1
- data/lib/dynamoid/criteria.rb +6 -7
- data/lib/dynamoid/dirty.rb +6 -9
- data/lib/dynamoid/document.rb +4 -10
- data/lib/dynamoid/fields.rb +16 -17
- data/lib/dynamoid/indexes.rb +45 -38
- data/lib/dynamoid/loadable.rb +6 -1
- data/lib/dynamoid/persistence/update_fields.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +1 -1
- data/lib/dynamoid/persistence.rb +61 -18
- data/lib/dynamoid/undumping.rb +18 -0
- data/lib/dynamoid/validations.rb +6 -0
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -0
- metadata +40 -37
data/lib/dynamoid/fields.rb
CHANGED
@@ -8,16 +8,6 @@ module Dynamoid
|
|
8
8
|
module Fields
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
|
-
# @private
|
12
|
-
# Types allowed in indexes:
|
13
|
-
PERMITTED_KEY_TYPES = %i[
|
14
|
-
number
|
15
|
-
integer
|
16
|
-
string
|
17
|
-
datetime
|
18
|
-
serialized
|
19
|
-
].freeze
|
20
|
-
|
21
11
|
# Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
|
22
12
|
included do
|
23
13
|
class_attribute :attributes, instance_accessor: false
|
@@ -219,20 +209,26 @@ module Dynamoid
|
|
219
209
|
#
|
220
210
|
# @since 0.4.0
|
221
211
|
def table(options)
|
212
|
+
self.options = options
|
213
|
+
|
222
214
|
# a default 'id' column is created when Dynamoid::Document is included
|
223
215
|
unless attributes.key? hash_key
|
224
216
|
remove_field :id
|
225
217
|
field(hash_key)
|
226
218
|
end
|
227
219
|
|
220
|
+
# The created_at/updated_at fields are declared in the `included` callback first.
|
221
|
+
# At that moment the only known setting is `Dynamoid::Config.timestamps`.
|
222
|
+
# Now `options[:timestamps]` may override the global setting for a model.
|
223
|
+
# So we need to make decision again and declare the fields or rollback thier declaration.
|
224
|
+
#
|
225
|
+
# Do not replace with `#timestamps_enabled?`.
|
228
226
|
if options[:timestamps] && !Dynamoid::Config.timestamps
|
229
|
-
#
|
230
|
-
# are disabled globaly
|
227
|
+
# The fields weren't declared in `included` callback because they are disabled globaly
|
231
228
|
field :created_at, :datetime
|
232
229
|
field :updated_at, :datetime
|
233
230
|
elsif options[:timestamps] == false && Dynamoid::Config.timestamps
|
234
|
-
#
|
235
|
-
# disabled for a table
|
231
|
+
# The fields were declared in `included` callback but they are disabled for a table
|
236
232
|
remove_field :created_at
|
237
233
|
remove_field :updated_at
|
238
234
|
end
|
@@ -289,10 +285,12 @@ module Dynamoid
|
|
289
285
|
#
|
290
286
|
# @param name [Symbol] the name of the field
|
291
287
|
# @param value [Object] the value to assign to that field
|
288
|
+
# @return [Dynamoid::Document] self
|
292
289
|
#
|
293
290
|
# @since 0.2.0
|
294
291
|
def write_attribute(name, value)
|
295
292
|
name = name.to_sym
|
293
|
+
old_value = read_attribute(name)
|
296
294
|
|
297
295
|
unless attribute_is_present_on_model?(name)
|
298
296
|
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
|
@@ -302,12 +300,13 @@ module Dynamoid
|
|
302
300
|
association.reset
|
303
301
|
end
|
304
302
|
|
305
|
-
attribute_will_change!(name) # Dirty API
|
306
|
-
|
307
303
|
@attributes_before_type_cast[name] = value
|
308
304
|
|
309
305
|
value_casted = TypeCasting.cast_field(value, self.class.attributes[name])
|
306
|
+
attribute_will_change!(name) if old_value != value_casted # Dirty API
|
307
|
+
|
310
308
|
attributes[name] = value_casted
|
309
|
+
self
|
311
310
|
end
|
312
311
|
alias []= write_attribute
|
313
312
|
|
@@ -367,7 +366,7 @@ module Dynamoid
|
|
367
366
|
# @since 0.2.0
|
368
367
|
def set_updated_at
|
369
368
|
# @_touch_record=false means explicit disabling
|
370
|
-
if self.class.timestamps_enabled? && !updated_at_changed? && @_touch_record != false
|
369
|
+
if self.class.timestamps_enabled? && changed? && !updated_at_changed? && @_touch_record != false
|
371
370
|
self.updated_at = DateTime.now.in_time_zone(Time.zone)
|
372
371
|
end
|
373
372
|
end
|
data/lib/dynamoid/indexes.rb
CHANGED
@@ -4,6 +4,15 @@ module Dynamoid
|
|
4
4
|
module Indexes
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
# @private
|
8
|
+
# @see https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey
|
9
|
+
# Types allowed in indexes
|
10
|
+
PERMITTED_KEY_DYNAMODB_TYPES = %i[
|
11
|
+
string
|
12
|
+
binary
|
13
|
+
number
|
14
|
+
].freeze
|
15
|
+
|
7
16
|
included do
|
8
17
|
class_attribute :local_secondary_indexes, instance_accessor: false
|
9
18
|
class_attribute :global_secondary_indexes, instance_accessor: false
|
@@ -20,17 +29,17 @@ module Dynamoid
|
|
20
29
|
#
|
21
30
|
# field :category
|
22
31
|
#
|
23
|
-
#
|
32
|
+
# global_secondary_index hash_key: :category
|
24
33
|
# end
|
25
34
|
#
|
26
35
|
# The full example with all the options being specified:
|
27
36
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
37
|
+
# global_secondary_index hash_key: :category,
|
38
|
+
# range_key: :created_at,
|
39
|
+
# name: 'posts_category_created_at_index',
|
40
|
+
# projected_attributes: :all,
|
41
|
+
# read_capacity: 100,
|
42
|
+
# write_capacity: 20
|
34
43
|
#
|
35
44
|
# Global secondary index should be declared after fields for mentioned
|
36
45
|
# hash key and optional range key are declared (with method +field+)
|
@@ -86,14 +95,14 @@ module Dynamoid
|
|
86
95
|
# range :created_at, :datetime
|
87
96
|
# field :author_id
|
88
97
|
#
|
89
|
-
#
|
98
|
+
# local_secondary_index range_key: :author_id
|
90
99
|
# end
|
91
100
|
#
|
92
101
|
# The full example with all the options being specified:
|
93
102
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
103
|
+
# local_secondary_index range_key: :created_at,
|
104
|
+
# name: 'posts_created_at_index',
|
105
|
+
# projected_attributes: :all
|
97
106
|
#
|
98
107
|
# Local secondary index should be declared after fields for mentioned
|
99
108
|
# hash key and optional range key are declared (with method +field+) as
|
@@ -290,39 +299,37 @@ module Dynamoid
|
|
290
299
|
end
|
291
300
|
end
|
292
301
|
|
302
|
+
|
303
|
+
def validate_hash_key
|
304
|
+
validate_index_key(:hash_key, @hash_key)
|
305
|
+
end
|
306
|
+
|
293
307
|
def validate_range_key
|
294
|
-
|
295
|
-
range_field_attributes = @dynamoid_class.attributes[@range_key]
|
296
|
-
if range_field_attributes.present?
|
297
|
-
range_key_type = range_field_attributes[:type]
|
298
|
-
if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(range_key_type)
|
299
|
-
@range_key_schema = {
|
300
|
-
@range_key => PrimaryKeyTypeMapping.dynamodb_type(range_key_type, range_field_attributes)
|
301
|
-
}
|
302
|
-
else
|
303
|
-
errors.add(:range_key, 'Index :range_key is not a valid key type')
|
304
|
-
end
|
305
|
-
else
|
306
|
-
errors.add(:range_key, "No such field #{@range_key} defined on table")
|
307
|
-
end
|
308
|
-
end
|
308
|
+
validate_index_key(:range_key, @range_key)
|
309
309
|
end
|
310
310
|
|
311
|
-
def
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
311
|
+
def validate_index_key(key_param, key_val)
|
312
|
+
return if key_val.blank?
|
313
|
+
|
314
|
+
key_field_attributes = @dynamoid_class.attributes[key_val]
|
315
|
+
if key_field_attributes.blank?
|
316
|
+
errors.add(key_param, "No such field #{key_val} defined on table")
|
317
|
+
return
|
318
|
+
end
|
319
|
+
|
320
|
+
key_dynamodb_type = dynamodb_type(key_field_attributes[:type], key_field_attributes)
|
321
|
+
if PERMITTED_KEY_DYNAMODB_TYPES.include?(key_dynamodb_type)
|
322
|
+
self.send("#{key_param}_schema=", { key_val => key_dynamodb_type })
|
322
323
|
else
|
323
|
-
errors.add(
|
324
|
+
errors.add(key_param, "Index :#{key_param} is not a valid key type")
|
324
325
|
end
|
325
326
|
end
|
327
|
+
|
328
|
+
def dynamodb_type(field_type, options)
|
329
|
+
PrimaryKeyTypeMapping.dynamodb_type(field_type, options)
|
330
|
+
rescue Errors::UnsupportedKeyType
|
331
|
+
field_type
|
332
|
+
end
|
326
333
|
end
|
327
334
|
end
|
328
335
|
end
|
data/lib/dynamoid/loadable.rb
CHANGED
@@ -8,12 +8,14 @@ module Dynamoid
|
|
8
8
|
attrs.each do |key, value|
|
9
9
|
send("#{key}=", value) if respond_to?("#{key}=")
|
10
10
|
end
|
11
|
+
|
12
|
+
self
|
11
13
|
end
|
12
14
|
|
13
15
|
# Reload an object from the database -- if you suspect the object has changed in the data store and you need those
|
14
16
|
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
15
17
|
#
|
16
|
-
# @return [Dynamoid::Document]
|
18
|
+
# @return [Dynamoid::Document] self
|
17
19
|
#
|
18
20
|
# @since 0.2.0
|
19
21
|
def reload
|
@@ -24,7 +26,10 @@ module Dynamoid
|
|
24
26
|
end
|
25
27
|
|
26
28
|
self.attributes = self.class.find(hash_key, **options).attributes
|
29
|
+
|
27
30
|
@associations.values.each(&:reset)
|
31
|
+
@new_record = false
|
32
|
+
|
28
33
|
self
|
29
34
|
end
|
30
35
|
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -112,8 +112,10 @@ module Dynamoid
|
|
112
112
|
|
113
113
|
if created_successfuly && self.options[:expires]
|
114
114
|
attribute = self.options[:expires][:field]
|
115
|
-
Dynamoid.adapter.update_time_to_live(table_name, attribute)
|
115
|
+
Dynamoid.adapter.update_time_to_live(options[:table_name], attribute)
|
116
116
|
end
|
117
|
+
|
118
|
+
self
|
117
119
|
end
|
118
120
|
|
119
121
|
# Deletes the table for the model.
|
@@ -122,8 +124,10 @@ module Dynamoid
|
|
122
124
|
# is deleted completely.
|
123
125
|
#
|
124
126
|
# Subsequent method calls for the same table will be ignored.
|
127
|
+
# @return [Model class] self
|
125
128
|
def delete_table
|
126
129
|
Dynamoid.adapter.delete_table(table_name)
|
130
|
+
self
|
127
131
|
end
|
128
132
|
|
129
133
|
# @private
|
@@ -365,6 +369,9 @@ module Dynamoid
|
|
365
369
|
#
|
366
370
|
# User.inc('1', 'Tylor', age: 2)
|
367
371
|
#
|
372
|
+
# It's an atomic operation it does not interfere with other write
|
373
|
+
# requests.
|
374
|
+
#
|
368
375
|
# Uses efficient low-level +UpdateItem+ operation and does only one HTTP
|
369
376
|
# request.
|
370
377
|
#
|
@@ -374,6 +381,7 @@ module Dynamoid
|
|
374
381
|
# @param hash_key_value [Scalar value] hash key
|
375
382
|
# @param range_key_value [Scalar value] range key (optional)
|
376
383
|
# @param counters [Hash] value to increase by
|
384
|
+
# @return [Model class] self
|
377
385
|
def inc(hash_key_value, range_key_value = nil, counters)
|
378
386
|
options = if range_key
|
379
387
|
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
@@ -391,6 +399,8 @@ module Dynamoid
|
|
391
399
|
t.add(k => value_dumped)
|
392
400
|
end
|
393
401
|
end
|
402
|
+
|
403
|
+
self
|
394
404
|
end
|
395
405
|
end
|
396
406
|
|
@@ -405,11 +415,13 @@ module Dynamoid
|
|
405
415
|
# user.touch(:last_login_at)
|
406
416
|
#
|
407
417
|
# @param name [Symbol] attribute name to update (optional)
|
418
|
+
# @return [Dynamoid::Document] self
|
408
419
|
def touch(name = nil)
|
409
420
|
now = DateTime.now
|
410
421
|
self.updated_at = now
|
411
422
|
attributes[name] = now if name
|
412
423
|
save
|
424
|
+
self
|
413
425
|
end
|
414
426
|
|
415
427
|
# Is this object persisted in DynamoDB?
|
@@ -479,13 +491,9 @@ module Dynamoid
|
|
479
491
|
|
480
492
|
@_touch_record = options[:touch]
|
481
493
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
Save.call(self)
|
486
|
-
end
|
487
|
-
end
|
488
|
-
else
|
494
|
+
create_or_update = new_record? ? :create : :update
|
495
|
+
|
496
|
+
run_callbacks(create_or_update) do
|
489
497
|
run_callbacks(:save) do
|
490
498
|
Save.call(self)
|
491
499
|
end
|
@@ -538,10 +546,13 @@ module Dynamoid
|
|
538
546
|
# @param attribute [Symbol] attribute name to update
|
539
547
|
# @param value [Object] the value to assign it
|
540
548
|
# @return [Dynamoid::Document] self
|
549
|
+
#
|
541
550
|
# @since 0.2.0
|
542
551
|
def update_attribute(attribute, value)
|
552
|
+
# final implementation is in the Dynamoid::Validation module
|
543
553
|
write_attribute(attribute, value)
|
544
554
|
save
|
555
|
+
self
|
545
556
|
end
|
546
557
|
|
547
558
|
# Update a model.
|
@@ -555,7 +566,7 @@ module Dynamoid
|
|
555
566
|
# collections if attribute is a collection (one of +array+, +set+ or
|
556
567
|
# +map+).
|
557
568
|
#
|
558
|
-
# user.update do |t|
|
569
|
+
# user.update! do |t|
|
559
570
|
# t.add(age: 1, followers_count: 5)
|
560
571
|
# t.add(hobbies: ['skying', 'climbing'])
|
561
572
|
# end
|
@@ -563,24 +574,28 @@ module Dynamoid
|
|
563
574
|
# Operation +delete+ is applied to collection attribute types and
|
564
575
|
# substructs one collection from another.
|
565
576
|
#
|
566
|
-
# user.update do |t|
|
577
|
+
# user.update! do |t|
|
567
578
|
# t.delete(hobbies: ['skying'])
|
568
579
|
# end
|
569
580
|
#
|
570
581
|
# Operation +set+ just changes an attribute value:
|
571
582
|
#
|
572
|
-
# user.update do |t|
|
583
|
+
# user.update! do |t|
|
573
584
|
# t.set(age: 21)
|
574
585
|
# end
|
575
586
|
#
|
576
|
-
# All the operations
|
587
|
+
# All the operations work like +ADD+, +DELETE+ and +PUT+ actions supported
|
577
588
|
# by +AttributeUpdates+
|
578
589
|
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
|
579
590
|
# of +UpdateItem+ operation.
|
580
591
|
#
|
592
|
+
# It's an atomic operation. So adding or deleting elements in a collection
|
593
|
+
# or incrementing or decrementing a numeric field is atomic and does not
|
594
|
+
# interfere with other write requests.
|
595
|
+
#
|
581
596
|
# Can update a model conditionaly:
|
582
597
|
#
|
583
|
-
# user.update(if: { age: 20 }) do |t|
|
598
|
+
# user.update!(if: { age: 20 }) do |t|
|
584
599
|
# t.add(age: 1)
|
585
600
|
# end
|
586
601
|
#
|
@@ -593,15 +608,24 @@ module Dynamoid
|
|
593
608
|
# fail.
|
594
609
|
#
|
595
610
|
# @param conditions [Hash] Conditions on model attributes to make a conditional update (optional)
|
611
|
+
# @return [Dynamoid::Document] self
|
596
612
|
def update!(conditions = {})
|
597
613
|
run_callbacks(:update) do
|
598
|
-
options =
|
614
|
+
options = {}
|
615
|
+
if range_key
|
616
|
+
value = read_attribute(range_key)
|
617
|
+
attribute_options = self.class.attributes[range_key]
|
618
|
+
options[:range_key] = Dumping.dump_field(value, attribute_options)
|
619
|
+
end
|
599
620
|
|
600
621
|
begin
|
601
|
-
|
622
|
+
table_name = self.class.table_name
|
623
|
+
update_item_options = options.merge(conditions: conditions)
|
624
|
+
|
625
|
+
new_attrs = Dynamoid.adapter.update_item(table_name, hash_key, update_item_options) do |t|
|
602
626
|
t.add(lock_version: 1) if self.class.attributes[:lock_version]
|
603
627
|
|
604
|
-
if
|
628
|
+
if self.class.timestamps_enabled?
|
605
629
|
time_now = DateTime.now.in_time_zone(Time.zone)
|
606
630
|
time_now_dumped = Dumping.dump_field(time_now, self.class.attributes[:updated_at])
|
607
631
|
t.set(updated_at: time_now_dumped)
|
@@ -614,6 +638,8 @@ module Dynamoid
|
|
614
638
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'update')
|
615
639
|
end
|
616
640
|
end
|
641
|
+
|
642
|
+
self
|
617
643
|
end
|
618
644
|
|
619
645
|
# Update a model.
|
@@ -639,6 +665,19 @@ module Dynamoid
|
|
639
665
|
# t.delete(hobbies: ['skying'])
|
640
666
|
# end
|
641
667
|
#
|
668
|
+
# If it's applied to a scalar attribute then the item's attribute is
|
669
|
+
# removed at all:
|
670
|
+
#
|
671
|
+
# user.update do |t|
|
672
|
+
# t.delete(age: nil)
|
673
|
+
# end
|
674
|
+
#
|
675
|
+
# or even without useless value at all:
|
676
|
+
#
|
677
|
+
# user.update do |t|
|
678
|
+
# t.delete(:age)
|
679
|
+
# end
|
680
|
+
#
|
642
681
|
# Operation +set+ just changes an attribute value:
|
643
682
|
#
|
644
683
|
# user.update do |t|
|
@@ -664,6 +703,7 @@ module Dynamoid
|
|
664
703
|
# fail.
|
665
704
|
#
|
666
705
|
# @param conditions [Hash] Conditions on model attributes to make a conditional update (optional)
|
706
|
+
# @return [true|false] - whether conditions are met and updating is successful
|
667
707
|
def update(conditions = {}, &block)
|
668
708
|
update!(conditions, &block)
|
669
709
|
true
|
@@ -748,9 +788,9 @@ module Dynamoid
|
|
748
788
|
# Supports optimistic locking with the +lock_version+ attribute and doesn't
|
749
789
|
# delete a model if it's already changed.
|
750
790
|
#
|
751
|
-
# Returns +
|
791
|
+
# Returns +self+ if deleted successfully and +false+ otherwise.
|
752
792
|
#
|
753
|
-
# @return [
|
793
|
+
# @return [Dynamoid::Document|false] whether deleted successfully
|
754
794
|
# @since 0.2.0
|
755
795
|
def destroy
|
756
796
|
ret = run_callbacks(:destroy) do
|
@@ -783,6 +823,7 @@ module Dynamoid
|
|
783
823
|
# Raises +Dynamoid::Errors::StaleObjectError+ exception if cannot delete a
|
784
824
|
# model.
|
785
825
|
#
|
826
|
+
# @return [Dynamoid::Document] self
|
786
827
|
# @since 0.2.0
|
787
828
|
def delete
|
788
829
|
options = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {}
|
@@ -806,6 +847,8 @@ module Dynamoid
|
|
806
847
|
self.class.associations.each do |name, options|
|
807
848
|
send(name).disassociate_source
|
808
849
|
end
|
850
|
+
|
851
|
+
self
|
809
852
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
810
853
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
|
811
854
|
end
|
data/lib/dynamoid/undumping.rb
CHANGED
@@ -236,9 +236,27 @@ module Dynamoid
|
|
236
236
|
end
|
237
237
|
|
238
238
|
class SerializedUndumper < Base
|
239
|
+
# We must use YAML.safe_load in Ruby 3.1 to handle serialized Set class
|
240
|
+
minimum_ruby_version = ->(version) { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(version) }
|
241
|
+
# Once we drop support for Rubies older than 2.6 we can remove this conditional (with major version bump)!
|
242
|
+
# YAML_SAFE_LOAD = minimum_ruby_version.call("2.6")
|
243
|
+
# But we don't want to change behavior for Ruby <= 3.0 that has been using the gem, without a major version bump
|
244
|
+
YAML_SAFE_LOAD = minimum_ruby_version.call('3.1')
|
245
|
+
|
239
246
|
def process(value)
|
240
247
|
if @options[:serializer]
|
241
248
|
@options[:serializer].load(value)
|
249
|
+
elsif YAML_SAFE_LOAD
|
250
|
+
# The classes listed in permitted classes are added to the default set of "safe loadable" classes.
|
251
|
+
# TrueClass
|
252
|
+
# FalseClass
|
253
|
+
# NilClass
|
254
|
+
# Integer
|
255
|
+
# Float
|
256
|
+
# String
|
257
|
+
# Array
|
258
|
+
# Hash
|
259
|
+
YAML.safe_load(value, permitted_classes: [Symbol, Set, Date, Time, DateTime])
|
242
260
|
else
|
243
261
|
YAML.load(value)
|
244
262
|
end
|
data/lib/dynamoid/validations.rb
CHANGED
@@ -38,6 +38,12 @@ module Dynamoid
|
|
38
38
|
self
|
39
39
|
end
|
40
40
|
|
41
|
+
def update_attribute(attribute, value)
|
42
|
+
write_attribute(attribute, value)
|
43
|
+
save(validate: false)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
41
47
|
module ClassMethods
|
42
48
|
# Override validates_presence_of to handle false values as present.
|
43
49
|
#
|
data/lib/dynamoid/version.rb
CHANGED