dynamoid 3.2.0 → 3.3.0

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.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Dynamoid
6
+ module Persistence
7
+ class Import
8
+ def self.call(model_class, array_of_attributes)
9
+ new(model_class, array_of_attributes).call
10
+ end
11
+
12
+ def initialize(model_class, array_of_attributes)
13
+ @model_class = model_class
14
+ @array_of_attributes = array_of_attributes
15
+ end
16
+
17
+ def call
18
+ models = @array_of_attributes.map(&method(:build_model))
19
+
20
+ unless Dynamoid.config.backoff
21
+ import(models)
22
+ else
23
+ import_with_backoff(models)
24
+ end
25
+
26
+ models.each { |d| d.new_record = false }
27
+ models
28
+ end
29
+
30
+ private
31
+
32
+ def build_model(attributes)
33
+ attrs = attributes.symbolize_keys
34
+
35
+ if Dynamoid::Config.timestamps
36
+ time_now = DateTime.now.in_time_zone(Time.zone)
37
+ attrs[:created_at] ||= time_now
38
+ attrs[:updated_at] ||= time_now
39
+ end
40
+
41
+ @model_class.build(attrs).tap do |model|
42
+ model.hash_key = SecureRandom.uuid if model.hash_key.blank?
43
+ end
44
+ end
45
+
46
+ def import_with_backoff(models)
47
+ backoff = nil
48
+ table_name = @model_class.table_name
49
+ items = array_of_dumped_attributes(models)
50
+
51
+ Dynamoid.adapter.batch_write_item(table_name, items) do |has_unprocessed_items|
52
+ if has_unprocessed_items
53
+ backoff ||= Dynamoid.config.build_backoff
54
+ backoff.call
55
+ else
56
+ backoff = nil
57
+ end
58
+ end
59
+ end
60
+
61
+ def import(models)
62
+ Dynamoid.adapter.batch_write_item(@model_class.table_name, array_of_dumped_attributes(models))
63
+ end
64
+
65
+ def array_of_dumped_attributes(models)
66
+ models.map do |m|
67
+ Dumping.dump_attributes(m.attributes, @model_class.attributes)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Persistence
5
+ class Save
6
+ def self.call(model)
7
+ new(model).call
8
+ end
9
+
10
+ def initialize(model)
11
+ @model = model
12
+ end
13
+
14
+ def call
15
+ @model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
16
+
17
+ # Add an optimistic locking check if the lock_version column exists
18
+ if @model.class.attributes[:lock_version]
19
+ @model.lock_version = (@model.lock_version || 0) + 1
20
+ end
21
+
22
+ attributes_dumped = Dumping.dump_attributes(@model.attributes, @model.class.attributes)
23
+ Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
24
+
25
+ @model.new_record = false
26
+ true
27
+ rescue Dynamoid::Errors::ConditionalCheckFailedException => e
28
+ if @model.new_record?
29
+ raise Dynamoid::Errors::RecordNotUnique.new(e, @model)
30
+ else
31
+ raise Dynamoid::Errors::StaleObjectError.new(@model, 'persist')
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Should be called after incrementing `lock_version` attribute
38
+ def conditions_for_write
39
+ conditions = {}
40
+
41
+ # Add an 'exists' check to prevent overwriting existing records with new ones
42
+ if @model.new_record?
43
+ conditions[:unless_exists] = [@model.class.hash_key]
44
+ if @model.range_key
45
+ conditions[:unless_exists] << @model.range_key
46
+ end
47
+ end
48
+
49
+ # Add an optimistic locking check if the lock_version column exists
50
+ if @model.class.attributes[:lock_version]
51
+ # Uses the original lock_version value from Dirty API
52
+ # in case user changed 'lock_version' manually
53
+ if @model.changes[:lock_version][0]
54
+ conditions[:if] ||= {}
55
+ conditions[:if][:lock_version] = @model.changes[:lock_version][0]
56
+ end
57
+ end
58
+
59
+ conditions
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Persistence
5
+ class UpdateFields
6
+ def self.call(*args)
7
+ new(*args).call
8
+ end
9
+
10
+ def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
11
+ @model_class = model_class
12
+ @partition_key = partition_key
13
+ @sort_key = sort_key
14
+ @attributes = attributes.symbolize_keys
15
+ @conditions = conditions
16
+ end
17
+
18
+ def call
19
+ if Dynamoid::Config.timestamps
20
+ @attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
21
+ end
22
+
23
+ raw_attributes = update_item
24
+ @model_class.new(undump_attributes(raw_attributes))
25
+ rescue Dynamoid::Errors::ConditionalCheckFailedException
26
+ end
27
+
28
+ private
29
+
30
+ def update_item
31
+ Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
32
+ @attributes.each do |k, v|
33
+ value_casted = TypeCasting.cast_field(v, @model_class.attributes[k])
34
+ value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
35
+ t.set(k => value_dumped)
36
+ end
37
+ end
38
+ end
39
+
40
+ def undump_attributes(attributes)
41
+ Undumping.undump_attributes(attributes, @model_class.attributes)
42
+ end
43
+
44
+ def options_to_update_item
45
+ options = {}
46
+
47
+ if @model_class.range_key
48
+ value_casted = TypeCasting.cast_field(@sort_key, @model_class.attributes[@model_class.range_key])
49
+ value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[@model_class.range_key])
50
+ options[:range_key] = value_dumped
51
+ end
52
+
53
+ conditions = @conditions.deep_dup
54
+ conditions[:if_exists] ||= {}
55
+ conditions[:if_exists][@model_class.hash_key] = @partition_key
56
+ options[:conditions] = conditions
57
+
58
+ options
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Persistence
5
+ class Upsert
6
+ def self.call(*args)
7
+ new(*args).call
8
+ end
9
+
10
+ def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
11
+ @model_class = model_class
12
+ @partition_key = partition_key
13
+ @sort_key = sort_key
14
+ @attributes = attributes.symbolize_keys
15
+ @conditions = conditions
16
+ end
17
+
18
+ def call
19
+ if Dynamoid::Config.timestamps
20
+ @attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
21
+ end
22
+
23
+ raw_attributes = update_item
24
+ @model_class.new(undump_attributes(raw_attributes))
25
+ rescue Dynamoid::Errors::ConditionalCheckFailedException
26
+ end
27
+
28
+ private
29
+
30
+ def update_item
31
+ Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
32
+ @attributes.each do |k, v|
33
+ value_casted = TypeCasting.cast_field(v, @model_class.attributes[k])
34
+ value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
35
+
36
+ t.set(k => value_dumped)
37
+ end
38
+ end
39
+ end
40
+
41
+ def options_to_update_item
42
+ options = {}
43
+
44
+ if @model_class.range_key
45
+ value_casted = TypeCasting.cast_field(@sort_key, @model_class.attributes[@model_class.range_key])
46
+ value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[@model_class.range_key])
47
+ options[:range_key] = value_dumped
48
+ end
49
+
50
+ options[:conditions] = @conditions
51
+ options
52
+ end
53
+
54
+ def undump_attributes(raw_attributes)
55
+ Undumping.undump_attributes(raw_attributes, @model_class.attributes)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
- VERSION = '3.2.0'
4
+ VERSION = '3.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Symonds
@@ -21,7 +21,7 @@ authors:
21
21
  autorequire:
