dynamoid 3.8.0 → 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 +45 -0
- data/README.md +96 -42
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +66 -0
- data/lib/dynamoid/adapter.rb +2 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +9 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +29 -8
- data/lib/dynamoid/components.rb +2 -3
- data/lib/dynamoid/criteria/chain.rb +7 -6
- data/lib/dynamoid/dirty.rb +56 -56
- data/lib/dynamoid/document.rb +38 -2
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields.rb +4 -20
- data/lib/dynamoid/finders.rb +9 -4
- data/lib/dynamoid/indexes.rb +2 -4
- 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_validations.rb +1 -1
- data/lib/dynamoid/persistence.rb +91 -46
- data/lib/dynamoid/version.rb +1 -1
- metadata +27 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aedb91a28972fd9357cbb260341e617c3d0d35ae05a620d5ac8312f5d3edbf96
|
4
|
+
data.tar.gz: a026d8c3d1e53f4e6800dcffe2b8350da50f70d953a19ae86dfeff0f65555c8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcb483ea21eaa16246d82603d18952a70bab15556b65ea94fad3ac9dbfe6358f00730e1825275d241a18675fdc0a2e4c3c4b0553acb41f9205e5fe350337f54c
|
7
|
+
data.tar.gz: 4e4c96ed9b90ccab85f2f09ad848455affa681c93740dcf1f7b6283797f8e47bebd1c83a70a524eb6ea44906bdd6baec07549f67a9e6bd6561b857ea8997b692
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
|
+
|
8
9
|
### Fixed
|
9
10
|
|
10
11
|
### Added
|
@@ -13,6 +14,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
13
14
|
|
14
15
|
### Removed
|
15
16
|
|
17
|
+
## 3.9.0
|
18
|
+
### Fixed
|
19
|
+
* [#610](https://github.com/Dynamoid/dynamoid/pull/610) Specs in JRuby; Support for JRuby 9.4.0.0 (@pboling)
|
20
|
+
* [#624](https://github.com/Dynamoid/dynamoid/pull/624) Fixed `#increment!`/`#decrement!` methods and made them compatible with Rails counterparts
|
21
|
+
* [#626](https://github.com/Dynamoid/dynamoid/pull/626) Fixed saving empty Set and String and replacing with `nil` in `#update`, `#update!`, `.update_fields`, and `.upsert` methods
|
22
|
+
* [#628](https://github.com/Dynamoid/dynamoid/pull/628) Fixed `.import` method to mark persisted model attributes as not changed/not dirty
|
23
|
+
* [#632](https://github.com/Dynamoid/dynamoid/pull/632) Fixed `#save` called with `touch: false` option to set `updated_at` attribute even for a new record (to comply with Rails)
|
24
|
+
* [#634](https://github.com/Dynamoid/dynamoid/pull/634) Fixed model callbacks:
|
25
|
+
* changed order of `save` and `create`/`update` callbacks - `save` callbacks are outer for the `create`/`update` ones
|
26
|
+
* removed `before_initialize` and `around_initialize` callbacks - there should be only `after_initialize` one
|
27
|
+
* [#634](https://github.com/Dynamoid/dynamoid/pull/634) Fixed `#touch` method compatibility with a Rails counterpart:
|
28
|
+
* don't save other modified attributes - only timestamps
|
29
|
+
* don't perform validation and don't call `save`/`create`/`update` callbacks
|
30
|
+
* accept a list of attribute names, but not one name
|
31
|
+
* accept a `:time` option
|
32
|
+
### Added
|
33
|
+
* [#611](https://github.com/Dynamoid/dynamoid/pull/611) Add `rubocop-md` (@pboling)
|
34
|
+
* [#612](https://github.com/Dynamoid/dynamoid/pull/612) Add `rubocop-rspec` (@pboling)
|
35
|
+
* [#613](https://github.com/Dynamoid/dynamoid/pull/613) Add `rubocop-performance` and `rubocop-rake` (@pboling)
|
36
|
+
* Added `funding_uri` set to open collective: https://opencollective.com/dynamoid
|
37
|
+
* Added `required_ruby_version` as `>= 2.3.0` (which was already the minimum supported version of Ruby)
|
38
|
+
* [#616](https://github.com/Dynamoid/dynamoid/pull/616) Upgrade `simplecov` (& remove `coveralls`) (@pboling)
|
39
|
+
* Setup GitHub actions for Code Coverage
|
40
|
+
* Setup GitHub actions for RuboCop linting
|
41
|
+
* Automate coverage feedback on Pull Requests via GitHub Actions and CodeCov
|
42
|
+
* [#618](https://github.com/Dynamoid/dynamoid/pull/618) Upgrade README Badges (@pboling)
|
43
|
+
* [#624](https://github.com/Dynamoid/dynamoid/pull/624) Added `:touch` option for `.inc` method to be more compatible with the Rails counterpart method `.update_counters`
|
44
|
+
* [#627](https://github.com/Dynamoid/dynamoid/pull/627) Made the following methods in the Dirty API public (to comply with Rails):
|
45
|
+
* `clear_changes_information`
|
46
|
+
* `changes_applied`
|
47
|
+
* `clear_attribute_changes`
|
48
|
+
* [#630](https://github.com/Dynamoid/dynamoid/pull/630) Added `Dynamoid::Adapter#execute` method to run PartiQL queries
|
49
|
+
* [#634](https://github.com/Dynamoid/dynamoid/pull/634) Added `after_touch` callback and run it in the following methods:
|
50
|
+
* `#touch`
|
51
|
+
* `#increment!`
|
52
|
+
* `#decrement!`
|
53
|
+
* [#642](https://github.com/Dynamoid/dynamoid/pull/642) Run specs on CI agains Ruby 3.2
|
54
|
+
* [#645](https://github.com/Dynamoid/dynamoid/pull/645) Added `after_find` callback
|
55
|
+
### Changed
|
56
|
+
* [#610](https://github.com/Dynamoid/dynamoid/pull/610) Switch to [`rubocop-lts`](https://rubocop-lts.gitlab.io/) (@pboling)
|
57
|
+
### Removed
|
58
|
+
* [#633](https://github.com/Dynamoid/dynamoid/pull/633) Change `#inspect` method to return only attributes
|
59
|
+
* [#623](https://github.com/Dynamoid/dynamoid/pull/623) Optimized performance of persisting to send only changed attributes in a request to DynamoDB
|
60
|
+
|
16
61
|
## 3.8.0
|
17
62
|
### Fixed
|
18
63
|
* [#525](https://github.com/Dynamoid/dynamoid/pull/525) Don't mark an attribute as changed if new assigned value equals the old one (@a5-stable)
|
data/README.md
CHANGED
@@ -1,21 +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]
|
10
12
|
[![GitMoji][🖐gitmoji-img]][🖐gitmoji]
|
11
|
-
[![SemVer 2.0.0][🧮semver-img]][semver]
|
13
|
+
[![SemVer 2.0.0][🧮semver-img]][🧮semver]
|
12
14
|
[![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog]
|
13
|
-
|
14
|
-
[🖐gitmoji]: https://gitmoji.dev
|
15
|
-
[🖐gitmoji-img]: https://img.shields.io/badge/gitmoji-3.9.0-FFDD67.svg?style=flat
|
16
|
-
[🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat
|
17
|
-
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
|
18
|
-
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat
|
15
|
+
[![Sponsor Project][🖇sponsor-img]][🖇sponsor]
|
19
16
|
|
20
17
|
Dynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It
|
21
18
|
provides similar functionality to ActiveRecord and improves on Amazon's
|
@@ -71,9 +68,9 @@ Create `config/initializers/aws.rb` as follows:
|
|
71
68
|
|
72
69
|
```ruby
|
73
70
|
Aws.config.update({
|
74
|
-
|
71
|
+
region: 'us-west-2',
|
75
72
|
credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),
|
76
|
-
})
|
73
|
+
})
|
77
74
|
```
|
78
75
|
|
79
76
|
Alternatively, if you don't want Aws connection settings to be
|
@@ -97,16 +94,16 @@ elsewhere in your project, etc.), you may do so:
|
|
97
94
|
require 'dynamoid'
|
98
95
|
|
99
96
|
credentials = Aws::AssumeRoleCredentials.new(
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
+
)
|
106
103
|
|
107
104
|
Dynamoid.configure do |config|
|
108
105
|
config.region = 'us-west-2',
|
109
|
-
|
106
|
+
config.credentials = credentials
|
110
107
|
end
|
111
108
|
```
|
112
109
|
|
@@ -135,7 +132,7 @@ end
|
|
135
132
|
Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
|
136
133
|
|
137
134
|
Its compatibility is tested against following Ruby versions: 2.3, 2.4,
|
138
|
-
2.5, 2.6, 2.7 and 3.
|
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,
|
139
136
|
5.2, 6.0, 6.1 and 7.0.
|
140
137
|
|
141
138
|
## Setup
|
@@ -360,7 +357,6 @@ class User
|
|
360
357
|
field :number, :number
|
361
358
|
field :joined_at, :datetime
|
362
359
|
field :hash, :serialized
|
363
|
-
|
364
360
|
end
|
365
361
|
```
|
366
362
|
|
@@ -406,7 +402,7 @@ class Money
|
|
406
402
|
'serialized representation as a string'
|
407
403
|
end
|
408
404
|
|
409
|
-
def self.dynamoid_load(
|
405
|
+
def self.dynamoid_load(_serialized_str)
|
410
406
|
# parse serialized representation and return a Money instance
|
411
407
|
Money.new(1.23)
|
412
408
|
end
|
@@ -431,7 +427,7 @@ serializing. Example:
|
|
431
427
|
class Money; end
|
432
428
|
|
433
429
|
class MoneyAdapter
|
434
|
-
def self.dynamoid_load(
|
430
|
+
def self.dynamoid_load(_money_serialized_str)
|
435
431
|
Money.new(1.23)
|
436
432
|
end
|
437
433
|
|
@@ -550,9 +546,18 @@ model.save(validate: false)
|
|
550
546
|
|
551
547
|
### Callbacks
|
552
548
|
|
553
|
-
Dynamoid also employs ActiveModel callbacks. Right now
|
554
|
-
|
555
|
-
|
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:
|
556
561
|
|
557
562
|
```ruby
|
558
563
|
class User
|
@@ -684,14 +689,14 @@ address.save
|
|
684
689
|
To create multiple documents at once:
|
685
690
|
|
686
691
|
```ruby
|
687
|
-
User.create([{name: 'Josh'}, {name: 'Nick'}])
|
692
|
+
User.create([{ name: 'Josh' }, { name: 'Nick' }])
|
688
693
|
```
|
689
694
|
|
690
695
|
There is an efficient and low-level way to create multiple documents
|
691
696
|
(without validation and callbacks running):
|
692
697
|
|
693
698
|
```ruby
|
694
|
-
users = User.import([{name: 'Josh'}, {name: 'Nick'}])
|
699
|
+
users = User.import([{ name: 'Josh' }, { name: 'Nick' }])
|
695
700
|
```
|
696
701
|
|
697
702
|
### Querying
|
@@ -796,8 +801,8 @@ for requesting documents in batches:
|
|
796
801
|
|
797
802
|
```ruby
|
798
803
|
# Do some maintenance on the entire table without flooding DynamoDB
|
799
|
-
Address.batch(100).each { |
|
800
|
-
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
|
801
806
|
```
|
802
807
|
|
803
808
|
The implication of batches is that the underlying requests are done in
|
@@ -849,13 +854,13 @@ operators are available: `gt`, `lt`, `gte`, `lte`, `begins_with`,
|
|
849
854
|
`between` as well as equality:
|
850
855
|
|
851
856
|
```ruby
|
852
|
-
Address.where(latitude:
|
853
|
-
Address.where('latitude.gt':
|
854
|
-
Address.where('latitude.lt':
|
855
|
-
Address.where('latitude.gte':
|
856
|
-
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)
|
857
862
|
Address.where('city.begins_with': 'Lon')
|
858
|
-
Address.where('latitude.between': [
|
863
|
+
Address.where('latitude.between': [10_212, 20_000])
|
859
864
|
```
|
860
865
|
|
861
866
|
You are able to filter results on the DynamoDB side and specify
|
@@ -863,7 +868,7 @@ conditions for non-key fields. Following additional operators are
|
|
863
868
|
available: `in`, `contains`, `not_contains`, `null`, `not_null`:
|
864
869
|
|
865
870
|
```ruby
|
866
|
-
Address.where('city.in': [
|
871
|
+
Address.where('city.in': %w[London Edenburg Birmingham])
|
867
872
|
Address.where('city.contains': ['on'])
|
868
873
|
Address.where('city.not_contains': ['ing'])
|
869
874
|
Address.where('postcode.null': false)
|
@@ -965,6 +970,14 @@ Address.upsert(id, city: 'Chicago')
|
|
965
970
|
Address.upsert(id, { city: 'Chicago' }, if: { deliverable: true })
|
966
971
|
```
|
967
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
|
+
|
968
981
|
### Deleting
|
969
982
|
|
970
983
|
In order to delete some items `delete_all` method should be used. Any
|
@@ -1046,6 +1059,21 @@ resolving the fields with a second query against the table since a query
|
|
1046
1059
|
against GSI then a query on base table is still likely faster than scan
|
1047
1060
|
on the base table*
|
1048
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
|
+
|
1049
1077
|
## Configuration
|
1050
1078
|
|
1051
1079
|
Listed below are all configuration options.
|
@@ -1282,6 +1310,7 @@ accidentally by adding the following to your test environment setup:
|
|
1282
1310
|
|
1283
1311
|
```ruby
|
1284
1312
|
raise "Tests should be run in 'test' environment only" if Rails.env != 'test'
|
1313
|
+
|
1285
1314
|
Dynamoid.configure do |config|
|
1286
1315
|
config.namespace = "#{Rails.application.railtie_name}_#{Rails.env}"
|
1287
1316
|
end
|
@@ -1406,4 +1435,29 @@ See [LICENSE][license] for the official [Copyright Notice][copyright-notice-expl
|
|
1406
1435
|
|
1407
1436
|
[security]: https://github.com/Dynamoid/dynamoid/blob/master/SECURITY.md
|
1408
1437
|
|
1409
|
-
[
|
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
@@ -142,7 +142,7 @@ module Dynamoid
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
%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|
|
146
146
|
# Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
|
147
147
|
#
|
148
148
|
# @since 0.2.0
|
@@ -163,6 +163,7 @@ module Dynamoid
|
|
163
163
|
# https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
164
164
|
|
165
165
|
return benchmark(method, *args) { adapter.send(method, *args, &block) } if adapter.respond_to?(method)
|
166
|
+
|
166
167
|
super
|
167
168
|
end
|
168
169
|
|
@@ -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
|
@@ -87,7 +87,15 @@ module Dynamoid
|
|
87
87
|
|
88
88
|
def sanitize_attributes(attributes)
|
89
89
|
attributes.transform_values do |v|
|
90
|
-
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
|
91
99
|
end
|
92
100
|
end
|
93
101
|
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.
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'aws_sdk_v3/query'
|
4
4
|
require_relative 'aws_sdk_v3/scan'
|
5
|
+
require_relative 'aws_sdk_v3/execute_statement'
|
5
6
|
require_relative 'aws_sdk_v3/create_table'
|
6
7
|
require_relative 'aws_sdk_v3/batch_get_item'
|
7
8
|
require_relative 'aws_sdk_v3/item_updater'
|
@@ -393,7 +394,7 @@ module Dynamoid
|
|
393
394
|
item = client.get_item(table_name: table_name,
|
394
395
|
key: key_stanza(table, key, range_key),
|
395
396
|
consistent_read: consistent_read)[:item]
|
396
|
-
item ?
|
397
|
+
item ? item_to_hash(item) : nil
|
397
398
|
end
|
398
399
|
|
399
400
|
# Edits an existing item's attributes, or adds a new item to the table if it does not already exist. You can put, delete, or add attribute values
|
@@ -422,7 +423,7 @@ module Dynamoid
|
|
422
423
|
attribute_updates: item_updater.attribute_updates,
|
423
424
|
expected: expected_stanza(conditions),
|
424
425
|
return_values: 'ALL_NEW')
|
425
|
-
|
426
|
+
item_to_hash(result[:attributes])
|
426
427
|
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
|
427
428
|
raise Dynamoid::Errors::ConditionalCheckFailedException, e
|
428
429
|
end
|
@@ -489,7 +490,7 @@ module Dynamoid
|
|
489
490
|
|
490
491
|
Query.new(client, table, options).call.each do |page|
|
491
492
|
yielder.yield(
|
492
|
-
page.items.map { |
|
493
|
+
page.items.map { |item| item_to_hash(item) },
|
493
494
|
last_evaluated_key: page.last_evaluated_key
|
494
495
|
)
|
495
496
|
end
|
@@ -522,7 +523,7 @@ module Dynamoid
|
|
522
523
|
|
523
524
|
Scan.new(client, table, conditions, options).call.each do |page|
|
524
525
|
yielder.yield(
|
525
|
-
page.items.map { |
|
526
|
+
page.items.map { |item| item_to_hash(item) },
|
526
527
|
last_evaluated_key: page.last_evaluated_key
|
527
528
|
)
|
528
529
|
end
|
@@ -560,6 +561,28 @@ module Dynamoid
|
|
560
561
|
describe_table(table_name, true).item_count
|
561
562
|
end
|
562
563
|
|
564
|
+
# Run PartiQL query.
|
565
|
+
#
|
566
|
+
# Dynamoid.adapter.execute("SELECT * FROM users WHERE id = ?", ["758"])
|
567
|
+
#
|
568
|
+
# @param [String] statement PartiQL statement
|
569
|
+
# @param [Array] parameters a list of bind parameters
|
570
|
+
# @param [Hash] options
|
571
|
+
# @option [Boolean] consistent_read
|
572
|
+
# @return [[] | Array[Hash] | Enumerator::Lazy[Hash]] items when used a SELECT statement and empty Array otherwise
|
573
|
+
#
|
574
|
+
def execute(statement, parameters = [], options = {})
|
575
|
+
items = ExecuteStatement.new(client, statement, parameters, options).call
|
576
|
+
|
577
|
+
if items.is_a?(Array)
|
578
|
+
items
|
579
|
+
else
|
580
|
+
items.lazy.flat_map { |array| array }
|
581
|
+
end
|
582
|
+
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
583
|
+
[]
|
584
|
+
end
|
585
|
+
|
563
586
|
protected
|
564
587
|
|
565
588
|
#
|
@@ -605,10 +628,8 @@ module Dynamoid
|
|
605
628
|
#
|
606
629
|
# Converts a hash returned by get_item, scan, etc. into a key-value hash
|
607
630
|
#
|
608
|
-
def
|
609
|
-
|
610
|
-
item.each { |k, v| r[k.to_sym] = v }
|
611
|
-
end
|
631
|
+
def item_to_hash(hash)
|
632
|
+
hash.symbolize_keys
|
612
633
|
end
|
613
634
|
|
614
635
|
def sanitize_item(attributes)
|
data/lib/dynamoid/components.rb
CHANGED
@@ -11,10 +11,9 @@ module Dynamoid
|
|
11
11
|
extend ActiveModel::Translation
|
12
12
|
extend ActiveModel::Callbacks
|
13
13
|
|
14
|
-
define_model_callbacks :create, :save, :destroy, :
|
14
|
+
define_model_callbacks :create, :save, :destroy, :update
|
15
|
+
define_model_callbacks :initialize, :find, :touch, only: :after
|
15
16
|
|
16
|
-
before_create :set_created_at
|
17
|
-
before_save :set_updated_at
|
18
17
|
before_save :set_expires_field
|
19
18
|
after_initialize :set_inheritance_field
|
20
19
|
end
|