dynamoid 3.8.0 → 3.10.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -3
  3. data/README.md +111 -60
  4. data/SECURITY.md +17 -0
  5. data/dynamoid.gemspec +65 -0
  6. data/lib/dynamoid/adapter.rb +20 -13
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +78 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +28 -2
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +38 -0
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +33 -27
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +116 -70
  16. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  17. data/lib/dynamoid/associations.rb +1 -1
  18. data/lib/dynamoid/components.rb +2 -3
  19. data/lib/dynamoid/config/options.rb +12 -12
  20. data/lib/dynamoid/config.rb +1 -0
  21. data/lib/dynamoid/criteria/chain.rb +101 -138
  22. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  23. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  24. data/lib/dynamoid/criteria/where_conditions.rb +29 -0
  25. data/lib/dynamoid/dirty.rb +57 -57
  26. data/lib/dynamoid/document.rb +39 -3
  27. data/lib/dynamoid/dumping.rb +2 -2
  28. data/lib/dynamoid/errors.rb +2 -0
  29. data/lib/dynamoid/fields/declare.rb +6 -6
  30. data/lib/dynamoid/fields.rb +9 -27
  31. data/lib/dynamoid/finders.rb +26 -30
  32. data/lib/dynamoid/indexes.rb +7 -10
  33. data/lib/dynamoid/loadable.rb +2 -2
  34. data/lib/dynamoid/log/formatter.rb +19 -4
  35. data/lib/dynamoid/persistence/import.rb +4 -1
  36. data/lib/dynamoid/persistence/inc.rb +66 -0
  37. data/lib/dynamoid/persistence/save.rb +55 -12
  38. data/lib/dynamoid/persistence/update_fields.rb +2 -2
  39. data/lib/dynamoid/persistence/update_validations.rb +2 -2
  40. data/lib/dynamoid/persistence.rb +128 -48
  41. data/lib/dynamoid/type_casting.rb +15 -14
  42. data/lib/dynamoid/undumping.rb +1 -1
  43. data/lib/dynamoid/version.rb +1 -1
  44. metadata +27 -49
  45. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  46. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e1643f808e93716814d86080198b41cdf26991d9e75614f7d877561590b23d9
4
- data.tar.gz: 453bb6e2c52d73cc94db42ba550fc621524ecba4cc4703445a00977d013b5c9a
3
+ metadata.gz: c2cc1feec6853b756fff467fa687cf2eaa3a2dc5f0a88dac948e5a92a4aa1073
4
+ data.tar.gz: 7165e081fa9979c45e7402265138e62cb6ee3c1f6f594ab6949c1f984621bc89
5
5
  SHA512:
6
- metadata.gz: 3cc09e208f6d1be1bf953efda001cb006fa8a752830e192a418ba449807163761427dd443fac32ee3cb34cf690e613c3ff1e34a3cf0515d50f609319b71c69e7
7
- data.tar.gz: 8ded4ae6a694788719dfc152adb6890f5cd18ff38acf99e422b824ed28c2d010e54caf05016af230d3cb49e5900618ced303148ebbc6ea5dce391dc838c278b2
6
+ metadata.gz: e18700ff74b9466e1ec166706446a1dca5248c6b8c33365e469c74e9a67b92f4afc9cacb0a06c2698d5165f4b68a93cdd214b83f5e3b749b92bcaff06fbc7628
7
+ data.tar.gz: 9b18fb2522f90eb3cf9b0b6014ca24beb08bcdebf403c57487222c46ec71d306f4e85e686aa89607c77ad7d994359a934537e8d22202b145bd478c0862bf497b
data/CHANGELOG.md CHANGED
@@ -5,15 +5,66 @@ 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
- ### Fixed
9
8
 
9
+ ### Fixed
10
10
  ### Added
11
+ ### Changed
12
+ ### Removed
11
13
 