22
22
  bindir: exe
23
23
  cert_chain: []
24
- date: 2019-05-15 00:00:00.000000000 Z
24
+ date: 2019-08-20 00:00:00.000000000 Z
25
25
  dependencies:
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
@@ -237,9 +237,11 @@ files:
237
237
  - gemfiles/rails_5_0.gemfile
238
238
  - gemfiles/rails_5_1.gemfile
239
239
  - gemfiles/rails_5_2.gemfile
240
+ - gemfiles/rails_6_0.gemfile
240
241
  - lib/dynamoid.rb
241
242
  - lib/dynamoid/adapter.rb
242
243
  - lib/dynamoid/adapter_plugin/aws_sdk_v3.rb
244
+ - lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb
243
245
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb
244
246
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb
245
247
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb
@@ -278,8 +280,13 @@ files:
278
280
  - lib/dynamoid/finders.rb
279
281
  - lib/dynamoid/identity_map.rb
280
282
  - lib/dynamoid/indexes.rb
283
+ - lib/dynamoid/loadable.rb
281
284
  - lib/dynamoid/middleware/identity_map.rb
282
285
  - lib/dynamoid/persistence.rb
286
+ - lib/dynamoid/persistence/import.rb
287
+ - lib/dynamoid/persistence/save.rb
288
+ - lib/dynamoid/persistence/update_fields.rb
289
+ - lib/dynamoid/persistence/upsert.rb
283
290
  - lib/dynamoid/primary_key_type_mapping.rb
284
291
  - lib/dynamoid/railtie.rb
285
292
  - lib/dynamoid/tasks.rb