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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e1643f808e93716814d86080198b41cdf26991d9e75614f7d877561590b23d9
4
- data.tar.gz: 453bb6e2c52d73cc94db42ba550fc621524ecba4cc4703445a00977d013b5c9a
3
+ metadata.gz: aedb91a28972fd9357cbb260341e617c3d0d35ae05a620d5ac8312f5d3edbf96
4
+ data.tar.gz: a026d8c3d1e53f4e6800dcffe2b8350da50f70d953a19ae86dfeff0f65555c8c
5
5
  SHA512:
6
- metadata.gz: 3cc09e208f6d1be1bf953efda001cb006fa8a752830e192a418ba449807163761427dd443fac32ee3cb34cf690e613c3ff1e34a3cf0515d50f609319b71c69e7
7
- data.tar.gz: 8ded4ae6a694788719dfc152adb6890f5cd18ff38acf99e422b824ed28c2d010e54caf05016af230d3cb49e5900618ced303148ebbc6ea5dce391dc838c278b2
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
- [![Build Status](https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml/badge.svg?branch=master)
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]
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
- region: 'us-west-2',
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
- region: region,
101
- access_key_id: key,
102
- secret_access_key: secret,
103
- role_arn: role_arn,
104
- role_session_name: 'our-session'
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
- config.credentials = credentials
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.0, JRuby 9.2.x and against Rails versions: 4.2, 5.0, 5.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,
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(serialized_str)
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(money_serialized_str)
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, callbacks are
554
- defined on `save`, `update`, `destroy`, which allows you to do `before_`
555
- 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:
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 { |address| address.do_some_work; sleep(0.01) }
800
- 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
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: 10212)
853
- Address.where('latitude.gt': 10212)
854
- Address.where('latitude.lt': 10212)
855
- Address.where('latitude.gte': 10212)
856
- 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)
857
862
  Address.where('city.begins_with': 'Lon')
858
- Address.where('latitude.between': [10212, 20000])
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': ['London', 'Edenburg', 'Birmingham'])
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
- [semver]: http://semver.org/
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
@@ -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) ? 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
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 ? result_item_to_hash(item) : nil
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
- result_item_to_hash(result[:attributes])
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 { |row| result_item_to_hash(row) },
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 { |row| result_item_to_hash(row) },
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 result_item_to_hash(item)
609
- {}.tap do |r|
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)
@@ -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, :initialize, :update
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