dynamoid 3.9.0 → 3.10.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -7
  3. data/README.md +20 -23
  4. data/dynamoid.gemspec +1 -2
  5. data/lib/dynamoid/adapter.rb +18 -12
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +78 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +19 -1
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +38 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +33 -27
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +87 -62
  13. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  14. data/lib/dynamoid/associations.rb +1 -1
  15. data/lib/dynamoid/config/options.rb +12 -12
  16. data/lib/dynamoid/config.rb +1 -0
  17. data/lib/dynamoid/criteria/chain.rb +95 -133
  18. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  19. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  20. data/lib/dynamoid/criteria/where_conditions.rb +29 -0
  21. data/lib/dynamoid/dirty.rb +1 -1
  22. data/lib/dynamoid/document.rb +1 -1
  23. data/lib/dynamoid/dumping.rb +2 -2
  24. data/lib/dynamoid/fields/declare.rb +6 -6
  25. data/lib/dynamoid/fields.rb +6 -8
  26. data/lib/dynamoid/finders.rb +17 -26
  27. data/lib/dynamoid/indexes.rb +6 -7
  28. data/lib/dynamoid/loadable.rb +2 -2
  29. data/lib/dynamoid/persistence/save.rb +12 -16
  30. data/lib/dynamoid/persistence/update_fields.rb +2 -2
  31. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  32. data/lib/dynamoid/persistence.rb +39 -4
  33. data/lib/dynamoid/type_casting.rb +15 -14
  34. data/lib/dynamoid/undumping.rb +1 -1
  35. data/lib/dynamoid/version.rb +1 -1
  36. metadata +17 -16
  37. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  38. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -130,13 +130,13 @@ module Dynamoid
130
130
  index_range_key = options[:range_key]
131
131
 
132
132
  unless index_range_key.present?
133
- raise Dynamoid::Errors::InvalidIndex, 'A local secondary index '\
134
- 'requires a :range_key to be specified'
133
+ raise Dynamoid::Errors::InvalidIndex, 'A local secondary index ' \
134
+ 'requires a :range_key to be specified'
135
135
  end
136
136
 
137
137
  if primary_range_key.present? && index_range_key == primary_range_key
138
- raise Dynamoid::Errors::InvalidIndex, 'A local secondary index'\
139
- ' must use a different :range_key than the primary key'
138
+ raise Dynamoid::Errors::InvalidIndex, 'A local secondary index ' \
139
+ 'must use a different :range_key than the primary key'
140
140
  end
141
141
 
