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