dynamoid 3.7.1 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +246 -244
  3. data/LICENSE.txt +2 -2
  4. data/README.md +134 -55
  5. data/SECURITY.md +17 -0
  6. data/dynamoid.gemspec +66 -0
  7. data/lib/dynamoid/adapter.rb +7 -9
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +2 -2
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -15
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +73 -59
  13. data/lib/dynamoid/associations/single_association.rb +28 -9
  14. data/lib/dynamoid/components.rb +2 -3
  15. data/lib/dynamoid/criteria/chain.rb +13 -9
  16. data/lib/dynamoid/criteria.rb +6 -7
  17. data/lib/dynamoid/dirty.rb +60 -63
  18. data/lib/dynamoid/document.rb +42 -12
  19. data/lib/dynamoid/errors.rb +2 -0
  20. data/lib/dynamoid/fields.rb +19 -37
  21. data/lib/dynamoid/finders.rb +9 -4
  22. data/lib/dynamoid/indexes.rb +45 -40
  23. data/lib/dynamoid/loadable.rb +6 -1
  24. data/lib/dynamoid/log/formatter.rb +19 -4
  25. data/lib/dynamoid/persistence/import.rb +4 -1
  26. data/lib/dynamoid/persistence/inc.rb +66 -0
  27. data/lib/dynamoid/persistence/save.rb +52 -5
  28. data/lib/dynamoid/persistence/update_fields.rb +1 -1
  29. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  30. data/lib/dynamoid/persistence/upsert.rb +1 -1
  31. data/lib/dynamoid/persistence.rb +149 -61
  32. data/lib/dynamoid/undumping.rb +18 -0
  33. data/lib/dynamoid/validations.rb +6 -0
  34. data/lib/dynamoid/version.rb +1 -1
  35. data/lib/dynamoid.rb +1 -0
  36. 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 - 2018 Dynamoid, https://github.com/Dynamoid
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
- [![Build Status](https://travis-ci.org/Dynamoid/dynamoid.svg?branch=master)](https://travis-ci.org/Dynamoid/dynamoid)
4
- [![Code Climate](https://codeclimate.com/github/Dynamoid/dynamoid.svg)](https://codeclimate.com/github/Dynamoid/dynamoid)
5
- [![Coverage Status](https://coveralls.io/repos/github/Dynamoid/dynamoid/badge.svg?branch=master)](https://coveralls.io/github/Dynamoid/dynamoid?branch=master)
6
- [![CodeTriage Helpers](https://www.codetriage.com/dynamoid/dynamoid/badges/users.svg)](https://www.codetriage.com/dynamoid/dynamoid)
7
- [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/Dynamoid/dynamoid/frames)
8
- [![Inline docs](http://inch-ci.org/github/Dynamoid/Dynamoid.svg?branch=master)](http://inch-ci.org/github/Dynamoid/Dynamoid)
9
- ![GitHub](https://img.shields.io/github/license/Dynamoid/dynamoid.svg)
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
- region: 'us-west-2',
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
- region: region,
92
- access_key_id: key,
93
- secret_access_key: secret,
94
- role_arn: role_arn,
95
- role_session_name: 'our-session'
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
- config.credentials = credentials
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.0, JRuby 9.2.x and against Rails versions: 4.2, 5.0, 5.1,
130
- 5.2, 6.0 and 6.1.
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(serialized_str)
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(money_serialized_str)
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, callbacks are
541
- defined on `save`, `update`, `destroy`, which allows you to do `before_`
542
- or `after_` any of those.
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 { |address| address.do_some_work; sleep(0.01) }
787
- Address.record_limit(10_000).batch(100).each { } # Batch specified as part of a chain
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: 10212)
840
- Address.where('latitude.gt': 10212)
841
- Address.where('latitude.lt': 10212)
842
- Address.where('latitude.gte': 10212)
843
- Address.where('latitude.lte': 10212)
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': [10212, 20000])
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': ['London', 'Edenburg', 'Birmingham'])
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 for model creation and updating. You can disable this
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 want to run all the specs that travis runs, use `bundle exec
1364
- wwtd`, but first you will need to setup all the rubies, for each of `%w(
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
- rvm use 3.0.0
1371
- gem install rubygems-update
1372
- gem install bundler
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
- ## Copyright
1377
-
1378
- Copyright (c) 2012 Josh Symonds.
1379
-
1380
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1381
-
1382
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1383
-
1384
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # require only 'concurrent/atom' once this issue is resolved:
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, can also be a string of just one id
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, can also be a string of just one id
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.to_s, *args) { adapter.send(m, *args, &blk) }
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 [hash_key, composite_key]. Otherwise just return hash key's
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
- # to remove
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
- @deletions.merge!(sanitize_attributes(values))
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 to_h
52
- ret = {}
59
+ def attribute_updates
60
+ result = {}
53
61
 
54
62
  @additions.each do |k, v|
55
- ret[k.to_s] = {
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
- ret[k.to_s] = {
70
+ result[k] = {
62
71
  action: DELETE
63
72
  }
64
- ret[k.to_s][:value] = v unless v.nil?
73
+ result[k][:value] = v unless v.nil?
65
74
  end
75
+
66
76
  @updates.each do |k, v|
67
- ret[k.to_s] = {
77
+ result[k] = {
68
78
  action: PUT,
69
79
  value: v
70
80
  }
71
81
  end
72
82
 
73
- ret
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) ? v.stringify_keys : v
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.