142
142
  index_opts = options.merge(
@@ -159,8 +159,7 @@ module Dynamoid
159
159
  # @param range [scalar] the range key used to declare an index (optional)
160
160
  # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
161
161
  def find_index(hash, range = nil)
162
- index = indexes[index_key(hash, range)]
163
- index
162
+ indexes[index_key(hash, range)]
164
163
  end
165
164
 
166
165
  # Returns an index by its name
@@ -317,7 +316,7 @@ module Dynamoid
317
316
 
318
317
  key_dynamodb_type = dynamodb_type(key_field_attributes[:type], key_field_attributes)
319
318
  if PERMITTED_KEY_DYNAMODB_TYPES.include?(key_dynamodb_type)
320
- send("#{key_param}_schema=", { key_val => key_dynamodb_type })
319
+ send(:"#{key_param}_schema=", { key_val => key_dynamodb_type })
321
320
  else
322
321
  errors.add(key_param, "Index :#{key_param} is not a valid key type")
323
322
  end
@@ -6,7 +6,7 @@ module Dynamoid
6
6
 
7
7
  def load(attrs)
8
8
  attrs.each do |key, value|
9
- send("#{key}=", value) if respond_to?("#{key}=")
9
+ send(:"#{key}=", value) if respond_to?(:"#{key}=")
10
10
  end
11
11
 
12
12
  self
@@ -27,7 +27,7 @@ module Dynamoid
27
27
 
28
28
  self.attributes = self.class.find(hash_key, **options).attributes
29
29
 
30
- @associations.values.each(&:reset)
30
+ @associations.each_value(&:reset)
31
31
  @new_record = false
32
32
 
33
33
  self
@@ -68,13 +68,11 @@ module Dynamoid
68
68
  end
69
69
 
70
70
  # Add an optimistic locking check if the lock_version column exists
71
- if @model.class.attributes[:lock_version]
72
- # Uses the original lock_version value from Dirty API
73
- # in case user changed 'lock_version' manually
74
- if @model.changes[:lock_version][0]
75
- conditions[:if] ||= {}
76
- conditions[:if][:lock_version] = @model.changes[:lock_version][0]
77
- end
71
+ # Uses the original lock_version value from Dirty API
72
+ # in case user changed 'lock_version' manually
73
+ if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
74
+ conditions[:if] ||= {}
75
+ conditions[:if][:lock_version] = @model.changes[:lock_version][0]
78
76
  end
79
77
 
80
78
  conditions
@@ -89,17 +87,15 @@ module Dynamoid
89
87
  end
90
88
 
91
89
  conditions = {}
92
- conditions[:if_exists] ||= {}
93
- conditions[:if_exists][@model.class.hash_key] = @model.hash_key
90
+ conditions[:if] ||= {}
91
+ conditions[:if][@model.class.hash_key] = @model.hash_key
94
92
 
95
93
  # Add an optimistic locking check if the lock_version column exists
96
- if @model.class.attributes[:lock_version]
97
- # Uses the original lock_version value from Dirty API
98
- # in case user changed 'lock_version' manually
99
- if @model.changes[:lock_version][0]
100
- conditions[:if] ||= {}
101
- conditions[:if][:lock_version] = @model.changes[:lock_version][0]
102
- end
94
+ # Uses the original lock_version value from Dirty API
95
+ # in case user changed 'lock_version' manually
96
+ if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
97
+ conditions[:if] ||= {}
98
+ conditions[:if][:lock_version] = @model.changes[:lock_version][0]
103
99
  end
104
100
 
105
101
  options[:conditions] = conditions
@@ -54,8 +54,8 @@ module Dynamoid
54
54
  end
55
55
 
56
56
  conditions = @conditions.deep_dup
57
- conditions[:if_exists] ||= {}
58
- conditions[:if_exists][@model_class.hash_key] = @partition_key
57
+ conditions[:if] ||= {}
58
+ conditions[:if][@model_class.hash_key] = @partition_key
59
59
  options[:conditions] = conditions
60
60
 
61
61
  options
@@ -7,7 +7,7 @@ module Dynamoid
7
7
  def self.validate_attributes_exist(model_class, attributes)
8
8
  model_attributes = model_class.attributes.keys
9
9
 
10
- attributes.each do |attr_name, _|
10
+ attributes.each_key do |attr_name|
11
11
  unless model_attributes.include?(attr_name)
12
12
  raise Dynamoid::Errors::UnknownAttribute, "Attribute #{attr_name} does not exist in #{model_class}"
13
13
  end
@@ -271,7 +271,7 @@ module Dynamoid
271
271
  # meets the specified conditions. Conditions can be specified as a +Hash+
272
272
  # with +:if+ key:
273
273
  #
274
- # User.update_fields('1', { age: 26 }, if: { version: 1 })
274
+ # User.update_fields('1', { age: 26 }, { if: { version: 1 } })
275
275
  #
276
276
  # Here +User+ model has an integer +version+ field and the document will
277
277
  # be updated only if the +version+ attribute currently has value 1.
@@ -279,6 +279,13 @@ module Dynamoid
279
279
  # If a document with specified hash and range keys doesn't exist or
280
280
  # conditions were specified and failed the method call returns +nil+.
281
281
  #
282
+ # To check if some attribute (or attributes) isn't stored in a DynamoDB
283
+ # item (e.g. it wasn't set explicitly) there is another condition -
284
+ # +unless_exists+:
285
+ #
286
+ # user = User.create(name: 'Tylor')
287
+ # User.update_fields(user.id, { age: 18 }, { unless_exists: [:age] })
288
+ #
282
289
  # +update_fields+ uses the +UpdateItem+ operation so it saves changes and
283
290
  # loads an updated document back with one HTTP request.
284
291
  #
@@ -323,11 +330,18 @@ module Dynamoid
323
330
  # meets the specified conditions. Conditions can be specified as a +Hash+
324
331
  # with +:if+ key:
325
332
  #
326
- # User.upsert('1', { age: 26 }, if: { version: 1 })
333
+ # User.upsert('1', { age: 26 }, { if: { version: 1 } })
327
334
  #
328
335
  # Here +User+ model has an integer +version+ field and the document will
329
336
  # be updated only if the +version+ attribute currently has value 1.
330
337
  #
338
+ # To check if some attribute (or attributes) isn't stored in a DynamoDB
339
+ # item (e.g. it wasn't set explicitly) there is another condition -
340
+ # +unless_exists+:
341
+ #
342
+ # user = User.create(name: 'Tylor')
343
+ # User.upsert(user.id, { age: 18 }, { unless_exists: [:age] })
344
+ #
331
345
  # If conditions were specified and failed the method call returns +nil+.
332
346
  #
333
347
  # +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
@@ -507,7 +521,10 @@ module Dynamoid
507
521
  # @return [true|false] Whether saving successful or not
508
522
  # @since 0.2.0
509
523
  def save(options = {})
510
- self.class.create_table(sync: true)
524
+ if Dynamoid.config.create_table_on_save
525
+ self.class.create_table(sync: true)
526
+ end
527
+
511
528
  create_or_update = new_record? ? :create : :update
512
529
 
513
530
  run_callbacks(:save) do
@@ -618,6 +635,15 @@ module Dynamoid
618
635
  # t.add(age: 1)
619
636
  # end
620
637
  #
638
+ # To check if some attribute (or attributes) isn't stored in a DynamoDB
639
+ # item (e.g. it wasn't set explicitly) there is another condition -
640
+ # +unless_exists+:
641
+ #
642
+ # user = User.create(name: 'Tylor')
643
+ # user.update!(unless_exists: [:age]) do |t|
644
+ # t.set(age: 18)
645
+ # end
646
+ #
621
647
  # If a document doesn't meet conditions it raises
622
648
  # +Dynamoid::Errors::StaleObjectError+ exception.
623
649
  #
@@ -714,6 +740,15 @@ module Dynamoid
714
740
  # t.add(age: 1)
715
741
  # end
716
742
  #
743
+ # To check if some attribute (or attributes) isn't stored in a DynamoDB
744
+ # item (e.g. it wasn't set explicitly) there is another condition -
745
+ # +unless_exists+:
746
+ #
747
+ # user = User.create(name: 'Tylor')
748
+ # user.update(unless_exists: [:age]) do |t|
749
+ # t.set(age: 18)
750
+ # end
751
+ #
717
752
  # If a document doesn't meet conditions it just returns +false+. Otherwise it returns +true+.
718
753
  #
719
754
  # It will increment the +lock_version+ attribute if a table has the column,
@@ -889,7 +924,7 @@ module Dynamoid
889
924
 
890
925
  Dynamoid.adapter.delete(self.class.table_name, hash_key, options)
891
926
 
892
- self.class.associations.each do |name, _options|
927
+ self.class.associations.each_key do |name|
893
928
  send(name).disassociate_source
894
929
  end
895
930
 
@@ -57,11 +57,12 @@ module Dynamoid
57
57
 
58
58
  class StringTypeCaster < Base
59
59
  def process(value)
60
- if value == true
60
+ case value
61
+ when true
61
62
  't'
62
- elsif value == false
63
+ when false
63
64
  'f'
64
- elsif value.is_a? String
65
+ when String
65
66
  value.dup
66
67
  else
67
68
  value.to_s
@@ -71,6 +72,7 @@ module Dynamoid
71
72
 
72
73
  class IntegerTypeCaster < Base
73
74
  def process(value)
75
+ # rubocop:disable Lint/DuplicateBranch
74
76
  if value == true
75
77
  1
76
78
  elsif value == false
@@ -84,11 +86,13 @@ module Dynamoid
84
86
  else
85
87
  value.to_i
86
88
  end
89
+ # rubocop:enable Lint/DuplicateBranch
87
90
  end
88
91
  end
89
92
 
90
93
  class NumberTypeCaster < Base
91
94
  def process(value)
95
+ # rubocop:disable Lint/DuplicateBranch
92
96
  if value == true
93
97
  1
94
98
  elsif value == false
@@ -104,6 +108,7 @@ module Dynamoid
104
108
  else
105
109
  value.to_d
106
110
  end
111
+ # rubocop:enable Lint/DuplicateBranch
107
112
  end
108
113
  end
109
114
 
@@ -135,7 +140,7 @@ module Dynamoid
135
140
  raise ArgumentError, "Set element type #{element_type} isn't supported"
136
141
  end
137
142
 
138
- set.map { |el| type_caster.process(el) }.to_set
143
+ set.to_set { |el| type_caster.process(el) }
139
144
  end
140
145
 
141
146
  def element_type
@@ -227,10 +232,10 @@ module Dynamoid
227
232
  nil
228
233
  elsif value.is_a?(String)
229
234
  dt = begin
230
- DateTime.parse(value)
231
- rescue StandardError
232
- nil
233
- end
235
+ DateTime.parse(value)
236
+ rescue StandardError
237
+ nil
238
+ end
234
239
  if dt
235
240
  seconds = string_utc_offset(value) || ApplicationTimeZone.utc_offset
236
241
  offset = seconds_to_offset(seconds)
@@ -255,9 +260,7 @@ module Dynamoid
255
260
 
256
261
  class DateTypeCaster < Base
257
262
  def process(value)
258
- if !value.respond_to?(:to_date)
259
- nil
260
- else
263
+ if value.respond_to?(:to_date)
261
264
  begin
262
265
  value.to_date
263
266
  rescue StandardError
@@ -277,10 +280,8 @@ module Dynamoid
277
280
  def process(value)
278
281
  if value == ''
279
282
  nil
280
- elsif [false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'].include? value
281
- false
282
283
  else
283
- true
284
+ ![false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'].include? value
284
285
  end
285
286
  end
286
287
  end
@@ -115,7 +115,7 @@ module Dynamoid
115
115
  def process_typed_collection(set)
116
116
  if allowed_type?
117
117
  undumper = Undumping.find_undumper(element_options)
118
- set.map { |el| undumper.process(el) }.to_set
118
+ set.to_set { |el| undumper.process(el) }
119
119
  else
120
120
  raise ArgumentError, "Set element type #{element_type} isn't supported"
121
121
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
- VERSION = '3.9.0'
4
+ VERSION = '3.10.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.9.0
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Symonds
@@ -21,7 +21,7 @@ authors:
21
21
  autorequire:
22
22
  bindir: bin
23
23
  cert_chain: []
24
- date: 2023-04-13 00:00:00.000000000 Z
24
+ date: 2024-02-10 00:00:00.000000000 Z
25
25
  dependencies:
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
@@ -122,33 +122,33 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '13.0'
124
124
  - !ruby/object:Gem::Dependency
125
- name: rspec
125
+ name: rexml
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
- - - "~>"
128
+ - - ">="
129
129
  - !ruby/object:Gem::Version
130
- version: '3.12'
130
+ version: '0'
131
131
  type: :development
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
- - - "~>"
135
+ - - ">="
136
136
  - !ruby/object:Gem::Version
137
- version: '3.12'
137
+ version: '0'
138
138
  - !ruby/object:Gem::Dependency
139
- name: rubocop-lts
139
+ name: rspec
140
140
  requirement: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '10.0'
144
+ version: '3.12'
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
- version: '10.0'
151
+ version: '3.12'
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: yard
154
154
  requirement: !ruby/object:Gem::Requirement
@@ -185,10 +185,12 @@ files:
185
185
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb
186
186
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb
187
187
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb
188
+ - lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb
188
189
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb
189
190
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb
190
191
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb
191
192
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb
193
+ - lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb
192
194
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb
193
195
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb
194
196
  - lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb
@@ -209,10 +211,9 @@ files:
209
211
  - lib/dynamoid/config/options.rb
210
212
  - lib/dynamoid/criteria.rb
211
213
  - lib/dynamoid/criteria/chain.rb
212
- - lib/dynamoid/criteria/ignored_conditions_detector.rb
213
214
  - lib/dynamoid/criteria/key_fields_detector.rb
214
215
  - lib/dynamoid/criteria/nonexistent_fields_detector.rb
215
- - lib/dynamoid/criteria/overwritten_conditions_detector.rb
216
+ - lib/dynamoid/criteria/where_conditions.rb
216
217
  - lib/dynamoid/dirty.rb
217
218
  - lib/dynamoid/document.rb
218
219
  - lib/dynamoid/dumping.rb
@@ -247,10 +248,10 @@ licenses:
247
248
  - MIT
248
249
  metadata:
249
250
  homepage_uri: http://github.com/Dynamoid/dynamoid
250
- source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.9.0
251
- changelog_uri: https://github.com/Dynamoid/dynamoid/blob/v3.9.0/CHANGELOG.md
251
+ source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.10.0
252
+ changelog_uri: https://github.com/Dynamoid/dynamoid/blob/v3.10.0/CHANGELOG.md
252
253
  bug_tracker_uri: https://github.com/Dynamoid/dynamoid/issues
253
- documentation_uri: https://www.rubydoc.info/gems/dynamoid/3.9.0
254
+ documentation_uri: https://www.rubydoc.info/gems/dynamoid/3.10.0
254
255
  funding_uri: https://opencollective.com/dynamoid
255
256
  wiki_uri: https://github.com/Dynamoid/dynamoid/wiki
256
257
  rubygems_mfa_required: 'true'
@@ -269,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
270
  - !ruby/object:Gem::Version
270
271
  version: '0'
271
272
  requirements: []
272
- rubygems_version: 3.4.6
273
+ rubygems_version: 3.5.3
273
274
  signing_key:
274
275
  specification_version: 4
275
276
  summary: Dynamoid is an ORM for Amazon's DynamoDB
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dynamoid
4
- module Criteria
5
- # @private
6
- class IgnoredConditionsDetector
7
- def initialize(conditions)
8
- @conditions = conditions
9
- @ignored_keys = ignored_keys
10
- end
11
-
12
- def found?
13
- @ignored_keys.present?
14
- end
15
-
16
- def warning_message
17
- return unless found?
18
-
19
- 'Where conditions may contain only one condition for an attribute. ' \
20
- "Following conditions are ignored: #{ignored_conditions}"
21
- end
22
-
23
- private
24
-
25
- def ignored_keys
26
- @conditions.keys
27
- .group_by(&method(:key_to_field))
28
- .select { |_, ary| ary.size > 1 }
29
- .flat_map { |_, ary| ary[0..-2] }
30
- end
31
-
32
- def key_to_field(key)
33
- key.to_s.split('.')[0]
34
- end
35
-
36
- def ignored_conditions
37
- @conditions.slice(*@ignored_keys)
38
- end
39
- end
40
- end
41
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dynamoid
4
- module Criteria
5
- # @private
6
- class OverwrittenConditionsDetector
7
- def initialize(conditions, conditions_new)
8
- @conditions = conditions
9
- @new_conditions = conditions_new
10
- @overwritten_keys = overwritten_keys
11
- end
12
-
13
- def found?
14
- @overwritten_keys.present?
15
- end
16
-
17
- def warning_message
18
- return unless found?
19
-
20
- 'Where conditions may contain only one condition for an attribute. ' \
21
- "Following conditions are ignored: #{ignored_conditions}"
22
- end
23
-
24
- private
25
-
26
- def overwritten_keys
27
- new_fields = @new_conditions.keys.map(&method(:key_to_field))
28
- @conditions.keys.select { |key| key_to_field(key).in?(new_fields) }
29
- end
30
-
31
- def key_to_field(key)
32
- key.to_s.split('.')[0]
33
- end
34
-
35
- def ignored_conditions
36
- @conditions.slice(*@overwritten_keys.map(&:to_sym))
37
- end
38
- end
39
- end
40
- end