dynamoid 3.8.0 → 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 +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
|