dynamoid 3.8.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
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