dynamoid 3.7.1 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +246 -244
- data/LICENSE.txt +2 -2
- data/README.md +134 -55
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +66 -0
- data/lib/dynamoid/adapter.rb +7 -9
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -15
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +73 -59
- data/lib/dynamoid/associations/single_association.rb +28 -9
- data/lib/dynamoid/components.rb +2 -3
- data/lib/dynamoid/criteria/chain.rb +13 -9
- data/lib/dynamoid/criteria.rb +6 -7
- data/lib/dynamoid/dirty.rb +60 -63
- data/lib/dynamoid/document.rb +42 -12
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields.rb +19 -37
- data/lib/dynamoid/finders.rb +9 -4
- data/lib/dynamoid/indexes.rb +45 -40
- data/lib/dynamoid/loadable.rb +6 -1
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +66 -0
- data/lib/dynamoid/persistence/save.rb +52 -5
- data/lib/dynamoid/persistence/update_fields.rb +1 -1
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +1 -1
- data/lib/dynamoid/persistence.rb +149 -61
- data/lib/dynamoid/undumping.rb +18 -0
- data/lib/dynamoid/validations.rb +6 -0
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -0
- metadata +30 -50
data/LICENSE.txt
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
MIT License
|
1
|
+
The MIT License (MIT)
|
2
2
|
|
3
3
|
Copyright (c) 2012 Josh Symonds
|
4
|
-
Copyright (c) 2013 -
|
4
|
+
Copyright (c) 2013 - 2022 Dynamoid, https://github.com/Dynamoid
|
5
5
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
# Dynamoid
|
2
2
|
|
3
|
-
[![
|
4
|
-
[![
|
5
|
-
[![
|
6
|
-
[![
|
7
|
-
[![
|
8
|
-
[![
|
9
|
-
![
|
3
|
+
[![Gem Version][⛳️version-img]][⛳️gem]
|
4
|
+
[![Supported Build Status][🏘sup-wf-img]][🏘sup-wf]
|
5
|
+
[![Maintainability][⛳cclim-maint-img♻️]][⛳cclim-maint]
|
6
|
+
[![Coveralls][🏘coveralls-img]][🏘coveralls]
|
7
|
+
[![CodeCov][🖇codecov-img♻️]][🖇codecov]
|
8
|
+
[![Helpers][🖇triage-help-img]][🖇triage-help]
|
9
|
+
[![Contributors][🖐contributors-img]][🖐contributors]
|
10
|
+
[![RubyDoc.info][🚎yard-img]][🚎yard]
|
11
|
+
[![License][🖇src-license-img]][🖇src-license]
|
12
|
+
[![GitMoji][🖐gitmoji-img]][🖐gitmoji]
|
13
|
+
[![SemVer 2.0.0][🧮semver-img]][🧮semver]
|
14
|
+
[![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog]
|
15
|
+
[![Sponsor Project][🖇sponsor-img]][🖇sponsor]
|
10
16
|
|
11
17
|
Dynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It
|
12
18
|
provides similar functionality to ActiveRecord and improves on Amazon's
|
@@ -62,9 +68,9 @@ Create `config/initializers/aws.rb` as follows:
|
|
62
68
|
|
63
69
|
```ruby
|
64
70
|
Aws.config.update({
|
65
|
-
|
71
|
+
region: 'us-west-2',
|
66
72
|
credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),
|
67
|
-
})
|
73
|
+
})
|
68
74
|
```
|
69
75
|
|
70
76
|
Alternatively, if you don't want Aws connection settings to be
|
@@ -88,16 +94,16 @@ elsewhere in your project, etc.), you may do so:
|
|
88
94
|
require 'dynamoid'
|
89
95
|
|
90
96
|
credentials = Aws::AssumeRoleCredentials.new(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
+
region: region,
|
98
|
+
access_key_id: key,
|
99
|
+
secret_access_key: secret,
|
100
|
+
role_arn: role_arn,
|
101
|
+
role_session_name: 'our-session'
|
102
|
+
)
|
97
103
|
|
98
104
|
Dynamoid.configure do |config|
|
99
105
|
config.region = 'us-west-2',
|
100
|
-
|
106
|
+
config.credentials = credentials
|
101
107
|
end
|
102
108
|
```
|
103
109
|
|
@@ -126,8 +132,8 @@ end
|
|
126
132
|
Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
|
127
133
|
|
128
134
|
Its compatibility is tested against following Ruby versions: 2.3, 2.4,
|
129
|
-
2.5, 2.6, 2.7 and 3.
|
130
|
-
5.2, 6.0
|
135
|
+
2.5, 2.6, 2.7, 3.0, 3.1 and 3.2, JRuby 9.4.x and against Rails versions: 4.2, 5.0, 5.1,
|
136
|
+
5.2, 6.0, 6.1 and 7.0.
|
131
137
|
|
132
138
|
## Setup
|
133
139
|
|
@@ -351,7 +357,6 @@ class User
|
|
351
357
|
field :number, :number
|
352
358
|
field :joined_at, :datetime
|
353
359
|
field :hash, :serialized
|
354
|
-
|
355
360
|
end
|
356
361
|
```
|
357
362
|
|
@@ -397,7 +402,7 @@ class Money
|
|
397
402
|
'serialized representation as a string'
|
398
403
|
end
|
399
404
|
|
400
|
-
def self.dynamoid_load(
|
405
|
+
def self.dynamoid_load(_serialized_str)
|
401
406
|
# parse serialized representation and return a Money instance
|
402
407
|
Money.new(1.23)
|
403
408
|
end
|
@@ -422,7 +427,7 @@ serializing. Example:
|
|
422
427
|
class Money; end
|
423
428
|
|
424
429
|
class MoneyAdapter
|
425
|
-
def self.dynamoid_load(
|
430
|
+
def self.dynamoid_load(_money_serialized_str)
|
426
431
|
Money.new(1.23)
|
427
432
|
end
|
428
433
|
|
@@ -468,6 +473,10 @@ Second argument, type, is optional. Default type is `string`.
|
|
468
473
|
Just like in ActiveRecord (or your other favorite ORM), Dynamoid uses
|
469
474
|
associations to create links between models.
|
470
475
|
|
476
|
+
**WARNING:** Associations are not supported for models with compound
|
477
|
+
primary key. If a model declares a range key it should not declare any
|
478
|
+
association itself and be referenced by an association in another model.
|
479
|
+
|
471
480
|
The only supported associations (so far) are `has_many`, `has_one`,
|
472
481
|
`has_and_belongs_to_many`, and `belongs_to`. Associations are very
|
473
482
|
simple to create: just specify the type, the name, and then any options
|
@@ -537,9 +546,18 @@ model.save(validate: false)
|
|
537
546
|
|
538
547
|
### Callbacks
|
539
548
|
|
540
|
-
Dynamoid also employs ActiveModel callbacks. Right now
|
541
|
-
|
542
|
-
|
549
|
+
Dynamoid also employs ActiveModel callbacks. Right now the following
|
550
|
+
callbacks are supported:
|
551
|
+
- `save` (before, after, around)
|
552
|
+
- `create` (before, after, around)
|
553
|
+
- `update` (before, after, around)
|
554
|
+
- `validation` (before, after)
|
555
|
+
- `destroy` (before, after, around)
|
556
|
+
- `after_touch`
|
557
|
+
- `after_initialize`
|
558
|
+
- `after_find`
|
559
|
+
|
560
|
+
Example:
|
543
561
|
|
544
562
|
```ruby
|
545
563
|
class User
|
@@ -671,14 +689,14 @@ address.save
|
|
671
689
|
To create multiple documents at once:
|
672
690
|
|
673
691
|
```ruby
|
674
|
-
User.create([{name: 'Josh'}, {name: 'Nick'}])
|
692
|
+
User.create([{ name: 'Josh' }, { name: 'Nick' }])
|
675
693
|
```
|
676
694
|
|
677
695
|
There is an efficient and low-level way to create multiple documents
|
678
696
|
(without validation and callbacks running):
|
679
697
|
|
680
698
|
```ruby
|
681
|
-
users = User.import([{name: 'Josh'}, {name: 'Nick'}])
|
699
|
+
users = User.import([{ name: 'Josh' }, { name: 'Nick' }])
|
682
700
|
```
|
683
701
|
|
684
702
|
### Querying
|
@@ -783,8 +801,8 @@ for requesting documents in batches:
|
|
783
801
|
|
784
802
|
```ruby
|
785
803
|
# Do some maintenance on the entire table without flooding DynamoDB
|
786
|
-
Address.batch(100).each { |
|
787
|
-
Address.record_limit(10_000).batch(100).each {
|
804
|
+
Address.batch(100).each { |addr| addr.do_some_work && sleep(0.01) }
|
805
|
+
Address.record_limit(10_000).batch(100).each { |addr| addr.do_some_work && sleep(0.01) } # Batch specified as part of a chain
|
788
806
|
```
|
789
807
|
|
790
808
|
The implication of batches is that the underlying requests are done in
|
@@ -836,13 +854,13 @@ operators are available: `gt`, `lt`, `gte`, `lte`, `begins_with`,
|
|
836
854
|
`between` as well as equality:
|
837
855
|
|
838
856
|
```ruby
|
839
|
-
Address.where(latitude:
|
840
|
-
Address.where('latitude.gt':
|
841
|
-
Address.where('latitude.lt':
|
842
|
-
Address.where('latitude.gte':
|
843
|
-
Address.where('latitude.lte':
|
857
|
+
Address.where(latitude: 10_212)
|
858
|
+
Address.where('latitude.gt': 10_212)
|
859
|
+
Address.where('latitude.lt': 10_212)
|
860
|
+
Address.where('latitude.gte': 10_212)
|
861
|
+
Address.where('latitude.lte': 10_212)
|
844
862
|
Address.where('city.begins_with': 'Lon')
|
845
|
-
Address.where('latitude.between': [
|
863
|
+
Address.where('latitude.between': [10_212, 20_000])
|
846
864
|
```
|
847
865
|
|
848
866
|
You are able to filter results on the DynamoDB side and specify
|
@@ -850,7 +868,7 @@ conditions for non-key fields. Following additional operators are
|
|
850
868
|
available: `in`, `contains`, `not_contains`, `null`, `not_null`:
|
851
869
|
|
852
870
|
```ruby
|
853
|
-
Address.where('city.in': [
|
871
|
+
Address.where('city.in': %w[London Edenburg Birmingham])
|
854
872
|
Address.where('city.contains': ['on'])
|
855
873
|
Address.where('city.not_contains': ['ing'])
|
856
874
|
Address.where('postcode.null': false)
|
@@ -952,6 +970,14 @@ Address.upsert(id, city: 'Chicago')
|
|
952
970
|
Address.upsert(id, { city: 'Chicago' }, if: { deliverable: true })
|
953
971
|
```
|
954
972
|
|
973
|
+
By default, `#upsert` will update all attributes of the document if it already exists.
|
974
|
+
To idempotently create-but-not-update a record, apply the `unless_exists` condition
|
975
|
+
to its keys when you upsert.
|
976
|
+
|
977
|
+
```ruby
|
978
|
+
Address.upsert(id, { city: 'Chicago' }, if: { unless_exists: [:id] })
|
979
|
+
```
|
980
|
+
|
955
981
|
### Deleting
|
956
982
|
|
957
983
|
In order to delete some items `delete_all` method should be used. Any
|
@@ -1033,6 +1059,21 @@ resolving the fields with a second query against the table since a query
|
|
1033
1059
|
against GSI then a query on base table is still likely faster than scan
|
1034
1060
|
on the base table*
|
1035
1061
|
|
1062
|
+
### PartiQL
|
1063
|
+
|
1064
|
+
To run PartiQL statements `Dynamoid.adapter.execute` method should be
|
1065
|
+
used:
|
1066
|
+
|
1067
|
+
```ruby
|
1068
|
+
Dynamoid.adapter.execute("UPDATE users SET name = 'Mike' WHERE id = '1'")
|
1069
|
+
```
|
1070
|
+
|
1071
|
+
Parameters are also supported:
|
1072
|
+
|
1073
|
+
```ruby
|
1074
|
+
Dynamoid.adapter.execute('SELECT * FROM users WHERE id = ?', ['1'])
|
1075
|
+
```
|
1076
|
+
|
1036
1077
|
## Configuration
|
1037
1078
|
|
1038
1079
|
Listed below are all configuration options.
|
@@ -1073,7 +1114,7 @@ Listed below are all configuration options.
|
|
1073
1114
|
when referring to them. Isn't thread safe. Default is `false`.
|
1074
1115
|
`Use Dynamoid::Middleware::IdentityMap` to clear identity map for each HTTP request
|
1075
1116
|
* `timestamps` - by default Dynamoid sets `created_at` and `updated_at`
|
1076
|
-
fields
|
1117
|
+
fields at model creation and updating. You can disable this
|
1077
1118
|
behavior by setting `false` value
|
1078
1119
|
* `sync_retry_max_times` - when Dynamoid creates or deletes table
|
1079
1120
|
synchronously it checks for completion specified times. Default is 60
|
@@ -1269,6 +1310,7 @@ accidentally by adding the following to your test environment setup:
|
|
1269
1310
|
|
1270
1311
|
```ruby
|
1271
1312
|
raise "Tests should be run in 'test' environment only" if Rails.env != 'test'
|
1313
|
+
|
1272
1314
|
Dynamoid.configure do |config|
|
1273
1315
|
config.namespace = "#{Rails.application.railtie_name}_#{Rails.env}"
|
1274
1316
|
end
|
@@ -1335,7 +1377,7 @@ DynamoDB running locally. Follow these steps to setup your test
|
|
1335
1377
|
environment.
|
1336
1378
|
|
1337
1379
|
* First download and unpack the latest version of DynamoDB. We have a
|
1338
|
-
script that will do this for you if you use homebrew on a Mac.
|
1380
|
+
script that will do this for you if you use bash, and homebrew on a Mac.
|
1339
1381
|
|
1340
1382
|
```shell
|
1341
1383
|
bin/setup
|
@@ -1360,25 +1402,62 @@ environment.
|
|
1360
1402
|
bin/stop_dynamodblocal
|
1361
1403
|
```
|
1362
1404
|
|
1363
|
-
If you
|
1364
|
-
|
1365
|
-
2.3.8 2.4.6 2.5.5 2.6.3 2.7.0 3.0.0 9.2.14.0)`. When you run
|
1366
|
-
`bundle exec wwtd` it will take care of starting and stopping the local
|
1367
|
-
dynamodb instance.
|
1368
|
-
|
1405
|
+
If you run into issues, please try these steps first.
|
1406
|
+
NOTE: You can use any version manager: rvm, rbenv, chruby, asdf-ruby
|
1369
1407
|
```shell
|
1370
|
-
|
1371
|
-
|
1372
|
-
gem
|
1408
|
+
asdf install ruby 3.1.1
|
1409
|
+
asdf local ruby 3.1.1
|
1410
|
+
gem update --system
|
1373
1411
|
bundle install
|
1374
1412
|
```
|
1375
1413
|
|
1376
|
-
##
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1414
|
+
## Security
|
1415
|
+
|
1416
|
+
See [SECURITY.md][security].
|
1417
|
+
|
1418
|
+
## Related links
|
1419
|
+
|
1420
|
+
This documentation may be useful for the contributors:
|
1421
|
+
- <https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/welcome.html>
|
1422
|
+
- <https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html>
|
1423
|
+
|
1424
|
+
## License
|
1425
|
+
|
1426
|
+
The gem is available as open source under the terms of
|
1427
|
+
the [MIT License][license] [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)][license-ref].
|
1428
|
+
See [LICENSE][license] for the official [Copyright Notice][copyright-notice-explainer].
|
1429
|
+
|
1430
|
+
[copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
1431
|
+
|
1432
|
+
[license]: https://github.com/Dynamoid/dynamoid/blob/master/LICENSE.txt
|
1433
|
+
|
1434
|
+
[license-ref]: https://opensource.org/licenses/MIT
|
1435
|
+
|
1436
|
+
[security]: https://github.com/Dynamoid/dynamoid/blob/master/SECURITY.md
|
1437
|
+
|
1438
|
+
[⛳️gem]: https://rubygems.org/gems/dynamoid
|
1439
|
+
[⛳️version-img]: http://img.shields.io/gem/v/dynamoid.svg
|
1440
|
+
[⛳cclim-maint]: https://codeclimate.com/github/Dynamoid/dynamoid/maintainability
|
1441
|
+
[⛳cclim-maint-img♻️]: https://api.codeclimate.com/v1/badges/27fd8b6b7ff338fa4914/maintainability
|
1442
|
+
[🏘coveralls]: https://coveralls.io/github/Dynamoid/dynamoid?branch=master
|
1443
|
+
[🏘coveralls-img]: https://coveralls.io/repos/github/Dynamoid/dynamoid/badge.svg?branch=master
|
1444
|
+
[🖇codecov]: https://codecov.io/gh/Dynamoid/dynamoid
|
1445
|
+
[🖇codecov-img♻️]: https://codecov.io/gh/Dynamoid/dynamoid/branch/master/graph/badge.svg?token=84WeeoxaN9
|
1446
|
+
[🖇src-license]: https://github.com/Dynamoid/dynamoid/blob/master/LICENSE.txt
|
1447
|
+
[🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg
|
1448
|
+
[🖐gitmoji]: https://gitmoji.dev
|
1449
|
+
[🖐gitmoji-img]: https://img.shields.io/badge/gitmoji-3.9.0-FFDD67.svg?style=flat
|
1450
|
+
[🚎yard]: https://www.rubydoc.info/gems/dynamoid
|
1451
|
+
[🚎yard-img]: https://img.shields.io/badge/yard-docs-blue.svg?style=flat
|
1452
|
+
[🧮semver]: http://semver.org/
|
1453
|
+
[🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat
|
1454
|
+
[🖐contributors]: https://github.com/Dynamoid/dynamoid/graphs/contributors
|
1455
|
+
[🖐contributors-img]: https://img.shields.io/github/contributors-anon/Dynamoid/dynamoid
|
1456
|
+
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
|
1457
|
+
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat
|
1458
|
+
[🖇sponsor-img]: https://img.shields.io/opencollective/all/dynamoid
|
1459
|
+
[🖇sponsor]: https://opencollective.com/dynamoid
|
1460
|
+
[🖇triage-help]: https://www.codetriage.com/dynamoid/dynamoid
|
1461
|
+
[🖇triage-help-img]: https://www.codetriage.com/dynamoid/dynamoid/badges/users.svg
|
1462
|
+
[🏘sup-wf]: https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml?query=branch%3Amaster
|
1463
|
+
[🏘sup-wf-img]: https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml/badge.svg?branch=master
|
data/SECURITY.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
| Version | Supported |
|
6
|
+
|---------|-----------|
|
7
|
+
| 3.7.x | ✅ |
|
8
|
+
| <= 3.6 | ❌ |
|
9
|
+
| 2.x | ❌ |
|
10
|
+
| 1.x | ❌ |
|
11
|
+
| 0.x | ❌ |
|
12
|
+
|
13
|
+
## Reporting a Vulnerability
|
14
|
+
|
15
|
+
Peter Boling is responsible for the security maintenance of this gem. Please find a way
|
16
|
+
to [contact him directly](https://railsbling.com/contact) to report the issue. Include as much relevant information as
|
17
|
+
possible.
|
data/dynamoid.gemspec
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'dynamoid/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'dynamoid'
|
9
|
+
spec.version = Dynamoid::VERSION
|
10
|
+
|
11
|
+
# Keep in sync with README
|
12
|
+
spec.authors = [
|
13
|
+
'Josh Symonds',
|
14
|
+
'Logan Bowers',
|
15
|
+
'Craig Heneveld',
|
16
|
+
'Anatha Kumaran',
|
17
|
+
'Jason Dew',
|
18
|
+
'Luis Arias',
|
19
|
+
'Stefan Neculai',
|
20
|
+
'Philip White',
|
21
|
+
'Peeyush Kumar',
|
22
|
+
'Sumanth Ravipati',
|
23
|
+
'Pascal Corpet',
|
24
|
+
'Brian Glusman',
|
25
|
+
'Peter Boling',
|
26
|
+
'Andrew Konchin'
|
27
|
+
]
|
28
|
+
spec.email = ['andry.konchin@gmail.com', 'peter.boling@gmail.com', 'brian@stellaservice.com']
|
29
|
+
|
30
|
+
spec.description = "Dynamoid is an ORM for Amazon's DynamoDB that supports offline development, associations, querying, and everything else you'd expect from an ActiveRecord-style replacement."
|
31
|
+
spec.summary = "Dynamoid is an ORM for Amazon's DynamoDB"
|
32
|
+
# Ignore not commited files
|
33
|
+
spec.files = Dir[
|
34
|
+
'CHANGELOG.md',
|
35
|
+
'dynamoid.gemspec',
|
36
|
+
'lib/**/*',
|
37
|
+
'LICENSE.txt',
|
38
|
+
'README.md',
|
39
|
+
'SECURITY.md'
|
40
|
+
]
|
41
|
+
spec.homepage = 'http://github.com/Dynamoid/dynamoid'
|
42
|
+
spec.licenses = ['MIT']
|
43
|
+
spec.require_paths = ['lib']
|
44
|
+
|
45
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
46
|
+
spec.metadata['source_code_uri'] = "https://github.com/Dynamoid/dynamoid/tree/v#{spec.version}"
|
47
|
+
spec.metadata['changelog_uri'] = "https://github.com/Dynamoid/dynamoid/blob/v#{spec.version}/CHANGELOG.md"
|
48
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/Dynamoid/dynamoid/issues'
|
49
|
+
spec.metadata['documentation_uri'] = "https://www.rubydoc.info/gems/dynamoid/#{spec.version}"
|
50
|
+
spec.metadata['funding_uri'] = 'https://opencollective.com/dynamoid'
|
51
|
+
spec.metadata['wiki_uri'] = 'https://github.com/Dynamoid/dynamoid/wiki'
|
52
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
53
|
+
|
54
|
+
spec.add_runtime_dependency 'activemodel', '>=4'
|
55
|
+
spec.add_runtime_dependency 'aws-sdk-dynamodb', '~> 1.0'
|
56
|
+
spec.add_runtime_dependency 'concurrent-ruby', '>= 1.0'
|
57
|
+
|
58
|
+
spec.add_development_dependency 'appraisal'
|
59
|
+
spec.add_development_dependency 'bundler'
|
60
|
+
spec.add_development_dependency 'pry', '~> 0.14'
|
61
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
62
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
63
|
+
# 'rubocop-lts' is for Ruby 2.3+, see https://rubocop-lts.gitlab.io/
|
64
|
+
spec.add_development_dependency 'rubocop-lts', '~> 10.0'
|
65
|
+
spec.add_development_dependency 'yard'
|
66
|
+
end
|
data/lib/dynamoid/adapter.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
# https://github.com/ruby-concurrency/concurrent-ruby/pull/377
|
5
|
-
require 'concurrent'
|
3
|
+
require 'concurrent/atom'
|
6
4
|
require 'dynamoid/adapter_plugin/aws_sdk_v3'
|
7
5
|
|
8
|
-
# encoding: utf-8
|
9
6
|
module Dynamoid
|
10
7
|
# Adapter's value-add:
|
11
8
|
# 1) For the rest of Dynamoid, the gateway to DynamoDB.
|
@@ -44,7 +41,7 @@ module Dynamoid
|
|
44
41
|
|
45
42
|
# Shows how long it takes a method to run on the adapter. Useful for generating logged output.
|
46
43
|
#
|
47
|
-
# @param [Symbol] method the name of the method to appear in the log
|
44
|
+
# @param [Symbol|String] method the name of the method to appear in the log
|
48
45
|
# @param [Array] args the arguments to the method to appear in the log
|
49
46
|
# @yield the actual code to benchmark
|
50
47
|
#
|
@@ -78,7 +75,7 @@ module Dynamoid
|
|
78
75
|
# that the batch get will acquire the correct record.
|
79
76
|
#
|
80
77
|
# @param [String] table the name of the table to write the object to
|
81
|
-
# @param [Array] ids to fetch
|
78
|
+
# @param [String, Array] ids to fetch; can also be a string of just one id
|
82
79
|
# @param [Hash] options Passed to the underlying query. The :range_key option is required whenever the table has a range key,
|
83
80
|
# unless multiple ids are passed in.
|
84
81
|
#
|
@@ -94,7 +91,7 @@ module Dynamoid
|
|
94
91
|
# Delete an item from a table.
|
95
92
|
#
|
96
93
|
# @param [String] table the name of the table to write the object to
|
97
|
-
# @param [Array] ids to delete
|
94
|
+
# @param [String, Array] ids to delete; can also be a string of just one id
|
98
95
|
# @param [Hash] options allowed only +range_key+ - range key or array of
|
99
96
|
# range keys of the record to delete, can also be
|
100
97
|
# a string of just one range_key, and +conditions+
|
@@ -145,12 +142,12 @@ module Dynamoid
|
|
145
142
|
end
|
146
143
|
end
|
147
144
|
|
148
|
-
%i[batch_get_item delete_item get_item list_tables put_item truncate batch_write_item batch_delete_item].each do |m|
|
145
|
+
%i[batch_get_item delete_item get_item list_tables put_item truncate batch_write_item batch_delete_item execute].each do |m|
|
149
146
|
# Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
|
150
147
|
#
|
151
148
|
# @since 0.2.0
|
152
149
|
define_method(m) do |*args, &blk|
|
153
|
-
benchmark(m
|
150
|
+
benchmark(m, *args) { adapter.send(m, *args, &blk) }
|
154
151
|
end
|
155
152
|
end
|
156
153
|
|
@@ -166,6 +163,7 @@ module Dynamoid
|
|
166
163
|
# https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
167
164
|
|
168
165
|
return benchmark(method, *args) { adapter.send(method, *args, &block) } if adapter.respond_to?(method)
|
166
|
+
|
169
167
|
super
|
170
168
|
end
|
171
169
|
|
@@ -87,8 +87,8 @@ module Dynamoid
|
|
87
87
|
# unprocessed_keys Hash contains as values instances of
|
88
88
|
# Aws::DynamoDB::Types::KeysAndAttributes
|
89
89
|
@api_response.unprocessed_keys[table.name].keys.map do |h|
|
90
|
-
# If a table has a composite key then we need to return an array
|
91
|
-
# of [
|
90
|
+
# If a table has a composite primary key then we need to return an array
|
91
|
+
# of [hash key, range key]. Otherwise just return hash key's
|
92
92
|
# value.
|
93
93
|
if table.range_key.nil?
|
94
94
|
h[table.hash_key.to_s]
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
# @private
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
# Excecute a PartiQL query
|
8
|
+
#
|
9
|
+
# Documentation:
|
10
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteStatement.html
|
11
|
+
# - https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#execute_statement-instance_method
|
12
|
+
#
|
13
|
+
# NOTE: For reads result may be paginated. Only pagination with NextToken
|
14
|
+
# is implemented. Currently LastEvaluatedKey in response cannot be fed to
|
15
|
+
# ExecuteStatement to get the next page.
|
16
|
+
#
|
17
|
+
# See also:
|
18
|
+
# - https://repost.aws/questions/QUgNPbBYWiRoOlMsJv-XzrWg/how-to-use-last-evaluated-key-in-execute-statement-request
|
19
|
+
# - https://stackoverflow.com/questions/71438439/aws-dynamodb-executestatement-pagination
|
20
|
+
class ExecuteStatement
|
21
|
+
attr_reader :client, :statement, :parameters, :options
|
22
|
+
|
23
|
+
def initialize(client, statement, parameters, options)
|
24
|
+
@client = client
|
25
|
+
@statement = statement
|
26
|
+
@parameters = parameters
|
27
|
+
@options = options.symbolize_keys.slice(:consistent_read)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
request = {
|
32
|
+
statement: @statement,
|
33
|
+
parameters: @parameters,
|
34
|
+
consistent_read: @options[:consistent_read],
|
35
|
+
}
|
36
|
+
|
37
|
+
response = client.execute_statement(request)
|
38
|
+
|
39
|
+
unless response.next_token
|
40
|
+
return response_to_items(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
Enumerator.new do |yielder|
|
44
|
+
yielder.yield(response_to_items(response))
|
45
|
+
|
46
|
+
while response.next_token
|
47
|
+
request[:next_token] = response.next_token
|
48
|
+
response = client.execute_statement(request)
|
49
|
+
yielder.yield(response_to_items(response))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def response_to_items(response)
|
57
|
+
response.items.map(&:symbolize_keys)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -6,6 +6,10 @@ module Dynamoid
|
|
6
6
|
class AwsSdkV3
|
7
7
|
# Mimics behavior of the yielded object on DynamoDB's update_item API (high level).
|
8
8
|
class ItemUpdater
|
9
|
+
ADD = 'ADD'
|
10
|
+
DELETE = 'DELETE'
|
11
|
+
PUT = 'PUT'
|
12
|
+
|
9
13
|
attr_reader :table, :key, :range_key
|
10
14
|
|
11
15
|
def initialize(table, key, range_key = nil)
|
@@ -31,11 +35,15 @@ module Dynamoid
|
|
31
35
|
#
|
32
36
|
# Removes values from the sets of the given columns
|
33
37
|
#
|
34
|
-
# @param [Hash] values keys of the hash are the columns, values are Arrays/Sets of items
|
35
|
-
#
|
38
|
+
# @param [Hash|Symbol|String] values keys of the hash are the columns, values are Arrays/Sets of items
|
39
|
+
# to remove
|
36
40
|
#
|
37
41
|
def delete(values)
|
38
|
-
|
42
|
+
if values.is_a?(Hash)
|
43
|
+
@deletions.merge!(sanitize_attributes(values))
|
44
|
+
else
|
45
|
+
@deletions.merge!(values.to_s => nil)
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
#
|
@@ -48,42 +56,48 @@ module Dynamoid
|
|
48
56
|
#
|
49
57
|
# Returns an AttributeUpdates hash suitable for passing to the V2 Client API
|
50
58
|
#
|
51
|
-
def
|
52
|
-
|
59
|
+
def attribute_updates
|
60
|
+
result = {}
|
53
61
|
|
54
62
|
@additions.each do |k, v|
|
55
|
-
|
63
|
+
result[k] = {
|
56
64
|
action: ADD,
|
57
65
|
value: v
|
58
66
|
}
|
59
67
|
end
|
68
|
+
|
60
69
|
@deletions.each do |k, v|
|
61
|
-
|
70
|
+
result[k] = {
|
62
71
|
action: DELETE
|
63
72
|
}
|
64
|
-
|
73
|
+
result[k][:value] = v unless v.nil?
|
65
74
|
end
|
75
|
+
|
66
76
|
@updates.each do |k, v|
|
67
|
-
|
77
|
+
result[k] = {
|
68
78
|
action: PUT,
|
69
79
|
value: v
|
70
80
|
}
|
71
81
|
end
|
72
82
|
|
73
|
-
|
83
|
+
result
|
74
84
|
end
|
75
85
|
|
76
86
|
private
|
77
87
|
|
78
88
|
def sanitize_attributes(attributes)
|
79
89
|
attributes.transform_values do |v|
|
80
|
-
v.is_a?(Hash)
|
90
|
+
if v.is_a?(Hash)
|
91
|
+
v.stringify_keys
|
92
|
+
elsif v.is_a?(Set) && v.empty?
|
93
|
+
nil
|
94
|
+
elsif v.is_a?(String) && v.empty?
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
v
|
98
|
+
end
|
81
99
|
end
|
82
100
|
end
|
83
|
-
|
84
|
-
ADD = 'ADD'
|
85
|
-
DELETE = 'DELETE'
|
86
|
-
PUT = 'PUT'
|
87
101
|
end
|
88
102
|
end
|
89
103
|
end
|
@@ -21,14 +21,17 @@ module Dynamoid
|
|
21
21
|
# lower to obey limits. We can assume the difference won't be
|
22
22
|
# negative due to break statements below but choose smaller limit
|
23
23
|
# which is why we have 2 separate if statements.
|
24
|
+
#
|
24
25
|
# NOTE: Adjusting based on record_limit can cause many HTTP requests
|
25
26
|
# being made. We may want to change this behavior, but it affects
|
26
27
|
# filtering on data with potentially large gaps.
|
28
|
+
#
|
27
29
|
# Example:
|
28
30
|
# User.where('created_at.gte' => 1.day.ago).record_limit(1000)
|
29
31
|
# Records 1-999 User's that fit criteria
|
30
32
|
# Records 1000-2000 Users's that do not fit criteria
|
31
33
|
# Record 2001 fits criteria
|
34
|
+
#
|
32
35
|
# The underlying implementation will have 1 page for records 1-999
|
33
36
|
# then will request with limit 1 for records 1000-2000 (making 1000
|
34
37
|
# requests of limit 1) until hit record 2001.
|