dynamoid 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -3
- data/README.md +93 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +8 -0
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +11 -3
- data/lib/dynamoid/dirty.rb +22 -11
- data/lib/dynamoid/dumping.rb +3 -3
- data/lib/dynamoid/errors.rb +16 -1
- data/lib/dynamoid/fields.rb +13 -3
- data/lib/dynamoid/finders.rb +38 -17
- data/lib/dynamoid/persistence/inc.rb +30 -13
- data/lib/dynamoid/persistence/save.rb +24 -12
- data/lib/dynamoid/persistence/update_fields.rb +18 -5
- data/lib/dynamoid/persistence/update_validations.rb +3 -3
- data/lib/dynamoid/persistence/upsert.rb +17 -4
- data/lib/dynamoid/persistence.rb +146 -11
- data/lib/dynamoid/transaction_read/find.rb +137 -0
- data/lib/dynamoid/transaction_read.rb +146 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +7 -2
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +7 -2
- data/lib/dynamoid/transaction_write/destroy.rb +7 -2
- data/lib/dynamoid/transaction_write/item_updater.rb +55 -0
- data/lib/dynamoid/transaction_write/save.rb +7 -2
- data/lib/dynamoid/transaction_write/update_fields.rb +169 -32
- data/lib/dynamoid/transaction_write/upsert.rb +12 -2
- data/lib/dynamoid/transaction_write.rb +212 -3
- data/lib/dynamoid/validations.rb +7 -4
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3e330233bdd01c0bb962fb7253b48629fe9f0aeb1ecdcb6cfe7d3272ddddbe0
|
4
|
+
data.tar.gz: 7f6ee27e8d6dedb5fe21bce21a6c00397f42d85dfa40b8d30dae4e13a8294d15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f84a868794dd6b812ccd6936eaa16c91a8d0f87b2ae959b89a8b01802537490168449bfa83f62d92f8643a6c47043cefeb9168da3301ca4f005400bc1086406f
|
7
|
+
data.tar.gz: 528587c73879f49d25e660974fa5832464eed5773da06535a76bfe3c9c96311a1877eb801e68362c94eadc479cf65a8df053a0f47574c78fbf5919859cb88e66
|
data/CHANGELOG.md
CHANGED
@@ -11,21 +11,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
11
11
|
### Changed
|
12
12
|
### Removed
|
13
13
|
|
14
|
-
## 3.
|
14
|
+
## 3.12.0
|
15
15
|
|
16
|
+
### Fixed
|
17
|
+
* [#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.
|
18
|
+
A proper behaviour is to use adapter's .dynamoid_dump method instead of a value's #dynamoid_dump method.
|
19
|
+
* [#851](https://github.com/Dynamoid/dynamoid/pull/851) Fixed Dirty API and don't require custom types to implement `#==` method. Add field option `:comparable`.
|
20
|
+
* [#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
|
21
|
+
* [#917](https://github.com/Dynamoid/dynamoid/pull/917) Fixed transactional write operations when primary key is of non-native DynamoDB type
|
22
|
+
* [#927](https://github.com/Dynamoid/dynamoid/pull/927) Multiple fixes in transactional and non-transactional write methods and finders:
|
23
|
+
* Changed non-transactional method `#find` and raise `MissingHashKey` when a given partition key is `nil`
|
24
|
+
* Fixed non-transactional method `#save!` and raise `RecordNotSaved` when a callback throws `:abort` and terminates the process
|
25
|
+
* Check whether partition and sort key are specified in the non-transactional persistence methods and raise a proper exception (`MissingHashKey` or `MissingRangeKey`)
|
26
|
+
* 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
|
27
|
+
* Fixed method `#update_attribute` to not raise `StaleObjectError` when an item with specified primary key is already deleted
|
28
|
+
* Fixed method `#where` with condition on a `:serialized` field to not raise an exception
|
29
|
+
### Added
|
30
|
+
* [#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)
|
31
|
+
* [#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)
|
32
|
+
* [#926](https://github.com/Dynamoid/dynamoid/pull/926) Implemented read transactions (using DynamoDB's `TransactGetItems` operation)
|
33
|
+
### Changed
|
34
|
+
* [#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)
|
35
|
+
### Removed
|
36
|
+
|
37
|
+
## 3.11.0 / 2025-01-12
|
16
38
|
### Fixed
|
17
39
|
* [#829](https://github.com/Dynamoid/dynamoid/pull/829) Fixed saving of in-place field changes
|
18
40
|
* [#812](https://github.com/Dynamoid/dynamoid/pull/812) Restored sanitizing of attribute names in `#where` conditions
|
19
41
|
* [#721](https://github.com/Dynamoid/dynamoid/pull/721) Fixed code examples in README.md (@ndjndj)
|
20
42
|
### Added
|
21
|
-
* [#688](https://github.com/Dynamoid/dynamoid/pull/688)
|
43
|
+
* [#688](https://github.com/Dynamoid/dynamoid/pull/688) Implemented write transactions (using DynamoDB's `TransactWriteItems` operation) (@ckhsponge)
|
22
44
|
* [#794](https://github.com/Dynamoid/dynamoid/pull/794) Added new config option `store_empty_string_as_nil`
|
23
45
|
* [#828](https://github.com/Dynamoid/dynamoid/pull/828) Added Ruby 3.4, Rails 8.0 and Rails 7.2 in CI
|
24
46
|
### Changed
|
25
47
|
* [#832](https://github.com/Dynamoid/dynamoid/pull/832) Support String condition expressions with `#where`
|
26
48
|
* [#822](https://github.com/Dynamoid/dynamoid/pull/822) Support binary type natively. Also added new config option `store_binary_as_native` (@dalibor)
|
27
49
|
|
28
|
-
## 3.10.0
|
50
|
+
## 3.10.0 / 2024-02-10
|
29
51
|
### Fixed
|
30
52
|
* [#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
53
|
* [#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
|
28
|
-
|
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
|
-
|
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.
|
136
|
-
5.2, 6.0, 6.1, 7.0
|
135
|
+
2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, and 3.4, 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, and 8.0.
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
@@ -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
|
data/lib/dynamoid/config.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
data/lib/dynamoid/dirty.rb
CHANGED
@@ -301,7 +301,19 @@ module Dynamoid
|
|
301
301
|
return false if value_from_database.nil?
|
302
302
|
|
303
303
|
value = read_attribute(name)
|
304
|
-
|
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
|
-
#
|
322
|
-
# IO objects
|
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
|
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
|
338
|
-
|
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.
|
351
|
+
value.deep_dup
|
341
352
|
end
|
342
353
|
end
|
343
354
|
end
|
data/lib/dynamoid/dumping.rb
CHANGED
@@ -323,10 +323,10 @@ module Dynamoid
|
|
323
323
|
def process(value)
|
324
324
|
field_class = @options[:type]
|
325
325
|
|
326
|
-
if
|
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
|
data/lib/dynamoid/errors.rb
CHANGED
@@ -86,10 +86,25 @@ module Dynamoid
|
|
86
86
|
|
87
87
|
class UnsupportedKeyType < Error; end
|
88
88
|
|
89
|
-
class UnknownAttribute < Error
|
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
|
data/lib/dynamoid/fields.rb
CHANGED
@@ -197,9 +197,19 @@ module Dynamoid
|
|
197
197
|
# field :id, :integer
|
198
198
|
# end
|
199
199
|
#
|
200
|
+
# To declare a new attribute with not-default type as a table hash key a
|
201
|
+
# :key_type option can be used:
|
202
|
+
#
|
203
|
+
# class User
|
204
|
+
# include Dynamoid::Document
|
205
|
+
#
|
206
|
+
# table key: :user_id, key_type: :integer
|
207
|
+
# end
|
208
|
+
#
|
200
209
|
# @param options [Hash] options to override default table settings
|
201
210
|
# @option options [Symbol] :name name of a table
|
202
211
|
# @option options [Symbol] :key name of a hash key attribute
|
212
|
+
# @option options [Symbol] :key_type type of a hash key attribute
|
203
213
|
# @option options [Symbol] :inheritance_field name of an attribute used for STI
|
204
214
|
# @option options [Symbol] :capacity_mode table billing mode - either +provisioned+ or +on_demand+
|
205
215
|
# @option options [Integer] :write_capacity table write capacity units
|
@@ -210,11 +220,11 @@ module Dynamoid
|
|
210
220
|
# @since 0.4.0
|
211
221
|
def table(options)
|
212
222
|
self.options = options
|
213
|
-
|
214
223
|
# a default 'id' column is created when Dynamoid::Document is included
|
215
224
|
unless attributes.key? hash_key
|
216
225
|
remove_field :id
|
217
|
-
|
226
|
+
key_type = options[:key_type] || :string
|
227
|
+
field(hash_key, key_type)
|
218
228
|
end
|
219
229
|
|
220
230
|
# The created_at/updated_at fields are declared in the `included` callback first.
|
@@ -291,7 +301,7 @@ module Dynamoid
|
|
291
301
|
old_value = read_attribute(name)
|
292
302
|
|
293
303
|
unless attribute_is_present_on_model?(name)
|
294
|
-
raise Dynamoid::Errors::UnknownAttribute,
|
304
|
+
raise Dynamoid::Errors::UnknownAttribute.new(self.class, name)
|
295
305
|
end
|
296
306
|
|
297
307
|
if association = @associations[name]
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -14,12 +14,12 @@ module Dynamoid
|
|
14
14
|
# specified +raise_error: false+ option then +find+ will not raise the
|
15
15
|
# exception.
|
16
16
|
#
|
17
|
-
# When a document schema includes range key it always
|
17
|
+
# When a document schema includes range key it should always be specified
|
18
18
|
# in +find+ method call. In case it's missing +MissingRangeKey+ exception
|
19
19
|
# will be raised.
|
20
20
|
#
|
21
21
|
# Please note that +find+ doesn't preserve order of models in result when
|
22
|
-
#
|
22
|
+
# given multiple ids.
|
23
23
|
#
|
24
24
|
# Supported following options:
|
25
25
|
# * +consistent_read+
|
@@ -107,14 +107,28 @@ module Dynamoid
|
|
107
107
|
|
108
108
|
# @private
|
109
109
|
def _find_all(ids, options = {})
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
110
|
+
ids = ids.map do |id|
|
111
|
+
if range_key
|
112
|
+
# expect [hash key, range key] pair
|
113
|
+
pk, sk = id
|
114
|
+
|
115
|
+
if pk.nil?
|
116
|
+
raise Errors::MissingHashKey
|
117
|
+
end
|
118
|
+
if sk.nil?
|
119
|
+
raise Errors::MissingRangeKey
|
120
|
+
end
|
121
|
+
|
122
|
+
pk_dumped = cast_and_dump(hash_key, pk)
|
123
|
+
sk_dumped = cast_and_dump(range_key, sk)
|
124
|
+
|
125
|
+
[pk_dumped, sk_dumped]
|
126
|
+
else
|
127
|
+
if id.nil?
|
128
|
+
raise Errors::MissingHashKey
|
129
|
+
end
|
116
130
|
|
117
|
-
|
131
|
+
cast_and_dump(hash_key, id)
|
118
132
|
end
|
119
133
|
end
|
120
134
|
|
@@ -144,7 +158,7 @@ module Dynamoid
|
|
144
158
|
models.each { |m| m.run_callbacks :find }
|
145
159
|
models
|
146
160
|
else
|
147
|
-
ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:
|
161
|
+
ids_list = range_key ? ids.map { |pk, sk| "(#{pk.inspect},#{sk.inspect})" } : ids.map(&:inspect)
|
148
162
|
message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
|
149
163
|
message += "(found #{items.size} results, but was looking for #{ids.size})"
|
150
164
|
raise Errors::RecordNotFound, message
|
@@ -153,22 +167,21 @@ module Dynamoid
|
|
153
167
|
|
154
168
|
# @private
|
155
169
|
def _find_by_id(id, options = {})
|
170
|
+
raise Errors::MissingHashKey if id.nil?
|
156
171
|
raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
|
157
172
|
|
158
|
-
|
159
|
-
key = options[:range_key]
|
160
|
-
key_casted = TypeCasting.cast_field(key, attributes[range_key])
|
161
|
-
key_dumped = Dumping.dump_field(key_casted, attributes[range_key])
|
173
|
+
partition_key_dumped = cast_and_dump(hash_key, id)
|
162
174
|
|
163
|
-
|
175
|
+
if range_key
|
176
|
+
options[:range_key] = cast_and_dump(range_key, options[:range_key])
|
164
177
|
end
|
165
178
|
|
166
|
-
if item = Dynamoid.adapter.read(table_name,
|
179
|
+
if item = Dynamoid.adapter.read(table_name, partition_key_dumped, options.slice(:range_key, :consistent_read))
|
167
180
|
model = from_database(item)
|
168
181
|
model.run_callbacks :find
|
169
182
|
model
|
170
183
|
elsif options[:raise_error]
|
171
|
-
primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
|
184
|
+
primary_key = range_key ? "(#{id.inspect},#{options[:range_key].inspect})" : id.inspect
|
172
185
|
message = "Couldn't find #{name} with primary key #{primary_key}"
|
173
186
|
raise Errors::RecordNotFound, message
|
174
187
|
end
|
@@ -308,6 +321,14 @@ module Dynamoid
|
|
308
321
|
super
|
309
322
|
end
|
310
323
|
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
def cast_and_dump(name, value)
|
328
|
+
attribute_options = attributes[name]
|
329
|
+
casted_value = TypeCasting.cast_field(value, attribute_options)
|
330
|
+
Dumping.dump_field(casted_value, attribute_options)
|
331
|
+
end
|
311
332
|
end
|
312
333
|
end
|
313
334
|
end
|
@@ -6,23 +6,25 @@ module Dynamoid
|
|
6
6
|
module Persistence
|
7
7
|
# @private
|
8
8
|
class Inc
|
9
|
-
def self.call(model_class,
|
10
|
-
new(model_class,
|
9
|
+
def self.call(model_class, partition_key, sort_key = nil, counters)
|
10
|
+
new(model_class, partition_key, sort_key, counters).call
|
11
11
|
end
|
12
12
|
|
13
13
|
# rubocop:disable Style/OptionalArguments
|
14
|
-
def initialize(model_class,
|
14
|
+
def initialize(model_class, partition_key, sort_key = nil, counters)
|
15
15
|
@model_class = model_class
|
16
|
-
@
|
17
|
-
@
|
16
|
+
@partition_key = partition_key
|
17
|
+
@sort_key = sort_key
|
18
18
|
@counters = counters
|
19
19
|
end
|
20
20
|
# rubocop:enable Style/OptionalArguments
|
21
21
|
|
22
22
|
def call
|
23
23
|
touch = @counters.delete(:touch)
|
24
|
+
partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
25
|
+
options = update_item_options(partition_key_dumped)
|
24
26
|
|
25
|
-
Dynamoid.adapter.update_item(@model_class.table_name,
|
27
|
+
Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options) do |t|
|
26
28
|
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
27
29
|
|
28
30
|
@counters.each do |name, value|
|
@@ -37,19 +39,28 @@ module Dynamoid
|
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
42
|
+
rescue Dynamoid::Errors::ConditionalCheckFailedException # rubocop:disable Lint/SuppressedException
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
43
46
|
|
44
|
-
def update_item_options
|
47
|
+
def update_item_options(partition_key_dumped)
|
48
|
+
options = {}
|
49
|
+
|
50
|
+
conditions = {
|
51
|
+
if: {
|
52
|
+
@model_class.hash_key => partition_key_dumped
|
53
|
+
}
|
54
|
+
}
|
55
|
+
options[:conditions] = conditions
|
56
|
+
|
45
57
|
if @model_class.range_key
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
{ range_key: value_dumped }
|
50
|
-
else
|
51
|
-
{}
|
58
|
+
sort_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
|
59
|
+
options[:range_key] = sort_key_dumped
|
60
|
+
options[:conditions][@model_class.range_key] = sort_key_dumped
|
52
61
|
end
|
62
|
+
|
63
|
+
options
|
53
64
|
end
|
54
65
|
|
55
66
|
def timestamp_attributes_to_touch(touch)
|
@@ -60,6 +71,12 @@ module Dynamoid
|
|
60
71
|
names += Array.wrap(touch) if touch != true
|
61
72
|
names
|
62
73
|
end
|
74
|
+
|
75
|
+
def cast_and_dump(name, value)
|
76
|
+
options = @model_class.attributes[name]
|
77
|
+
value_casted = TypeCasting.cast_field(value, options)
|
78
|
+
Dumping.dump_field(value_casted, options)
|
79
|
+
end
|
63
80
|
end
|
64
81
|
end
|
65
82
|
end
|