dynamoid 3.7.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-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.
|