dynamoid 3.9.0 → 3.10.0

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