aws-record 2.7.0 → 2.10.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +330 -0
- data/LICENSE +202 -0
- data/VERSION +1 -0
- data/lib/aws-record/record/attribute.rb +5 -15
- data/lib/aws-record/record/attributes.rb +161 -32
- data/lib/aws-record/record/batch.rb +99 -35
- data/lib/aws-record/record/batch_read.rb +186 -0
- data/lib/aws-record/record/batch_write.rb +9 -14
- data/lib/aws-record/record/buildable_search.rb +4 -2
- data/lib/aws-record/record/client_configuration.rb +13 -14
- data/lib/aws-record/record/dirty_tracking.rb +1 -12
- data/lib/aws-record/record/errors.rb +1 -12
- data/lib/aws-record/record/item_collection.rb +2 -13
- data/lib/aws-record/record/item_data.rb +1 -12
- data/lib/aws-record/record/item_operations.rb +47 -12
- data/lib/aws-record/record/key_attributes.rb +1 -12
- data/lib/aws-record/record/marshalers/boolean_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/date_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/date_time_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/epoch_time_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/float_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/integer_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/list_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/map_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/numeric_set_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/string_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/string_set_marshaler.rb +1 -12
- data/lib/aws-record/record/marshalers/time_marshaler.rb +1 -12
- data/lib/aws-record/record/model_attributes.rb +8 -12
- data/lib/aws-record/record/query.rb +1 -12
- data/lib/aws-record/record/secondary_indexes.rb +24 -12
- data/lib/aws-record/record/table_config.rb +1 -12
- data/lib/aws-record/record/table_migration.rb +1 -12
- data/lib/aws-record/record/transactions.rb +39 -7
- data/lib/aws-record/record/version.rb +2 -13
- data/lib/aws-record/record.rb +100 -20
- data/lib/aws-record.rb +2 -12
- metadata +8 -4
@@ -1,15 +1,4 @@
|
|
1
|
-
#
|
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.
|
1
|
+
# frozen_string_literal: true
|
13
2
|
|
14
3
|
module Aws
|
15
4
|
module Record
|
@@ -20,8 +9,20 @@ module Aws
|
|
20
9
|
model_attributes = ModelAttributes.new(self)
|
21
10
|
sub_class.instance_variable_set("@attributes", model_attributes)
|
22
11
|
sub_class.instance_variable_set("@keys", KeyAttributes.new(model_attributes))
|
12
|
+
if Aws::Record.extends_record?(sub_class)
|
13
|
+
inherit_attributes(sub_class)
|
14
|
+
end
|
23
15
|
end
|
24
16
|
|
17
|
+
# Base initialization method for a new item. Optionally, allows you to
|
18
|
+
# provide initial attribute values for the model. You do not need to
|
19
|
+
# provide all, or even any, attributes at item creation time.
|
20
|
+
#
|
21
|
+
# === Inheritance Support
|
22
|
+
# Child models will inherit the attributes and keys defined in the parent
|
23
|
+
# model. Child models can override attribute keys if defined in their own model.
|
24
|
+
#
|
25
|
+
# See examples below to see the feature in action.
|
25
26
|
# @example Usage Example
|
26
27
|
# class MyModel
|
27
28
|
# include Aws::Record
|
@@ -31,11 +32,34 @@ module Aws
|
|
31
32
|
# end
|
32
33
|
#
|
33
34
|
# item = MyModel.new(id: 1, name: "Quick Create")
|
35
|
+
# @example Child model inheriting from Parent model
|
36
|
+
# class Animal
|
37
|
+
# include Aws::Record
|
38
|
+
# string_attr :name, hash_key: true
|
39
|
+
# integer_attr :age, default_value: 1
|
40
|
+
# end
|
34
41
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
42
|
+
# class Cat < Animal
|
43
|
+
# include Aws::Record
|
44
|
+
# integer_attr :num_of_wiskers
|
45
|
+
# end
|
38
46
|
#
|
47
|
+
# cat = Cat.find(name: 'Foo')
|
48
|
+
# cat.age # => 1
|
49
|
+
# cat.num_of_wiskers = 200
|
50
|
+
# @example Child model overrides the hash key
|
51
|
+
# class Animal
|
52
|
+
# include Aws::Record
|
53
|
+
# string_attr :name, hash_key: true
|
54
|
+
# integer_attr :age, range_key: true
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# class Dog < Animal
|
58
|
+
# include Aws::Record
|
59
|
+
# integer_attr :id, hash_key: true
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Dog.keys # => {:hash=>:id, :range=>:age}
|
39
63
|
# @param [Hash] attr_values Attribute symbol/value pairs for any initial
|
40
64
|
# attribute values you wish to set.
|
41
65
|
# @return [Aws::Record] An item instance for your model.
|
@@ -56,6 +80,27 @@ module Aws
|
|
56
80
|
@data.hash_copy
|
57
81
|
end
|
58
82
|
|
83
|
+
private
|
84
|
+
def self.inherit_attributes(klass)
|
85
|
+
superclass_attributes = klass.superclass.instance_variable_get("@attributes")
|
86
|
+
|
87
|
+
superclass_attributes.attributes.each do |name, attribute|
|
88
|
+
subclass_attributes = klass.instance_variable_get("@attributes")
|
89
|
+
subclass_attributes.register_superclass_attribute(name, attribute)
|
90
|
+
end
|
91
|
+
|
92
|
+
superclass_keys = klass.superclass.instance_variable_get("@keys")
|
93
|
+
subclass_keys = klass.instance_variable_get("@keys")
|
94
|
+
|
95
|
+
if superclass_keys.hash_key
|
96
|
+
subclass_keys.hash_key = superclass_keys.hash_key
|
97
|
+
end
|
98
|
+
|
99
|
+
if superclass_keys.range_key
|
100
|
+
subclass_keys.range_key = superclass_keys.range_key
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
59
104
|
module ClassMethods
|
60
105
|
|
61
106
|
# Define an attribute for your model, providing your own attribute type.
|
@@ -82,7 +127,8 @@ module Aws
|
|
82
127
|
# nil values will be ignored and not persisted. By default, is false.
|
83
128
|
# @option opts [Object] :default_value Optional attribute used to
|
84
129
|
# define a "default value" to be used if the attribute's value on an
|
85
|
-
# item is nil or not set at persistence time.
|
130
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
131
|
+
# can be used as a default value.
|
86
132
|
# @option opts [Boolean] :hash_key Set to true if this attribute is
|
87
133
|
# the hash key for the table.
|
88
134
|
# @option opts [Boolean] :range_key Set to true if this attribute is
|
@@ -108,7 +154,8 @@ module Aws
|
|
108
154
|
# nil values will be ignored and not persisted. By default, is false.
|
109
155
|
# @option opts [Object] :default_value Optional attribute used to
|
110
156
|
# define a "default value" to be used if the attribute's value on an
|
111
|
-
# item is nil or not set at persistence time.
|
157
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
158
|
+
# can be used as a default value.
|
112
159
|
def string_attr(name, opts = {})
|
113
160
|
opts[:dynamodb_type] = "S"
|
114
161
|
attr(name, Marshalers::StringMarshaler.new(opts), opts)
|
@@ -129,7 +176,8 @@ module Aws
|
|
129
176
|
# nil values will be ignored and not persisted. By default, is false.
|
130
177
|
# @option opts [Object] :default_value Optional attribute used to
|
131
178
|
# define a "default value" to be used if the attribute's value on an
|
132
|
-
# item is nil or not set at persistence time.
|
179
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
180
|
+
# can be used as a default value.
|
133
181
|
def boolean_attr(name, opts = {})
|
134
182
|
opts[:dynamodb_type] = "BOOL"
|
135
183
|
attr(name, Marshalers::BooleanMarshaler.new(opts), opts)
|
@@ -150,7 +198,8 @@ module Aws
|
|
150
198
|
# nil values will be ignored and not persisted. By default, is false.
|
151
199
|
# @option opts [Object] :default_value Optional attribute used to
|
152
200
|
# define a "default value" to be used if the attribute's value on an
|
153
|
-
# item is nil or not set at persistence time.
|
201
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
202
|
+
# can be used as a default value.
|
154
203
|
def integer_attr(name, opts = {})
|
155
204
|
opts[:dynamodb_type] = "N"
|
156
205
|
attr(name, Marshalers::IntegerMarshaler.new(opts), opts)
|
@@ -171,7 +220,8 @@ module Aws
|
|
171
220
|
# nil values will be ignored and not persisted. By default, is false.
|
172
221
|
# @option opts [Object] :default_value Optional attribute used to
|
173
222
|
# define a "default value" to be used if the attribute's value on an
|
174
|
-
# item is nil or not set at persistence time.
|
223
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
224
|
+
# can be used as a default value.
|
175
225
|
def float_attr(name, opts = {})
|
176
226
|
opts[:dynamodb_type] = "N"
|
177
227
|
attr(name, Marshalers::FloatMarshaler.new(opts), opts)
|
@@ -192,7 +242,8 @@ module Aws
|
|
192
242
|
# nil values will be ignored and not persisted. By default, is false.
|
193
243
|
# @option options [Object] :default_value Optional attribute used to
|
194
244
|
# define a "default value" to be used if the attribute's value on an
|
195
|
-
# item is nil or not set at persistence time.
|
245
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
246
|
+
# can be used as a default value.
|
196
247
|
def date_attr(name, opts = {})
|
197
248
|
opts[:dynamodb_type] = "S"
|
198
249
|
attr(name, Marshalers::DateMarshaler.new(opts), opts)
|
@@ -213,7 +264,8 @@ module Aws
|
|
213
264
|
# nil values will be ignored and not persisted. By default, is false.
|
214
265
|
# @option opts [Object] :default_value Optional attribute used to
|
215
266
|
# define a "default value" to be used if the attribute's value on an
|
216
|
-
# item is nil or not set at persistence time.
|
267
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
268
|
+
# can be used as a default value.
|
217
269
|
def datetime_attr(name, opts = {})
|
218
270
|
opts[:dynamodb_type] = "S"
|
219
271
|
attr(name, Marshalers::DateTimeMarshaler.new(opts), opts)
|
@@ -234,14 +286,15 @@ module Aws
|
|
234
286
|
# nil values will be ignored and not persisted. By default, is false.
|
235
287
|
# @option opts [Object] :default_value Optional attribute used to
|
236
288
|
# define a "default value" to be used if the attribute's value on an
|
237
|
-
# item is nil or not set at persistence time.
|
289
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
290
|
+
# can be used as a default value.
|
238
291
|
def time_attr(name, opts = {})
|
239
292
|
opts[:dynamodb_type] = "S"
|
240
293
|
attr(name, Marshalers::TimeMarshaler.new(opts), opts)
|
241
294
|
end
|
242
295
|
|
243
296
|
# Define a time-type attribute for your model which persists as
|
244
|
-
#
|
297
|
+
# epoch-seconds.
|
245
298
|
#
|
246
299
|
# @param [Symbol] name Name of this attribute. It should be a name
|
247
300
|
# that is safe to use as a method.
|
@@ -256,7 +309,8 @@ module Aws
|
|
256
309
|
# nil values will be ignored and not persisted. By default, is false.
|
257
310
|
# @option opts [Object] :default_value Optional attribute used to
|
258
311
|
# define a "default value" to be used if the attribute's value on an
|
259
|
-
# item is nil or not set at persistence time.
|
312
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
313
|
+
# can be used as a default value.
|
260
314
|
def epoch_time_attr(name, opts = {})
|
261
315
|
opts[:dynamodb_type] = "N"
|
262
316
|
attr(name, Marshalers::EpochTimeMarshaler.new(opts), opts)
|
@@ -265,7 +319,7 @@ module Aws
|
|
265
319
|
# Define a list-type attribute for your model.
|
266
320
|
#
|
267
321
|
# Lists do not have to be homogeneous, but they do have to be types that
|
268
|
-
# the AWS SDK for Ruby
|
322
|
+
# the AWS SDK for Ruby V3's DynamoDB client knows how to marshal and
|
269
323
|
# unmarshal. Those types are:
|
270
324
|
#
|
271
325
|
# * Hash
|
@@ -291,7 +345,8 @@ module Aws
|
|
291
345
|
# the range key for the table.
|
292
346
|
# @option opts [Object] :default_value Optional attribute used to
|
293
347
|
# define a "default value" to be used if the attribute's value on an
|
294
|
-
# item is nil or not set at persistence time.
|
348
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
349
|
+
# can be used as a default value.
|
295
350
|
def list_attr(name, opts = {})
|
296
351
|
opts[:dynamodb_type] = "L"
|
297
352
|
attr(name, Marshalers::ListMarshaler.new(opts), opts)
|
@@ -300,7 +355,7 @@ module Aws
|
|
300
355
|
# Define a map-type attribute for your model.
|
301
356
|
#
|
302
357
|
# Maps do not have to be homogeneous, but they do have to use types that
|
303
|
-
# the AWS SDK for Ruby
|
358
|
+
# the AWS SDK for Ruby V3's DynamoDB client knows how to marshal and
|
304
359
|
# unmarshal. Those types are:
|
305
360
|
#
|
306
361
|
# * Hash
|
@@ -326,7 +381,8 @@ module Aws
|
|
326
381
|
# the range key for the table.
|
327
382
|
# @option opts [Object] :default_value Optional attribute used to
|
328
383
|
# define a "default value" to be used if the attribute's value on an
|
329
|
-
# item is nil or not set at persistence time.
|
384
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
385
|
+
# can be used as a default value.
|
330
386
|
def map_attr(name, opts = {})
|
331
387
|
opts[:dynamodb_type] = "M"
|
332
388
|
attr(name, Marshalers::MapMarshaler.new(opts), opts)
|
@@ -351,7 +407,8 @@ module Aws
|
|
351
407
|
# the range key for the table.
|
352
408
|
# @option opts [Object] :default_value Optional attribute used to
|
353
409
|
# define a "default value" to be used if the attribute's value on an
|
354
|
-
# item is nil or not set at persistence time.
|
410
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
411
|
+
# can be used as a default value.
|
355
412
|
def string_set_attr(name, opts = {})
|
356
413
|
opts[:dynamodb_type] = "SS"
|
357
414
|
attr(name, Marshalers::StringSetMarshaler.new(opts), opts)
|
@@ -376,18 +433,90 @@ module Aws
|
|
376
433
|
# the range key for the table.
|
377
434
|
# @option opts [Object] :default_value Optional attribute used to
|
378
435
|
# define a "default value" to be used if the attribute's value on an
|
379
|
-
# item is nil or not set at persistence time.
|
436
|
+
# item is nil or not set at persistence time. Additionally, lambda
|
437
|
+
# can be used as a default value.
|
380
438
|
def numeric_set_attr(name, opts = {})
|
381
439
|
opts[:dynamodb_type] = "NS"
|
382
440
|
attr(name, Marshalers::NumericSetMarshaler.new(opts), opts)
|
383
441
|
end
|
384
442
|
|
443
|
+
# Define an atomic counter attribute for your model.
|
444
|
+
#
|
445
|
+
# Atomic counter are an integer-type attribute that is incremented,
|
446
|
+
# unconditionally, without interfering with other write requests.
|
447
|
+
# The numeric value increments each time you call +increment_<attr>!+.
|
448
|
+
# If a specific numeric value are passed in the call, the attribute will
|
449
|
+
# increment by that value.
|
450
|
+
#
|
451
|
+
# To use +increment_<attr>!+ method, the following condition must be true:
|
452
|
+
# * None of the attributes have dirty changes.
|
453
|
+
# * If there is a value passed in, it must be an integer.
|
454
|
+
# For more information, see
|
455
|
+
# {https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters Atomic counter}
|
456
|
+
# in the Amazon DynamoDB Developer Guide.
|
457
|
+
#
|
458
|
+
# @param [Symbol] name Name of this attribute. It should be a name that
|
459
|
+
# is safe to use as a method.
|
460
|
+
# @param [Hash] opts
|
461
|
+
# @option opts [Object] :default_value Optional attribute used to
|
462
|
+
# define a "default value" to be used if the attribute's value on an
|
463
|
+
# item is nil or not set at persistence time. The "default value" of
|
464
|
+
# the attribute starts at 0.
|
465
|
+
#
|
466
|
+
# @example Usage Example
|
467
|
+
# class MyRecord
|
468
|
+
# include Aws::Record
|
469
|
+
# integer_attr :id, hash_key: true
|
470
|
+
# atomic_counter :counter
|
471
|
+
# end
|
472
|
+
#
|
473
|
+
# record = MyRecord.find(id: 1)
|
474
|
+
# record.counter #=> 0
|
475
|
+
# record.increment_counter! #=> 1
|
476
|
+
# record.increment_counter!(2) #=> 3
|
477
|
+
# @see #attr #attr method for additional hash options.
|
478
|
+
def atomic_counter(name, opts = {})
|
479
|
+
opts[:dynamodb_type] = "N"
|
480
|
+
opts[:default_value] ||= 0
|
481
|
+
attr(name, Marshalers::IntegerMarshaler.new(opts), opts)
|
482
|
+
|
483
|
+
define_method("increment_#{name}!") do |increment=1|
|
484
|
+
|
485
|
+
if dirty?
|
486
|
+
msg = "Attributes need to be saved before atomic counter can be incremented"
|
487
|
+
raise Errors::RecordError, msg
|
488
|
+
end
|
489
|
+
|
490
|
+
unless increment.is_a?(Integer)
|
491
|
+
msg = "expected an Integer value, got #{increment.class}"
|
492
|
+
raise ArgumentError, msg
|
493
|
+
end
|
494
|
+
|
495
|
+
resp = dynamodb_client.update_item({
|
496
|
+
table_name: self.class.table_name,
|
497
|
+
key: key_values,
|
498
|
+
expression_attribute_values: {
|
499
|
+
":i" => increment
|
500
|
+
},
|
501
|
+
expression_attribute_names: {
|
502
|
+
"#n" => name
|
503
|
+
},
|
504
|
+
update_expression: "SET #n = #n + :i",
|
505
|
+
return_values: "UPDATED_NEW"
|
506
|
+
})
|
507
|
+
assign_attributes(resp[:attributes])
|
508
|
+
@data.clean!
|
509
|
+
@data.get_attribute(name)
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
385
514
|
# @return [Symbol,nil] The symbolic name of the table's hash key.
|
386
515
|
def hash_key
|
387
516
|
@keys.hash_key
|
388
517
|
end
|
389
518
|
|
390
|
-
# @return [Symbol,nil] The
|
519
|
+
# @return [Symbol,nil] The symbolic name of the table's range key, or nil if there is no range key.
|
391
520
|
def range_key
|
392
521
|
@keys.range_key
|
393
522
|
end
|
@@ -1,15 +1,4 @@
|
|
1
|
-
#
|
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.
|
1
|
+
# frozen_string_literal: true
|
13
2
|
|
14
3
|
module Aws
|
15
4
|
module Record
|
@@ -17,6 +6,28 @@ module Aws
|
|
17
6
|
extend ClientConfiguration
|
18
7
|
|
19
8
|
class << self
|
9
|
+
# Provides a thin wrapper to the
|
10
|
+
# {https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#batch_write_item-instance_method Aws::DynamoDB::Client#batch_write_item}
|
11
|
+
# method. Up to 25 +PutItem+ or +DeleteItem+ operations are supported.
|
12
|
+
# A single request may write up to 16 MB of data, with each item having a
|
13
|
+
# write limit of 400 KB.
|
14
|
+
#
|
15
|
+
# *Note*: this operation does not support dirty attribute handling,
|
16
|
+
# nor does it enforce safe write operations (i.e. update vs new record
|
17
|
+
# checks).
|
18
|
+
#
|
19
|
+
# This call may partially execute write operations. Failed operations
|
20
|
+
# are returned as {BatchWrite.unprocessed_items unprocessed_items} (i.e. the
|
21
|
+
# table fails to meet requested write capacity). Any unprocessed
|
22
|
+
# items may be retried by calling {BatchWrite.execute! .execute!}
|
23
|
+
# again. You can determine if the request needs to be retried by calling
|
24
|
+
# the {BatchWrite.complete? .complete?} method - which returns +true+
|
25
|
+
# when all operations have been completed.
|
26
|
+
#
|
27
|
+
# Please see
|
28
|
+
# {https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html#Programming.Errors.BatchOperations Batch Operations and Error Handling}
|
29
|
+
# in the DynamoDB Developer Guide for more details.
|
30
|
+
#
|
20
31
|
# @example Usage Example
|
21
32
|
# class Breakfast
|
22
33
|
# include Aws::Record
|
@@ -38,29 +49,7 @@ module Aws
|
|
38
49
|
# end
|
39
50
|
#
|
40
51
|
# # unprocessed items can be retried by calling Aws::Record::BatchWrite#execute!
|
41
|
-
# operation.execute!
|
42
|
-
#
|
43
|
-
# Provides a thin wrapper to the
|
44
|
-
# {https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#batch_write_item-instance_method Aws::DynamoDB::Client#batch_write_item}
|
45
|
-
# method. Up to 25 +PutItem+ or +DeleteItem+ operations are supported.
|
46
|
-
# A single rquest may write up to 16 MB of data, with each item having a
|
47
|
-
# write limit of 400 KB.
|
48
|
-
#
|
49
|
-
# *Note*: this operation does not support dirty attribute handling,
|
50
|
-
# nor does it enforce safe write operations (i.e. update vs new record
|
51
|
-
# checks).
|
52
|
-
#
|
53
|
-
# This call may partially execute write operations. Failed operations
|
54
|
-
# are returned as +Aws::Record::BatchWrite#unprocessed_items+ (i.e. the
|
55
|
-
# table fails to meet requested write capacity). Any unprocessed
|
56
|
-
# items may be retried by calling +Aws::Record::BatchWrite#execute!+
|
57
|
-
# again. You can determine if the request needs to be retried by calling
|
58
|
-
# the +Aws::Record::BatchWrite#complete?+ method - which returns +true+
|
59
|
-
# when all operations have been completed.
|
60
|
-
#
|
61
|
-
# Please see
|
62
|
-
# {https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html#Programming.Errors.BatchOperations Batch Operations and Error Handling}
|
63
|
-
# in the DynamoDB Developer Guide for more details.
|
52
|
+
# operation.execute! until operation.complete?
|
64
53
|
#
|
65
54
|
# @param [Hash] opts the options you wish to use to create the client.
|
66
55
|
# Note that if you include the option +:client+, all other options
|
@@ -76,6 +65,81 @@ module Aws
|
|
76
65
|
block.call(batch)
|
77
66
|
batch.execute!
|
78
67
|
end
|
68
|
+
|
69
|
+
# Provides support for the
|
70
|
+
# {https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#batch_get_item-instance_method
|
71
|
+
# Aws::DynamoDB::Client#batch_get_item} for aws-record models.
|
72
|
+
#
|
73
|
+
# +Aws::Record::Batch+ is Enumerable and using Enumerable methods will handle
|
74
|
+
# paging through all requested keys automatically. Alternatively, a lower level
|
75
|
+
# interface is available. You can determine if there are any unprocessed keys by calling
|
76
|
+
# {BatchRead.complete? .complete?} and any unprocessed keys can be processed by
|
77
|
+
# calling {BatchRead.execute! .execute!}. You can access all processed items
|
78
|
+
# through {BatchRead.items .items}.
|
79
|
+
#
|
80
|
+
# The +batch_get_item+ supports up to 100 operations in a single call and a single
|
81
|
+
# operation can retrieve up to 16 MB of data.
|
82
|
+
#
|
83
|
+
# +Aws::Record::BatchRead+ can take more than 100 item keys. The first 100 requests
|
84
|
+
# will be processed and the remaining requests will be stored.
|
85
|
+
# When using Enumerable methods, any pending item keys will be automatically
|
86
|
+
# processed and the new items will be added to +items+.
|
87
|
+
# Alternately, use {BatchRead.execute! .execute!} to process any pending item keys.
|
88
|
+
#
|
89
|
+
# All processed operations can be accessed by {BatchRead.items items} - which is an
|
90
|
+
# array of modeled items from the response. The items will be unordered since
|
91
|
+
# DynamoDB does not return items in any particular order.
|
92
|
+
#
|
93
|
+
# If a requested item does not exist in the database, it is not returned in the response.
|
94
|
+
#
|
95
|
+
# If there is a returned item from the call and there's no reference model class
|
96
|
+
# to be found, the item will not show up under +items+.
|
97
|
+
#
|
98
|
+
# @example Usage Example
|
99
|
+
# class Lunch
|
100
|
+
# include Aws::Record
|
101
|
+
# integer_attr :id, hash_key: true
|
102
|
+
# string_attr :name, range_key: true
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# class Dessert
|
106
|
+
# include Aws::Record
|
107
|
+
# integer_attr :id, hash_key: true
|
108
|
+
# string_attr :name, range_key: true
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# # batch operations
|
112
|
+
# operation = Aws::Record::Batch.read do |db|
|
113
|
+
# db.find(Lunch, id: 1, name: 'Papaya Salad')
|
114
|
+
# db.find(Lunch, id: 2, name: 'BLT Sandwich')
|
115
|
+
# db.find(Dessert, id: 1, name: 'Apple Pie')
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# # BatchRead is enumerable and handles pagination
|
119
|
+
# operation.each { |item| item.id }
|
120
|
+
#
|
121
|
+
# # Alternatively, BatchRead provides a lower level
|
122
|
+
# # interface through: execute!, complete? and items.
|
123
|
+
# # Unprocessed items can be processed by calling:
|
124
|
+
# operation.execute! until operation.complete?
|
125
|
+
#
|
126
|
+
# @param [Hash] opts the options you wish to use to create the client.
|
127
|
+
# Note that if you include the option +:client+, all other options
|
128
|
+
# will be ignored. See the documentation for other options in the
|
129
|
+
# {https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#initialize-instance_method
|
130
|
+
# AWS SDK for Ruby}.
|
131
|
+
# @option opts [Aws::DynamoDB::Client] :client allows you to pass in your
|
132
|
+
# own pre-configured client.
|
133
|
+
# @return [Aws::Record::BatchRead] An instance that contains modeled items
|
134
|
+
# from the +BatchGetItem+ result and stores unprocessed keys to be
|
135
|
+
# manually processed later.
|
136
|
+
def read(opts = {}, &block)
|
137
|
+
batch = BatchRead.new(client: _build_client(opts))
|
138
|
+
block.call(batch)
|
139
|
+
batch.execute!
|
140
|
+
batch
|
141
|
+
end
|
142
|
+
|
79
143
|
end
|
80
144
|
end
|
81
145
|
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Record
|
5
|
+
class BatchRead
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
BATCH_GET_ITEM_LIMIT = 100
|
10
|
+
|
11
|
+
# @param [Aws::DynamoDB::Client] client the DynamoDB SDK client.
|
12
|
+
def initialize(opts = {})
|
13
|
+
@client = opts[:client]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Append the item keys to a batch read request.
|
17
|
+
#
|
18
|
+
# See {Batch.read} for example usage.
|
19
|
+
# @param [Aws::Record] klass a model class that includes {Aws::Record}
|
20
|
+
# @param [Hash] key attribute-value pairs for the key you wish to search for.
|
21
|
+
# @raise [Aws::Record::Errors::KeyMissing] if your option parameters
|
22
|
+
# do not include all item keys defined in the model.
|
23
|
+
# @raise [ArgumentError] if the provided item keys is a duplicate request
|
24
|
+
# in the same instance.
|
25
|
+
def find(klass, key = {})
|
26
|
+
unprocessed_key = format_unprocessed_key(klass, key)
|
27
|
+
store_unprocessed_key(klass, unprocessed_key)
|
28
|
+
store_item_class(klass, unprocessed_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Perform a +batch_get_item+ request.
|
32
|
+
#
|
33
|
+
# This method processes the first 100 item keys and
|
34
|
+
# returns an array of new modeled items.
|
35
|
+
#
|
36
|
+
# See {Batch.read} for example usage.
|
37
|
+
# @return [Array] an array of unordered new items
|
38
|
+
def execute!
|
39
|
+
operation_keys = unprocessed_keys[0..BATCH_GET_ITEM_LIMIT - 1]
|
40
|
+
@unprocessed_keys = unprocessed_keys[BATCH_GET_ITEM_LIMIT..-1] || []
|
41
|
+
|
42
|
+
operations = build_operations(operation_keys)
|
43
|
+
result = @client.batch_get_item(request_items: operations)
|
44
|
+
new_items = build_items(result.responses)
|
45
|
+
items.concat(new_items)
|
46
|
+
|
47
|
+
unless result.unprocessed_keys.nil?
|
48
|
+
update_unprocessed_keys(result.unprocessed_keys)
|
49
|
+
end
|
50
|
+
|
51
|
+
new_items
|
52
|
+
end
|
53
|
+
|
54
|
+
# Provides an enumeration of the results from the +batch_get_item+ request
|
55
|
+
# and handles pagination.
|
56
|
+
#
|
57
|
+
# Any pending item keys will be automatically processed and be
|
58
|
+
# added to the {#items}.
|
59
|
+
#
|
60
|
+
# See {Batch.read} for example usage.
|
61
|
+
# @yieldparam [Aws::Record] item a modeled item
|
62
|
+
# @return [Enumerable<BatchRead>] an enumeration over the results of
|
63
|
+
# +batch_get_item+ request.
|
64
|
+
def each
|
65
|
+
return enum_for(:each) unless block_given?
|
66
|
+
|
67
|
+
@items.each { |item| yield item }
|
68
|
+
until complete?
|
69
|
+
new_items = execute!
|
70
|
+
new_items.each { |new_item| yield new_item }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Indicates if all item keys have been processed.
|
75
|
+
#
|
76
|
+
# See {Batch.read} for example usage.
|
77
|
+
# @return [Boolean] +true+ if all item keys has been processed, +false+ otherwise.
|
78
|
+
def complete?
|
79
|
+
unprocessed_keys.none?
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns an array of modeled items. The items are marshaled into classes used in {#find} method.
|
83
|
+
# These items will be unordered since DynamoDB does not return items in any particular order.
|
84
|
+
#
|
85
|
+
# See {Batch.read} for example usage.
|
86
|
+
# @return [Array] an array of modeled items from the +batch_get_item+ call.
|
87
|
+
def items
|
88
|
+
@items ||= []
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def unprocessed_keys
|
94
|
+
@unprocessed_keys ||= []
|
95
|
+
end
|
96
|
+
|
97
|
+
def item_classes
|
98
|
+
@item_classes ||= Hash.new { |h, k| h[k] = [] }
|
99
|
+
end
|
100
|
+
|
101
|
+
def format_unprocessed_key(klass, key)
|
102
|
+
item_key = {}
|
103
|
+
attributes = klass.attributes
|
104
|
+
klass.keys.each_value do |attr_sym|
|
105
|
+
unless key[attr_sym]
|
106
|
+
raise Errors::KeyMissing, "Missing required key #{attr_sym} in #{key}"
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_name = attributes.storage_name_for(attr_sym)
|
110
|
+
item_key[attr_name] = attributes.attribute_for(attr_sym)
|
111
|
+
.serialize(key[attr_sym])
|
112
|
+
end
|
113
|
+
item_key
|
114
|
+
end
|
115
|
+
|
116
|
+
def store_unprocessed_key(klass, unprocessed_key)
|
117
|
+
unprocessed_keys << { keys: unprocessed_key, table_name: klass.table_name }
|
118
|
+
end
|
119
|
+
|
120
|
+
def store_item_class(klass, key)
|
121
|
+
if item_classes.include?(klass.table_name)
|
122
|
+
item_classes[klass.table_name].each do |item|
|
123
|
+
if item[:keys] == key && item[:class] != klass
|
124
|
+
raise ArgumentError, 'Provided item keys is a duplicate request'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
item_classes[klass.table_name] << { keys: key, class: klass }
|
129
|
+
end
|
130
|
+
|
131
|
+
def build_operations(keys)
|
132
|
+
operations = Hash.new { |h, k| h[k] = { keys: [] } }
|
133
|
+
keys.each do |key|
|
134
|
+
operations[key[:table_name]][:keys] << key[:keys]
|
135
|
+
end
|
136
|
+
operations
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_items(item_responses)
|
140
|
+
new_items = []
|
141
|
+
item_responses.each do |table, unprocessed_items|
|
142
|
+
unprocessed_items.each do |item|
|
143
|
+
item_class = find_item_class(table, item)
|
144
|
+
if item_class.nil? && @client.config.logger
|
145
|
+
@client.config.logger.warn(
|
146
|
+
'Unexpected response from service.'\
|
147
|
+
"Received: #{item}. Skipping above item and continuing"
|
148
|
+
)
|
149
|
+
else
|
150
|
+
new_items << build_item(item, item_class)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
new_items
|
155
|
+
end
|
156
|
+
|
157
|
+
def update_unprocessed_keys(keys)
|
158
|
+
keys.each do |table_name, table_values|
|
159
|
+
table_values.keys.each do |key|
|
160
|
+
unprocessed_keys << { keys: key, table_name: table_name }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_item_class(table, item)
|
166
|
+
selected_item = item_classes[table].find { |item_info| contains_keys?(item, item_info[:keys]) }
|
167
|
+
selected_item[:class] if selected_item
|
168
|
+
end
|
169
|
+
|
170
|
+
def contains_keys?(item, keys)
|
171
|
+
item.merge(keys) == item
|
172
|
+
end
|
173
|
+
|
174
|
+
def build_item(item, item_class)
|
175
|
+
new_item_opts = {}
|
176
|
+
item.each do |db_name, value|
|
177
|
+
name = item_class.attributes.db_to_attribute_name(db_name)
|
178
|
+
new_item_opts[name] = value
|
179
|
+
end
|
180
|
+
item = item_class.new(new_item_opts)
|
181
|
+
item.clean!
|
182
|
+
item
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|