dynamoid 3.11.0 → 3.13.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -3
  3. data/README.md +94 -14
  4. data/SECURITY.md +6 -6
  5. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +3 -1
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +1 -1
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +4 -1
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +4 -1
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +13 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +24 -9
  11. data/lib/dynamoid/config.rb +1 -0
  12. data/lib/dynamoid/criteria/chain.rb +11 -3
  13. data/lib/dynamoid/dirty.rb +22 -11
  14. data/lib/dynamoid/dumping.rb +3 -3
  15. data/lib/dynamoid/errors.rb +16 -1
  16. data/lib/dynamoid/fields/declare.rb +1 -1
  17. data/lib/dynamoid/fields.rb +44 -4
  18. data/lib/dynamoid/finders.rb +44 -19
  19. data/lib/dynamoid/persistence/inc.rb +30 -13
  20. data/lib/dynamoid/persistence/save.rb +24 -12
  21. data/lib/dynamoid/persistence/update_fields.rb +18 -5
  22. data/lib/dynamoid/persistence/update_validations.rb +3 -3
  23. data/lib/dynamoid/persistence/upsert.rb +17 -4
  24. data/lib/dynamoid/persistence.rb +273 -19
  25. data/lib/dynamoid/transaction_read/find.rb +137 -0
  26. data/lib/dynamoid/transaction_read.rb +146 -0
  27. data/lib/dynamoid/transaction_write/base.rb +12 -0
  28. data/lib/dynamoid/transaction_write/delete_with_instance.rb +7 -2
  29. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +7 -2
  30. data/lib/dynamoid/transaction_write/destroy.rb +10 -5
  31. data/lib/dynamoid/transaction_write/item_updater.rb +60 -0
  32. data/lib/dynamoid/transaction_write/save.rb +22 -9
  33. data/lib/dynamoid/transaction_write/update_fields.rb +176 -31
  34. data/lib/dynamoid/transaction_write/upsert.rb +23 -6
  35. data/lib/dynamoid/transaction_write.rb +212 -3
  36. data/lib/dynamoid/validations.rb +15 -4
  37. data/lib/dynamoid/version.rb +1 -1
  38. data/lib/dynamoid.rb +1 -0
  39. metadata +9 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e13af88750cfc2b8421710068215b18cf596f86b0c5caa207574535af04369cc
4
- data.tar.gz: b0cc179fb6c547fd766c788300bfe8fc38a9e10117895c0b39b665fcc609e448
3
+ metadata.gz: b115b0eb8a9244d3482f3b748edf42272f6d9ffa94db20c5c07c7d08a6a3cf6a
4
+ data.tar.gz: 60821b56861e74149f3751f0c58bbc58e6805a9b4db0a4b863527aad99748099
5
5
  SHA512:
6
- metadata.gz: 3b363e83838f36600b1af31c59406ac4fccad981e24af8762d5ae73fd1b648a2d03754483be89ed648f7ca982dc1640901af242c0b626ef9fb34b3002438edb4
7
- data.tar.gz: a1bc08e5ab754f0a2ee383d2ac335396e7e058ad3b91df2d7c532d346da79daa0d239053ab856a8dfd0a2bdfb213f70a654cecdd3b42901c0261e61bcaca9ad5
6
+ metadata.gz: 1eef7ad0569e01d06b8f52a273acff4621a23419bc7f83830dfb37a44149149ec41b010cfa96c4e81d51043b4c7154c7996c72d7e50b599b4ea969ff6bb7575f
7
+ data.tar.gz: ee7561403e8f0bfd2c0e29e2212c0cec903e264c83adc9d238d565054bc98f111d85b3e70e64e838d6c35a434d8b7ef3025eba347ccfee53d249a3834ce53dc0
data/CHANGELOG.md CHANGED
@@ -11,21 +11,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
  ### Changed
12
12
  ### Removed
13
13
 
14
- ## 3.11.0
14
+ ## 3.13.0
15
15
 