14
+ ## 3.10.0
15
+ ### Fixed
16
+ * [#681](https://github.com/Dynamoid/dynamoid/pull/681) Fixed saving persisted model and deleting attributes with `nil` value if `config.store_attribute_with_nil_value` is `false`
17
+ * [#716](https://github.com/Dynamoid/dynamoid/pull/716), [#691](https://github.com/Dynamoid/dynamoid/pull/691), [#687](https://github.com/Dynamoid/dynamoid/pull/687), [#660](https://github.com/Dynamoid/dynamoid/pull/660) Numerous fixes in README.md and RDoc documentation (@ndjndj, @kiharito, @dunkOnIT)
18
+ ### Added
19
+ * [#656](https://github.com/Dynamoid/dynamoid/pull/656) Added a `create_table_on_save` configuration flag to create table on save (@imaximix)
20
+ * [#697](https://github.com/Dynamoid/dynamoid/pull/697) Ensure Ruby 3.3 and Rails 7.1 versions are supported and added them on CI
12
21
  ### Changed
22
+ * [#655](https://github.com/Dynamoid/dynamoid/pull/655) Support multiple `where` in the same chain with multiple conditions for the same field
13
23
 
14
- ### Removed
24
+ ## 3.9.0 / 2023-04-13
25
+ ### Fixed
26
+ * [#610](https://github.com/Dynamoid/dynamoid/pull/610) Specs in JRuby; Support for JRuby 9.4.0.0 (@pboling)
27
+ * [#624](https://github.com/Dynamoid/dynamoid/pull/624) Fixed `#increment!`/`#decrement!` methods and made them compatible with Rails counterparts
28
+ * [#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
29
+ * [#628](https://github.com/Dynamoid/dynamoid/pull/628) Fixed `.import` method to mark persisted model attributes as not changed/not dirty
30
+ * [#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)
31
+ * [#634](https://github.com/Dynamoid/dynamoid/pull/634) Fixed model callbacks:
32
+ * changed order of `save` and `create`/`update` callbacks - `save` callbacks are outer for the `create`/`update` ones
33
+ * removed `before_initialize` and `around_initialize` callbacks - there should be only `after_initialize` one
34
+ * [#634](https://github.com/Dynamoid/dynamoid/pull/634) Fixed `#touch` method compatibility with a Rails counterpart:
35
+ * don't save other modified attributes - only timestamps
36
+ * don't perform validation and don't call `save`/`create`/`update` callbacks
37
+ * accept a list of attribute names, but not one name
38
+ * accept a `:time` option
39
+ ### Added
40
+ * [#611](https://github.com/Dynamoid/dynamoid/pull/611) Add `rubocop-md` (@pboling)
41
+ * [#612](https://github.com/Dynamoid/dynamoid/pull/612) Add `rubocop-rspec` (@pboling)
42
+ * [#613](https://github.com/Dynamoid/dynamoid/pull/613) Add `rubocop-performance` and `rubocop-rake` (@pboling)
43
+ * Added `funding_uri` set to open collective: https://opencollective.com/dynamoid
44
+ * Added `required_ruby_version` as `>= 2.3.0` (which was already the minimum supported version of Ruby)
45
+ * [#616](https://github.com/Dynamoid/dynamoid/pull/616) Upgrade `simplecov` (& remove `coveralls`) (@pboling)
46
+ * Setup GitHub actions for Code Coverage
47
+ * Setup GitHub actions for RuboCop linting
48
+ * Automate coverage feedback on Pull Requests via GitHub Actions and CodeCov
49
+ * [#618](https://github.com/Dynamoid/dynamoid/pull/618) Upgrade README Badges (@pboling)
50
+ * [#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`
51
+ * [#627](https://github.com/Dynamoid/dynamoid/pull/627) Made the following methods in the Dirty API public (to comply with Rails):
52
+ * `clear_changes_information`
53
+ * `changes_applied`
54
+ * `clear_attribute_changes`
55
+ * [#630](https://github.com/Dynamoid/dynamoid/pull/630) Added `Dynamoid::Adapter#execute` method to run PartiQL queries
56
+ * [#634](https://github.com/Dynamoid/dynamoid/pull/634) Added `after_touch` callback and run it in the following methods:
57
+ * `#touch`
58
+ * `#increment!`
59
+ * `#decrement!`
60
+ * [#642](https://github.com/Dynamoid/dynamoid/pull/642) Run specs on CI against Ruby 3.2
61
+ * [#645](https://github.com/Dynamoid/dynamoid/pull/645) Added `after_find` callback
62
+ ### Changed
63
+ * [#610](https://github.com/Dynamoid/dynamoid/pull/610) Switch to [`rubocop-lts`](https://rubocop-lts.gitlab.io/) (@pboling)
64
+ * [#633](https://github.com/Dynamoid/dynamoid/pull/633) Change `#inspect` method to return only attributes
65
+ * [#623](https://github.com/Dynamoid/dynamoid/pull/623) Optimized performance of persisting to send only changed attributes in a request to DynamoDB
15
66
 
16
- ## 3.8.0
67
+ ## 3.8.0 / 2022-11-09
17
68
  ### Fixed
18
69
  * [#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)
19
70
  * Minor changes in the documentation:
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
@@ -70,10 +67,10 @@ For example, to configure AWS access:
70
67
  Create `config/initializers/aws.rb` as follows:
71
68
 
72
69
  ```ruby
73
- Aws.config.update({
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
- 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,8 +132,8 @@ 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,
139
- 5.2, 6.0, 6.1 and 7.0.
135
+ 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and 3.3, JRuby 9.4.x and against Rails versions: 4.2, 5.0, 5.1,
136
+ 5.2, 6.0, 6.1, 7.0 and 7.1.
140
137
 
141
138
  ## Setup
142
139
 
@@ -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
@@ -718,18 +723,6 @@ join, but instead finds all the user's addresses and naively filters
718
723
  them in Ruby. For large associations this is a performance hit compared
719
724
  to relational database engines.
720
725
 
721
- **WARNING:** There is a limitation of conditions passed to `where`
722
- method. Only one condition for some particular field could be specified.
723
- The last one only will be applied and others will be ignored. E.g. in
724
- examples:
725
-
726
- ```ruby
727
- User.where('age.gt': 10, 'age.lt': 20)
728
- User.where(name: 'Mike').where('name.begins_with': 'Ed')
729
- ```
730
-
731
- the first one will be ignored and the last one will be used.
732
-
733
726
  **Warning:** There is a caveat with filtering documents by `nil` value
734
727
  attribute. By default Dynamoid ignores attributes with `nil` value and
735
728
  doesn't store them in a DynamoDB document. This behavior could be
@@ -747,7 +740,7 @@ If Dynamoid keeps `nil` value attributes `eq`/`ne` operators should be
747
740
  used instead:
748
741
 
749
742
  ```ruby
750
- Address.where('postcode': nil)
743
+ Address.where(postcode: nil)
751
744
  Address.where('postcode.ne': nil)
752
745
  ```
753
746
 
@@ -796,8 +789,8 @@ for requesting documents in batches:
796
789
 
797
790
  ```ruby
798
791
  # 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
792
+ Address.batch(100).each { |addr| addr.do_some_work && sleep(0.01) }
793
+ Address.record_limit(10_000).batch(100).each { |addr| addr.do_some_work && sleep(0.01) } # Batch specified as part of a chain
801
794
  ```
802
795
 
803
796
  The implication of batches is that the underlying requests are done in
@@ -849,13 +842,13 @@ operators are available: `gt`, `lt`, `gte`, `lte`, `begins_with`,
849
842
  `between` as well as equality:
850
843
 
851
844
  ```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)
845
+ Address.where(latitude: 10_212)
846
+ Address.where('latitude.gt': 10_212)
847
+ Address.where('latitude.lt': 10_212)
848
+ Address.where('latitude.gte': 10_212)
849
+ Address.where('latitude.lte': 10_212)
857
850
  Address.where('city.begins_with': 'Lon')
858
- Address.where('latitude.between': [10212, 20000])
851
+ Address.where('latitude.between': [10_212, 20_000])
859
852
  ```
860
853
 
861
854
  You are able to filter results on the DynamoDB side and specify
@@ -863,7 +856,7 @@ conditions for non-key fields. Following additional operators are
863
856
  available: `in`, `contains`, `not_contains`, `null`, `not_null`:
864
857
 
865
858
  ```ruby
866
- Address.where('city.in': ['London', 'Edenburg', 'Birmingham'])
859
+ Address.where('city.in': %w[London Edenburg Birmingham])
867
860
  Address.where('city.contains': ['on'])
868
861
  Address.where('city.not_contains': ['ing'])
869
862
  Address.where('postcode.null': false)
@@ -921,8 +914,8 @@ If you have a range index, Dynamoid provides a number of additional
921
914
  other convenience methods to make your life a little easier:
922
915
 
923
916
  ```ruby
924
- User.where("created_at.gt": DateTime.now - 1.day).all
925
- User.where("created_at.lt": DateTime.now - 1.day).all
917
+ User.where('created_at.gt': DateTime.now - 1.day).all
918
+ User.where('created_at.lt': DateTime.now - 1.day).all
926
919
  ```
927
920
 
928
921
  It also supports `gte` and `lte`. Turning those into symbols and
@@ -965,6 +958,14 @@ Address.upsert(id, city: 'Chicago')
965
958
  Address.upsert(id, { city: 'Chicago' }, if: { deliverable: true })
966
959
  ```
967
960
 
961
+ By default, `#upsert` will update all attributes of the document if it already exists.
962
+ To idempotently create-but-not-update a record, apply the `unless_exists` condition
963
+ to its keys when you upsert.
964
+
965
+ ```ruby
966
+ Address.upsert(id, { city: 'Chicago' }, { unless_exists: [:id] })
967
+ ```
968
+
968
969
  ### Deleting
969
970
 
970
971
  In order to delete some items `delete_all` method should be used. Any
@@ -1046,6 +1047,21 @@ resolving the fields with a second query against the table since a query
1046
1047
  against GSI then a query on base table is still likely faster than scan
1047
1048
  on the base table*
1048
1049
 
1050
+ ### PartiQL
1051
+
1052
+ To run PartiQL statements `Dynamoid.adapter.execute` method should be
1053
+ used:
1054
+
1055
+ ```ruby
1056
+ Dynamoid.adapter.execute("UPDATE users SET name = 'Mike' WHERE id = '1'")
1057
+ ```
1058
+
1059
+ Parameters are also supported:
1060
+
1061
+ ```ruby
1062
+ Dynamoid.adapter.execute('SELECT * FROM users WHERE id = ?', ['1'])
1063
+ ```
1064
+
1049
1065
  ## Configuration
1050
1066
 
1051
1067
  Listed below are all configuration options.
@@ -1116,12 +1132,12 @@ Listed below are all configuration options.
1116
1132
  in ISO 8601 string format. Default is `false`
1117
1133
  * `store_boolean_as_native` - if `true` Dynamoid stores boolean fields
1118
1134
  as native DynamoDB boolean values. Otherwise boolean fields are stored
1119
- as string values `'t'` and `'f'`. Default is true
1135
+ as string values `'t'` and `'f'`. Default is `true`
1120
1136
  * `backoff` - is a hash: key is a backoff strategy (symbol), value is
1121
1137
  parameters for the strategy. Is used in batch operations. Default id
1122
1138
  `nil`
1123
1139
  * `backoff_strategies`: is a hash and contains all available strategies.
1124
- Default is { constant: ..., exponential: ...}
1140
+ Default is `{ constant: ..., exponential: ...}`
1125
1141
  * `log_formatter`: overrides default AWS SDK formatter. There are
1126
1142
  several canned formatters: `Aws::Log::Formatter.default`,
1127
1143
  `Aws::Log::Formatter.colored` and `Aws::Log::Formatter.short`. Please
@@ -1139,6 +1155,9 @@ Listed below are all configuration options.
1139
1155
  * `http_read_timeout`:The number of seconds to wait for HTTP response
1140
1156
  data. Default option value is `nil`. If not specified effected value
1141
1157
  is `60`
1158
+ * `create_table_on_save`: if `true` then Dynamoid creates a
1159
+ corresponding table in DynamoDB at model persisting if the table
1160
+ doesn't exist yet. Default is `true`
1142
1161
 
1143
1162
 
1144
1163
  ## Concurrency
@@ -1277,11 +1296,18 @@ RSpec.configure do |config|
1277
1296
  end
1278
1297
  ```
1279
1298
 
1299
+ In addition, the first test for each model may fail if the relevant models are not included in `included_models`. This can be fixed by adding this line before the `DynamoidReset` module:
1300
+ ```ruby
1301
+ Dir[File.join(Dynamoid::Config.models_dir, '**/*.rb')].sort.each { |file| require file }
1302
+ ```
1303
+ Note that this will require _all_ models in your models folder - you can also explicitly require only certain models if you would prefer to.
1304
+
1280
1305
  In Rails, you may also want to ensure you do not delete non-test data
1281
1306
  accidentally by adding the following to your test environment setup:
1282
1307
 
1283
1308
  ```ruby
1284
1309
  raise "Tests should be run in 'test' environment only" if Rails.env != 'test'
1310
+
1285
1311
  Dynamoid.configure do |config|
1286
1312
  config.namespace = "#{Rails.application.railtie_name}_#{Rails.env}"
1287
1313
  end
@@ -1406,4 +1432,29 @@ See [LICENSE][license] for the official [Copyright Notice][copyright-notice-expl
1406
1432
 
1407
1433
  [security]: https://github.com/Dynamoid/dynamoid/blob/master/SECURITY.md
1408
1434
 
1409
- [semver]: http://semver.org/
1435
+ [⛳️gem]: https://rubygems.org/gems/dynamoid
1436
+ [⛳️version-img]: http://img.shields.io/gem/v/dynamoid.svg
1437
+ [⛳cclim-maint]: https://codeclimate.com/github/Dynamoid/dynamoid/maintainability
1438
+ [⛳cclim-maint-img♻️]: https://api.codeclimate.com/v1/badges/27fd8b6b7ff338fa4914/maintainability
1439
+ [🏘coveralls]: https://coveralls.io/github/Dynamoid/dynamoid?branch=master
1440
+ [🏘coveralls-img]: https://coveralls.io/repos/github/Dynamoid/dynamoid/badge.svg?branch=master
1441
+ [🖇codecov]: https://codecov.io/gh/Dynamoid/dynamoid
1442
+ [🖇codecov-img♻️]: https://codecov.io/gh/Dynamoid/dynamoid/branch/master/graph/badge.svg?token=84WeeoxaN9
1443
+ [🖇src-license]: https://github.com/Dynamoid/dynamoid/blob/master/LICENSE.txt
1444
+ [🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg
1445
+ [🖐gitmoji]: https://gitmoji.dev
1446
+ [🖐gitmoji-img]: https://img.shields.io/badge/gitmoji-3.9.0-FFDD67.svg?style=flat
1447
+ [🚎yard]: https://www.rubydoc.info/gems/dynamoid
1448
+ [🚎yard-img]: https://img.shields.io/badge/yard-docs-blue.svg?style=flat
1449
+ [🧮semver]: http://semver.org/
1450
+ [🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat
1451
+ [🖐contributors]: https://github.com/Dynamoid/dynamoid/graphs/contributors
1452
+ [🖐contributors-img]: https://img.shields.io/github/contributors-anon/Dynamoid/dynamoid
1453
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
1454
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat
1455
+ [🖇sponsor-img]: https://img.shields.io/opencollective/all/dynamoid
1456
+ [🖇sponsor]: https://opencollective.com/dynamoid
1457
+ [🖇triage-help]: https://www.codetriage.com/dynamoid/dynamoid
1458
+ [🖇triage-help-img]: https://www.codetriage.com/dynamoid/dynamoid/badges/users.svg
1459
+ [🏘sup-wf]: https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml?query=branch%3Amaster
1460
+ [🏘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,65 @@
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 'rexml'
63
+ spec.add_development_dependency 'rspec', '~> 3.12'
64
+ spec.add_development_dependency 'yard'
65
+ 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
 
@@ -170,19 +171,25 @@ module Dynamoid
170
171
  # only really useful for range queries, since it can only find by one hash key at once. Only provide
171
172
  # one range key to the hash.
172
173
  #
174
+ # Dynamoid.adapter.query('users', { id: [[:eq, '1']], age: [[:between, [10, 30]]] }, { batch_size: 1000 })
175
+ #
173
176
  # @param [String] table_name the name of the table
174
- # @param [Hash] opts the options to query the table with
175
- # @option opts [String] :hash_value the value of the hash key to find
176
- # @option opts [Range] :range_value find the range key within this range
177
- # @option opts [Number] :range_greater_than find range keys greater than this
178
- # @option opts [Number] :range_less_than find range keys less than this
179
- # @option opts [Number] :range_gte find range keys greater than or equal to this
180
- # @option opts [Number] :range_lte find range keys less than or equal to this
181
- #
182
- # @return [Array] an array of all matching items
183
- #
184
- def query(table_name, opts = {})
185
- adapter.query(table_name, opts)
177
+ # @param [Array[Array]] key_conditions conditions for the primary key attributes
178
+ # @param [Array[Array]] non_key_conditions (optional) conditions for non-primary key attributes
179
+ # @param [Hash] options (optional) the options to query the table with
180
+ # @option options [Boolean] :consistent_read You can set the ConsistentRead parameter to true and obtain a strongly consistent result
181
+ # @option options [Boolean] :scan_index_forward Specifies the order for index traversal: If true (default), the traversal is performed in ascending order; if false, the traversal is performed in descending order.
182
+ # @option options [Symbop] :select The attributes to be returned in the result (one of ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, ...)
183
+ # @option options [Symbol] :index_name The name of an index to query. This index can be any local secondary index or global secondary index on the table.
184
+ # @option options [Hash] :exclusive_start_key The primary key of the first item that this operation will evaluate.
185
+ # @option options [Integer] :batch_size The number of items to lazily load one by one
186
+ # @option options [Integer] :record_limit The maximum number of items to return (not necessarily the number of evaluated items)
187
+ # @option options [Integer] :scan_limit The maximum number of items to evaluate (not necessarily the number of matching items)
188
+ # @option options [Array[Symbol]] :project The attributes to retrieve from the table
189
+ #
190
+ # @return [Enumerable] matching items
191
+ def query(table_name, key_conditions, non_key_conditions = {}, options = {})
192
+ adapter.query(table_name, key_conditions, non_key_conditions, options)
186
193
  end
187
194
 
188
195
  def self.adapter_plugin_class
@@ -29,7 +29,7 @@ module Dynamoid
29
29
  gs_indexes = options[:global_secondary_indexes]
30
30
 
31
31
  key_schema = {
32
- hash_key_schema: { key => (options[:hash_key_type] || :string) },
32
+ hash_key_schema: { key => options[:hash_key_type] || :string },
33
33
  range_key_schema: options[:range_key]
34
34
  }
35
35
  attribute_definitions = build_all_attribute_definitions(
@@ -69,7 +69,7 @@ module Dynamoid
69
69
  end
70
70
  end
71
71
  resp = client.create_table(client_opts)
72
- options[:sync] = true if !options.key?(:sync) && ls_indexes.present? || gs_indexes.present?
72
+ options[:sync] = true if (!options.key?(:sync) && ls_indexes.present?) || gs_indexes.present?
73
73
 
74
74
  if options[:sync]
75
75
  status = PARSE_TABLE_STATUS.call(resp, :table_description)
@@ -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