16
+ ### Fixed
17
+ * [#944](https://github.com/Dynamoid/dynamoid/pull/944) Fix `#delete` and `#destroy` methods and set `#destroyed?` properly when operations fail
18
+ * [#987](https://github.com/Dynamoid/dynamoid/pull/987) Fix checking that a primary key is given in transactional methods `#save` and `#destroy`
19
+ ### Added
20
+ * [#941](https://github.com/Dynamoid/dynamoid/pull/941) Support table ARN and add option `:arn` for the `table` method to specify a table belonged to specific AWS account
21
+ * [#943](https://github.com/Dynamoid/dynamoid/pull/943) Implement `delete` class method
22
+ * [#945](https://github.com/Dynamoid/dynamoid/pull/945) Implement `#update_attribute!` method
23
+ * [#947](https://github.com/Dynamoid/dynamoid/pull/947) Allow skipping default model fields generation and add option `:skip_generating_fields` for the `table` method to specify field names
24
+ * [#988](https://github.com/Dynamoid/dynamoid/pull/988) Add Ruby 4.0 and Rails 8.1 in CI
25
+ ### Changed
26
+ ### Removed
27
+
28
+ ## 3.12.0 / 2025-08-23
29
+
30
+ ### Fixed
31
+ * [#849](https://github.com/Dynamoid/dynamoid/pull/849) Fixed saving a field of custom type when it implements both `.dynamoid_dump()` and `#dynamoid_dump` method and use the former one.
32
+ A proper behaviour is to use adapter's .dynamoid_dump method instead of a value's #dynamoid_dump method.
33
+ * [#851](https://github.com/Dynamoid/dynamoid/pull/851) Fixed Dirty API and don't require custom types to implement `#==` method. Add field option `:comparable`.
34
+ * [#913](https://github.com/Dynamoid/dynamoid/pull/913) Fixed saving partition keys of Dynamoid-specific types (e.g. `date` or `datetime`) and always convert them into a proper DynamoDB type
35
+ * [#917](https://github.com/Dynamoid/dynamoid/pull/917) Fixed transactional write operations when primary key is of non-native DynamoDB type
36
+ * [#927](https://github.com/Dynamoid/dynamoid/pull/927) Multiple fixes in transactional and non-transactional write methods and finders:
37
+ * Changed non-transactional method `#find` and raise `MissingHashKey` when a given partition key is `nil`
38
+ * Fixed non-transactional method `#save!` and raise `RecordNotSaved` when a callback throws `:abort` and terminates the process
39
+ * Check whether partition and sort key are specified in the non-transactional persistence methods and raise a proper exception (`MissingHashKey` or `MissingRangeKey`)
40
+ * Fixed methods `#inc`, `#increment!` and `#decrement!`, `#update` and `#update!` to not create a new item when an item with specified primary key is already deleted
41
+ * Fixed method `#update_attribute` to not raise `StaleObjectError` when an item with specified primary key is already deleted
42
+ * Fixed method `#where` with condition on a `:serialized` field to not raise an exception
43
+ ### Added
44
+ * [#910](https://github.com/Dynamoid/dynamoid/pull/910) Added `key_type` option to the `table` method to change type of a partition key field (@ogidow)
45
+ * [#916](https://github.com/Dynamoid/dynamoid/pull/916) Added new `error_on_scan` config option to raises an error and prevent table scans (DynamoDB's `Scan` operation) (@aaronalberg)
46
+ * [#926](https://github.com/Dynamoid/dynamoid/pull/926) Implemented read transactions (using DynamoDB's `TransactGetItems` operation)
47
+ ### Changed
48
+ * [#846](https://github.com/Dynamoid/dynamoid/pull/846) Changed transactional `update_fields()` method to accept a block and support low-level operations `set`, `add`, `delete`, `remove` (similar to the non-transactional `#update()` method) (@ckhsponge)
49
+ ### Removed
50
+
51
+ ## 3.11.0 / 2025-01-12
16
52
  ### Fixed
17
53
  * [#829](https://github.com/Dynamoid/dynamoid/pull/829) Fixed saving of in-place field changes
18
54
  * [#812](https://github.com/Dynamoid/dynamoid/pull/812) Restored sanitizing of attribute names in `#where` conditions
19
55
  * [#721](https://github.com/Dynamoid/dynamoid/pull/721) Fixed code examples in README.md (@ndjndj)
20
56
  ### Added
21
- * [#688](https://github.com/Dynamoid/dynamoid/pull/688) Transactional modifying was added utilizing `TransactWriteItems` operation (@ckhsponge)
57
+ * [#688](https://github.com/Dynamoid/dynamoid/pull/688) Implemented write transactions (using DynamoDB's `TransactWriteItems` operation) (@ckhsponge)
22
58
  * [#794](https://github.com/Dynamoid/dynamoid/pull/794) Added new config option `store_empty_string_as_nil`
23
59
  * [#828](https://github.com/Dynamoid/dynamoid/pull/828) Added Ruby 3.4, Rails 8.0 and Rails 7.2 in CI
24
60
  ### Changed
25
61
  * [#832](https://github.com/Dynamoid/dynamoid/pull/832) Support String condition expressions with `#where`
26
62
  * [#822](https://github.com/Dynamoid/dynamoid/pull/822) Support binary type natively. Also added new config option `store_binary_as_native` (@dalibor)
27
63
 
28
- ## 3.10.0
64
+ ## 3.10.0 / 2024-02-10
29
65
  ### Fixed
30
66
  * [#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`
31
67
  * [#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)
data/README.md CHANGED
@@ -24,8 +24,8 @@ DynamoDB is not like other document-based databases you might know, and
24
24
  is very different indeed from relational databases. It sacrifices
25
25
  anything beyond the simplest relational queries and transactional
26
26
  support to provide a fast, cost-efficient, and highly durable storage
27
- solution. If your database requires complicated relational queries and
28
- transaction support, then this modest Gem cannot provide them for you,
27
+ solution. If your database requires complicated relational queries
28
+ then this modest Gem cannot provide them for you
29
29
  and neither can DynamoDB. In those cases you would do better to look
30
30
  elsewhere for your database needs.
31
31
 
@@ -102,8 +102,8 @@ credentials = Aws::AssumeRoleCredentials.new(
102
102
  )
103
103
 
104
104
  Dynamoid.configure do |config|
105
- config.region = 'us-west-2',
106
- config.credentials = credentials
105
+ config.region = 'us-west-2'
106
+ config.credentials = credentials
107
107
  end
108
108
  ```
109
109
 
@@ -132,8 +132,8 @@ end
132
132
  Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
133
133
 
134
134
  Its compatibility is tested against following Ruby versions: 2.3, 2.4,
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.
135
+ 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, and 4.0, JRuby 9.4.x and against Rails versions: 4.2, 5.0, 5.1,
136
+ 5.2, 6.0, 6.1, 7.0, 7.1, 7.2, 8.0, and 8.1.
137
137
 
138
138
  ## Setup
139
139
 
@@ -471,6 +471,27 @@ method, which would return either `:string` or `:number`.
471
471
  DynamoDB may support some other attribute types that are not yet
472
472
  supported by Dynamoid.
473
473
 
474
+ If a custom type implements `#==` method you can specify `comparable:
475
+ true` option in a field declaration to specify that an object is safely
476
+ comparable for the purpose of detecting changes. By default old and new
477
+ objects will be compared by their serialized representation.
478
+
479
+ ```ruby
480
+ class Money
481
+ # ...
482
+
483
+ def ==(other)
484
+ # comparison logic
485
+ end
486
+ end
487
+
488
+ class User
489
+ # ...
490
+
491
+ field :balance, Money, comparable: true
492
+ end
493
+ ```
494
+
474
495
  ### Sort key
475
496
 
476
497
  Along with partition key table may have a sort key. In order to declare
@@ -620,6 +641,7 @@ with `inheritance_field` table option:
620
641
  ```ruby
621
642
  class Car
622
643
  include Dynamoid::Document
644
+
623
645
  table inheritance_field: :my_new_type
624
646
 
625
647
  field :my_new_type
@@ -914,6 +936,7 @@ It could be done with `project` method:
914
936
  ```ruby
915
937
  class User
916
938
  include Dynamoid::Document
939
+
917
940
  field :name
918
941
  end
919
942
 
@@ -1091,6 +1114,14 @@ on the base table*
1091
1114
  > Please note that this API is experimental and can be changed in
1092
1115
  > future releases.
1093
1116
 
1117
+ DynamoDB supports modifying and reading operations but there are some
1118
+ limitations:
1119
+ - read and write operation cannot be combined in the same transaction
1120
+ - operations are executed in batch, so operations should be given before
1121
+ actual execution and cannot be changed on the fly
1122
+
1123
+ #### Modifying transactions
1124
+
1094
1125
  Multiple modifying actions can be grouped together and submitted as an
1095
1126
  all-or-nothing operation. Atomic modifying operations are supported in
1096
1127
  Dynamoid using transactions. If any action in the transaction fails they
@@ -1100,7 +1131,7 @@ The following actions are supported:
1100
1131
 
1101
1132
  * `#create`/`#create!` - add a new model if it does not already exist
1102
1133
  * `#save`/`#save!` - create or update model
1103
- * `#update_attributes`/`#update_attributes!` - modifies one or more attributes from an existig
1134
+ * `#update_attributes`/`#update_attributes!` - modifies one or more attributes from an existing
1104
1135
  model
1105
1136
  * `#delete` - remove an model without callbacks nor validations
1106
1137
  * `#destroy`/`#destroy!` - remove an model
@@ -1110,8 +1141,7 @@ model
1110
1141
  These methods are supposed to behave exactly like their
1111
1142
  non-transactional counterparts.
1112
1143
 
1113
-
1114
- #### Create models
1144
+ ##### Create models
1115
1145
 
1116
1146
  Models can be created inside of a transaction. The partition and sort
1117
1147
  keys, if applicable, are used to determine uniqueness. Creating will
@@ -1135,7 +1165,7 @@ Dynamoid::TransactionWrite.execute do |txn|
1135
1165
  end
1136
1166
  ```
1137
1167
 
1138
- #### Save models
1168
+ ##### Save models
1139
1169
 
1140
1170
  Models can be saved in a transaction. New records are created otherwise
1141
1171
  the model is updated. Save, create, update, validate and destroy
@@ -1154,7 +1184,7 @@ Dynamoid::TransactionWrite.execute do |txn|
1154
1184
  end
1155
1185
  ```
1156
1186
 
1157
- #### Update models
1187
+ ##### Update models
1158
1188
 
1159
1189
  A model can be updated by providing a model or primary key, and the fields to update.
1160
1190
 
@@ -1166,10 +1196,23 @@ Dynamoid::TransactionWrite.execute do |txn|
1166
1196
  # sets the name and title for a user
1167
1197
  # The user is found by id (that equals 1)
1168
1198
  txn.update_fields(User, '1', name: 'bob', title: 'mister')
1199
+
1200
+ # sets the name, increments a count and deletes a field
1201
+ txn.update_fields(User, 1) do |t|
1202
+ t.set(name: 'bob')
1203
+ t.add(article_count: 1)
1204
+ t.delete(:title)
1205
+ end
1206
+
1207
+ # adds to a set of integers and deletes from a set of strings
1208
+ txn.update_fields(User, 2) do |t|
1209
+ t.add(friend_ids: [1, 2])
1210
+ t.delete(child_names: ['bebe'])
1211
+ end
1169
1212
  end
1170
1213
  ```
1171
1214
 
1172
- #### Destroy or delete models
1215
+ ##### Destroy or delete models
1173
1216
 
1174
1217
  Models can be used or the model class and key can be specified.
1175
1218
  `#destroy` uses callbacks and validations. Use `#delete` to skip
@@ -1188,7 +1231,7 @@ Dynamoid::TransactionWrite.execute do |txn|
1188
1231
  end
1189
1232
  ```
1190
1233
 
1191
- #### Validation failures that don't raise
1234
+ ##### Validation failures that don't raise
1192
1235
 
1193
1236
  All of the transaction methods can be called without the `!` which
1194
1237
  results in `false` instead of a raised exception when validation fails.
@@ -1208,7 +1251,7 @@ Dynamoid::TransactionWrite.execute do |txn|
1208
1251
  end
1209
1252
  ```
1210
1253
 
1211
- #### Incrementally building a transaction
1254
+ ##### Incrementally building a transaction
1212
1255
 
1213
1256
  Transactions can also be built without a block.
1214
1257
 
@@ -1222,6 +1265,41 @@ transaction.upsert(Address, 'A#1', street: '123')
1222
1265
  transaction.commit # changes are persisted in this moment
1223
1266
  ```
1224
1267
 
1268
+ #### Reading transactions
1269
+
1270
+ Multiple reading actions can be grouped together and submitted as an
1271
+ all-or-nothing operation. Atomic operations are supported in Dynamoid
1272
+ using transactions. If any action in the transaction fails they all
1273
+ fail.
1274
+
1275
+ The following actions are supported:
1276
+
1277
+ * `#find` - load a single model or multiple models by its primary key
1278
+
1279
+ These methods are supposed to behave exactly like their
1280
+ non-transactional counterparts.
1281
+
1282
+ ##### Find a model
1283
+
1284
+ The `#find` action can load single model or multiple ones. Different
1285
+ model classes can be mixed in the same transactions. Result is returned
1286
+ as a plain list of all the found models. The order is preserved.
1287
+
1288
+ ```ruby
1289
+ user, address = Dynamoid::TransactionRead.execute do |t|
1290
+ t.find(User, user_id)
1291
+ t.find(Address, address_id)
1292
+ end
1293
+ ```
1294
+
1295
+ Multiple primary keys can be specified at once:
1296
+
1297
+ ```ruby
1298
+ users = Dynamoid::TransactionRead.execute do |t|
1299
+ t.find(User, [id1, id2, id3])
1300
+ end
1301
+ ```
1302
+
1225
1303
  ### PartiQL
1226
1304
 
1227
1305
  To run PartiQL statements `Dynamoid.adapter.execute` method should be
@@ -1269,6 +1347,7 @@ Listed below are all configuration options.
1269
1347
  * `write_capacity` - is used at table or indices creation. Default is 20
1270
1348
  (units)
1271
1349
  * `warn_on_scan` - log warnings when scan table. Default is `true`
1350
+ * `error_on_scan` - raises an error when scan table. Default is `false`
1272
1351
  * `endpoint` - if provided, it communicates with the DynamoDB listening
1273
1352
  at the endpoint. This is useful for testing with
1274
1353
  [DynamoDB Local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html)
@@ -1501,6 +1580,7 @@ order to troubleshoot and debug issues just set it:
1501
1580
  ```ruby
1502
1581
  class User
1503
1582
  include Dynamoid::Document
1583
+
1504
1584
  field name
1505
1585
  end
1506
1586
 
data/SECURITY.md CHANGED
@@ -4,14 +4,14 @@
4
4
 
5
5
  | Version | Supported |
6
6
  |---------|-----------|
7
- | 3.7.x | ✅ |
8
- | <= 3.6 | ❌ |
7
+ | 3.12.x | ✅ |
8
+ | < 3.12 | ❌ |
9
9
  | 2.x | ❌ |
10
10
  | 1.x | ❌ |
11
11
  | 0.x | ❌ |
12
12
 
13
- ## Reporting a Vulnerability
13
+ ## Security contact information
14
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.
15
+ To report a security vulnerability, please use the
16
+ [Tidelift security contact](https://tidelift.com/security).
17
+ Tidelift will coordinate the fix and disclosure.
@@ -63,9 +63,11 @@ module Dynamoid
63
63
  ids.map { |hk, rk| { table.hash_key => hk, table.range_key => rk } }
64
64
  end
65
65
 
66
+ table_name = table.local? ? table.name : table.arn
67
+
66
68
  {
67
69
  request_items: {
68
- table.name => {
70
+ table_name => {
69
71
  keys: keys,
70
72
  consistent_read: options[:consistent_read]
71
73
  }
@@ -58,7 +58,7 @@ module Dynamoid
58
58
  # delete explicitly attributes if assigned nil value and configured
59
59
  # to not store nil values
60
60
  values_to_update = values_sanitized.reject { |_, v| v.nil? }
61
- values_to_delete = values_sanitized.select { |_, v| v.nil? }
61
+ values_to_delete = values_sanitized.select { |_, v| v.nil? } # rubocop:disable Style/PartitionInsteadOfDoubleSelect
62
62
 
63
63
  @updates.merge!(values_to_update)
64
64
  @deletions.merge!(values_to_delete)
@@ -64,6 +64,9 @@ module Dynamoid
64
64
  name_placeholders = {}
65
65
  value_placeholders = {}
66
66
 
67
+ # use table arn if it's configured for a model
68
+ table_name = table.local? ? table.name : table.arn
69
+
67
70
  # Deal with various limits and batching
68
71
  batch_size = options[:batch_size]
69
72
  limit = [record_limit, scan_limit, batch_size].compact.min
@@ -93,7 +96,7 @@ module Dynamoid
93
96
  :exclusive_start_key
94
97
  ).compact
95
98
 
96
- request[:table_name] = table.name
99
+ request[:table_name] = table_name
97
100
  request[:limit] = limit if limit
98
101
  request[:key_condition_expression] = key_condition_expression if key_condition_expression.present?
99
102
  request[:filter_expression] = filter_expression if filter_expression.present?
@@ -57,6 +57,9 @@ module Dynamoid
57
57
  name_placeholders = {}
58
58
  value_placeholders = {}
59
59
 
60
+ # use table arn if it's configured for a model
61
+ table_name = table.local? ? table.name : table.arn
62
+
60
63
  # Deal with various limits and batching
61
64
  batch_size = options[:batch_size]
62
65
  limit = [record_limit, scan_limit, batch_size].compact.min
@@ -79,7 +82,7 @@ module Dynamoid
79
82
  :index_name
80
83
  ).compact
81
84
 
82
- request[:table_name] = table.name
85
+ request[:table_name] = table_name
83
86
  request[:limit] = limit if limit
84
87
  request[:filter_expression] = filter_expression if filter_expression.present?
85
88
  request[:expression_attribute_values] = value_placeholders if value_placeholders.present?
@@ -14,6 +14,7 @@ module Dynamoid
14
14
  #
15
15
  def initialize(schema)
16
16
  @schema = schema[:table]
17
+ @local = false
17
18
  end
18
19
 
19
20
  def range_key
@@ -47,6 +48,18 @@ module Dynamoid
47
48
  def name
48
49
  schema[:table_name]
49
50
  end
51
+
52
+ def arn
53
+ schema[:table_arn]
54
+ end
55
+
56
+ def local!
57
+ @local = true
58
+ end
59
+
60
+ def local?
61
+ @local
62
+ end
50
63
  end
51
64
  end
52
65
  end
@@ -294,6 +294,14 @@ module Dynamoid
294
294
  Transact.new(client).transact_write_items(items)
295
295
  end
296
296
 
297
+ def transact_read_items(items)
298
+ request = {
299
+ transact_items: items,
300
+ return_consumed_capacity: 'TOTAL',
301
+ }
302
+ client.transact_get_items(request)
303
+ end
304
+
297
305
  # Create a table on DynamoDB. This usually takes a long time to complete.
298
306
  #
299
307
  # @param [String] table_name the name of the table to create
@@ -313,6 +321,22 @@ module Dynamoid
313
321
  false
314
322
  end
315
323
 
324
+ #
325
+ # New, semi-arbitrary API to get data on the table
326
+ #
327
+ def describe_table(table_name, reload: false)
328
+ (!reload && table_cache[table_name]) || begin
329
+ response = client.describe_table(table_name: table_name)
330
+ table = Table.new(response.data)
331
+
332
+ if table.name == table_name.to_s
333
+ table.local!
334
+ end
335
+
336
+ table_cache[table_name] = table
337
+ end
338
+ end
339
+
316
340
  def update_time_to_live(table_name, attribute)
317
341
  request = {
318
342
  table_name: table_name,
@@ -646,15 +670,6 @@ module Dynamoid
646
670
  expected
647
671
  end
648
672
 
649
- #
650
- # New, semi-arbitrary API to get data on the table
651
- #
652
- def describe_table(table_name, reload: false)
653
- (!reload && table_cache[table_name]) || begin
654
- table_cache[table_name] = Table.new(client.describe_table(table_name: table_name).data)
655
- end
656
- end
657
-
658
673
  #
659
674
  # Converts a hash returned by get_item, scan, etc. into a key-value hash
660
675
  #
@@ -36,6 +36,7 @@ module Dynamoid
36
36
  option :read_capacity, default: 100
37
37
  option :write_capacity, default: 20
38
38
  option :warn_on_scan, default: true
39
+ option :error_on_scan, default: false
39
40
  option :endpoint, default: nil
40
41
  option :identity_map, default: false
41
42
  option :timestamps, default: true
@@ -563,7 +563,7 @@ module Dynamoid
563
563
  if @key_fields_detector.key_present?
564
564
  raw_pages_via_query
565
565
  else
566
- issue_scan_warning if Dynamoid::Config.warn_on_scan && !@where_conditions.empty?
566
+ validate_scan_conditions
567
567
  raw_pages_via_scan
568
568
  end
569
569
  end
@@ -598,6 +598,12 @@ module Dynamoid
598
598
  end
599
599
  end
600
600
 
601
+ def validate_scan_conditions
602
+ raise Dynamoid::Errors::ScanProhibited if Dynamoid::Config.error_on_scan && !@where_conditions.empty?
603
+
604
+ issue_scan_warning if Dynamoid::Config.warn_on_scan && !@where_conditions.empty?
605
+ end
606
+
601
607
  def issue_scan_warning
602
608
  Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
603
609
  Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.underscore}.rb:"
@@ -697,11 +703,13 @@ module Dynamoid
697
703
  # e.g. for NULL operator value should be boolean
698
704
  # and isn't related to an attribute own type
699
705
  def type_cast_condition_parameter(key, value)
700
- return value if %i[array set].include?(source.attributes[key.to_sym][:type])
706
+ field_type = source.attributes[key.to_sym][:type]
707
+
708
+ return value if %i[array set].include?(field_type)
701
709
 
702
710
  if [true, false].include?(value) # Support argument for null/not_null operators
703
711
  value
704
- elsif !value.respond_to?(:to_ary)
712
+ elsif !value.respond_to?(:to_ary) || field_type == :serialized
705
713
  options = source.attributes[key.to_sym]
706
714
  value_casted = TypeCasting.cast_field(value, options)
707
715
  Dumping.dump_field(value_casted, options)
@@ -301,7 +301,19 @@ module Dynamoid
301
301
  return false if value_from_database.nil?
302
302
 
303
303
  value = read_attribute(name)
304
- value != value_from_database
304
+ type_options = self.class.attributes[name.to_sym]
305
+
306
+ unless type_options[:type].is_a?(Class) && !type_options[:comparable]
307
+ # common case
308
+ value != value_from_database
309
+ else
310
+ # objects of a custom type that does not implement its own `#==` method
311
+ # (that's declared by `comparable: false` or just not specifying the
312
+ # option `comparable`) are compared by comparing their dumps
313
+ dump = Dumping.dump_field(value, type_options)
314
+ dump_from_database = Dumping.dump_field(value_from_database, type_options)
315
+ dump != dump_from_database
316
+ end
305
317
  end
306
318
 
307
319
  module DeepDupper
@@ -318,26 +330,25 @@ module Dynamoid
318
330
 
319
331
  case value
320
332
  when NilClass, TrueClass, FalseClass, Numeric, Symbol, IO
321
- # till Ruby 2.4 these immutable objects could not be duplicated
322
- # IO objects cannot be duplicated - is used for binary fields
333
+ # Till Ruby 2.4 these immutable objects could not be duplicated.
334
+ # IO objects (used for the binary type) cannot be duplicated as well.
323
335
  value
324
- when String
325
- value.dup
326
336
  when Array
327
- if of.is_a? Class # custom type
337
+ if of.is_a? Class
338
+ # custom type
328
339
  value.map { |e| dup_attribute(e, type: of) }
329
340
  else
330
341
  value.deep_dup
331
342
  end
332
343
  when Set
333
344
  Set.new(value.map { |e| dup_attribute(e, type: of) })
334
- when Hash
335
- value.deep_dup
336
345
  else
337
- if type.is_a? Class # custom type
338
- Marshal.load(Marshal.dump(value)) # dup instance variables
346
+ if type.is_a? Class
347
+ # custom type
348
+ dump = Dumping.dump_field(value, type_options)
349
+ Undumping.undump_field(dump.deep_dup, type_options)
339
350
  else
340
- value.dup # date, datetime
351
+ value.deep_dup
341
352
  end
342
353
  end
343
354
  end
@@ -323,10 +323,10 @@ module Dynamoid
323
323
  def process(value)
324
324
  field_class = @options[:type]
325
325
 
326
- if value.respond_to?(:dynamoid_dump)
327
- value.dynamoid_dump
328
- elsif field_class.respond_to?(:dynamoid_dump)
326
+ if field_class.respond_to?(:dynamoid_dump)
329
327
  field_class.dynamoid_dump(value)
328
+ elsif value.respond_to?(:dynamoid_dump)
329
+ value.dynamoid_dump
330
330
  else
331
331
  raise ArgumentError, "Neither #{field_class} nor #{value} supports serialization for Dynamoid."
332
332
  end
@@ -86,10 +86,25 @@ module Dynamoid
86
86
 
87
87
  class UnsupportedKeyType < Error; end
88
88
 
89
- class UnknownAttribute < Error; end
89
+ class UnknownAttribute < Error
90
+ attr_reader :model_class, :attribute_name
91
+
92
+ def initialize(model_class, attribute_name)
93
+ super("Attribute #{attribute_name} does not exist in #{model_class}")
94
+
95
+ @model_class = model_class
96
+ @attribute_name = attribute_name
97
+ end
98
+ end
90
99
 
91
100
  class SubclassNotFound < Error; end
92
101
 
93
102
  class Rollback < Error; end
103
+
104
+ class ScanProhibited < Error
105
+ def initialize(_msg = nil)
106
+ super('Scan operations prohibited. Modify Dynamoid::Config.error_on_scan to change this behavior.')
107
+ end
108
+ end
94
109
  end
95
110
  end
@@ -77,7 +77,7 @@ module Dynamoid
77
77
  end
78
78
 
79
79
  def warn_if_method_exists(method)
80
- if @source.instance_methods.include?(method.to_sym)
80
+ if @source.method_defined?(method.to_sym)
81
81
  Dynamoid.logger.warn("Method #{method} generated for the field #{@name} overrides already existing method")
82
82
  end
83
83
  end