dynamoid 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +28 -8
- data/lib/dynamoid/adapter.rb +7 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +10 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +2 -2
- data/lib/dynamoid/associations/association.rb +6 -0
- data/lib/dynamoid/associations/many_association.rb +3 -1
- data/lib/dynamoid/associations/single_association.rb +3 -3
- data/lib/dynamoid/criteria.rb +8 -1
- data/lib/dynamoid/criteria/chain.rb +31 -1
- data/lib/dynamoid/criteria/key_fields_detector.rb +14 -1
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields.rb +29 -35
- data/lib/dynamoid/fields/declare.rb +86 -0
- data/lib/dynamoid/indexes.rb +10 -0
- data/lib/dynamoid/loadable.rb +2 -2
- data/lib/dynamoid/persistence.rb +23 -3
- data/lib/dynamoid/persistence/update_fields.rb +4 -2
- data/lib/dynamoid/persistence/update_validations.rb +18 -0
- data/lib/dynamoid/persistence/upsert.rb +4 -2
- data/lib/dynamoid/validations.rb +4 -1
- data/lib/dynamoid/version.rb +1 -1
- metadata +11 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2146a1a188ecf2b8dd2f280172119879d6612d9eadda6cbcbf6defb3319f7022
|
4
|
+
data.tar.gz: df59300783324be7952fd25f8d49880707938762d3960ce1d422c18b49e26ac6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2ebd06729d024b98338e5c11953976935f5252ad76f6dec9265929fd447ed488295d9f06c8ae5440c7c6a494fb1cdb44ffeba002c18ea52bd4ad100df2d4c66
|
7
|
+
data.tar.gz: eddf03eb98125fbc7fcfa842e8276ba5dcbd558abacc3adeb2d451e3bb71bcb92f69eb84a01231304b4690b087a826ebf7c4ee2426a16d92a7a126ceddf1e132
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,28 @@
|
|
1
1
|
# HEAD
|
2
2
|
|
3
|
+
---
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
# 3.7.0 / 2021-02-02
|
8
|
+
|
9
|
+
|
3
10
|
## Features
|
4
11
|
|
12
|
+
* [#476](https://github.com/Dynamoid/dynamoid/pull/476) Added `#with_index` method to force an index in query (@bmalinconico)
|
13
|
+
* [#481](https://github.com/Dynamoid/dynamoid/pull/481) Added `alias` option to the `field` method to declare a field alias and use more conventional name to set and get value
|
14
|
+
|
5
15
|
## Improvements
|
6
16
|
|
17
|
+
* [#482](https://github.com/Dynamoid/dynamoid/pull/482) Support Ruby 3.0 and Rails 6.1
|
18
|
+
* [#461](https://github.com/Dynamoid/dynamoid/pull/461) Allow to delete item attribute with `#update` method (@jkirsteins)
|
19
|
+
* [#463](https://github.com/Dynamoid/dynamoid/pull/463) Raise `UnknownAttribute` exception when specified not declared attribute name (@AlexGascon)
|
20
|
+
|
7
21
|
## Fixes
|
8
22
|
|
23
|
+
* [#480](https://github.com/Dynamoid/dynamoid/pull/480) Repair `.consistent`/`.delete_all`/`.destroy_all` calls directly on a model class
|
24
|
+
* [#484](https://github.com/Dynamoid/dynamoid/pull/484) Fix broken foreign keys after model deleting (@kkan)
|
25
|
+
* Fixes in Readme.md: [#470](https://github.com/Dynamoid/dynamoid/pull/470) (@rromanchuk), [#473](https://github.com/Dynamoid/dynamoid/pull/473) (@Rulikkk)
|
9
26
|
|
10
27
|
---
|
11
28
|
|
data/README.md
CHANGED
@@ -126,8 +126,8 @@ end
|
|
126
126
|
Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
|
127
127
|
|
128
128
|
Its compatibility is tested against following Ruby versions: 2.3, 2.4,
|
129
|
-
2.5
|
130
|
-
5.2 and 6.
|
129
|
+
2.5, 2.6, 2.7 and 3.0, JRuby 9.2.x and against Rails versions: 4.2, 5.0, 5.1,
|
130
|
+
5.2, 6.0 and 6.1.
|
131
131
|
|
132
132
|
## Setup
|
133
133
|
|
@@ -365,6 +365,26 @@ field :actions_taken, :integer, default: 0
|
|
365
365
|
field :joined_at, :datetime, default: -> { Time.now }
|
366
366
|
```
|
367
367
|
|
368
|
+
#### Aliases
|
369
|
+
|
370
|
+
It might be helpful to define an alias for already existing field when
|
371
|
+
naming convention used for a table differs from conventions common in
|
372
|
+
Ruby:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
field firstName, :string, alias: :first_name
|
376
|
+
```
|
377
|
+
|
378
|
+
This way there will be generated
|
379
|
+
setters/getters/`<name>?`/`<name>_before_type_cast` methods for both
|
380
|
+
original field name (`firstName`) and an alias (`first_name`).
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
user = User.new(first_name: 'Michael')
|
384
|
+
user.first_name # => 'Michael'
|
385
|
+
user.firstName # => 'Michael'
|
386
|
+
```
|
387
|
+
|
368
388
|
#### Custom Types
|
369
389
|
|
370
390
|
To use a custom type for a field, suppose you have a `Money` type.
|
@@ -576,7 +596,7 @@ c.my_new_type
|
|
576
596
|
|
577
597
|
### Type casting
|
578
598
|
|
579
|
-
|
599
|
+
Dynamoid supports type casting and tries to do it in the most convenient
|
580
600
|
way. Values for all fields (except custom type) are coerced to declared
|
581
601
|
field types.
|
582
602
|
|
@@ -614,7 +634,7 @@ well.
|
|
614
634
|
|
615
635
|
### Dirty API
|
616
636
|
|
617
|
-
Dynamoid supports Dirty API which
|
637
|
+
Dynamoid supports Dirty API which is equivalent to [Rails 5.2
|
618
638
|
`ActiveModel::Dirty`](https://api.rubyonrails.org/v5.2/classes/ActiveModel/Dirty.html).
|
619
639
|
There is only one limitation - change in place of field isn't detected
|
620
640
|
automatically.
|
@@ -632,7 +652,7 @@ u.email = 'josh@joshsymonds.com'
|
|
632
652
|
u.save
|
633
653
|
```
|
634
654
|
|
635
|
-
Save forces persistence to the
|
655
|
+
Save forces persistence to the data store: a unique ID is also assigned,
|
636
656
|
but it is a string and not an auto-incrementing number.
|
637
657
|
|
638
658
|
```ruby
|
@@ -1267,7 +1287,7 @@ class User
|
|
1267
1287
|
end
|
1268
1288
|
|
1269
1289
|
Dynamoid.config.logger.level = :debug
|
1270
|
-
Dynamoid.config.endpoint = 'localhost:8000'
|
1290
|
+
Dynamoid.config.endpoint = 'http://localhost:8000'
|
1271
1291
|
|
1272
1292
|
User.create(name: 'Alex')
|
1273
1293
|
|
@@ -1342,12 +1362,12 @@ environment.
|
|
1342
1362
|
|
1343
1363
|
If you want to run all the specs that travis runs, use `bundle exec
|
1344
1364
|
wwtd`, but first you will need to setup all the rubies, for each of `%w(
|
1345
|
-
2.
|
1365
|
+
2.3.8 2.4.6 2.5.5 2.6.3 2.7.0 3.0.0 9.2.14.0)`. When you run
|
1346
1366
|
`bundle exec wwtd` it will take care of starting and stopping the local
|
1347
1367
|
dynamodb instance.
|
1348
1368
|
|
1349
1369
|
```shell
|
1350
|
-
rvm use
|
1370
|
+
rvm use 3.0.0
|
1351
1371
|
gem install rubygems-update
|
1352
1372
|
gem install bundler
|
1353
1373
|
bundle install
|
data/lib/dynamoid/adapter.rb
CHANGED
@@ -158,8 +158,14 @@ module Dynamoid
|
|
158
158
|
#
|
159
159
|
# @since 0.2.0
|
160
160
|
def method_missing(method, *args, &block)
|
161
|
-
|
161
|
+
# Don't use keywork arguments delegating (with **kw). It works in
|
162
|
+
# different way in different Ruby versions: <= 2.6, 2.7, 3.0 and in some
|
163
|
+
# future 3.x versions. Providing that there are no downstream methods
|
164
|
+
# with keyword arguments in adapter.
|
165
|
+
#
|
166
|
+
# https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
162
167
|
|
168
|
+
return benchmark(method, *args) { adapter.send(method, *args, &block) } if adapter.respond_to?(method)
|
163
169
|
super
|
164
170
|
end
|
165
171
|
|
@@ -12,6 +12,15 @@ module Dynamoid
|
|
12
12
|
# @private
|
13
13
|
module AdapterPlugin
|
14
14
|
# The AwsSdkV3 adapter provides support for the aws-sdk version 2 for ruby.
|
15
|
+
|
16
|
+
# Note: Don't use keyword arguments in public methods as far as method
|
17
|
+
# calls on adapter are delegated to the plugin.
|
18
|
+
#
|
19
|
+
# There are breaking changes in Ruby related to delegating keyword
|
20
|
+
# arguments so we have decided just to avoid them when use delegation.
|
21
|
+
#
|
22
|
+
# https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
23
|
+
|
15
24
|
class AwsSdkV3
|
16
25
|
EQ = 'EQ'
|
17
26
|
RANGE_MAP = {
|
@@ -281,7 +290,7 @@ module Dynamoid
|
|
281
290
|
false
|
282
291
|
end
|
283
292
|
|
284
|
-
def update_time_to_live(table_name
|
293
|
+
def update_time_to_live(table_name, attribute)
|
285
294
|
request = {
|
286
295
|
table_name: table_name,
|
287
296
|
time_to_live_specification: {
|
@@ -58,6 +58,12 @@ module Dynamoid
|
|
58
58
|
:set
|
59
59
|
end
|
60
60
|
|
61
|
+
def disassociate_source
|
62
|
+
Array(target).each do |target_entry|
|
63
|
+
target_entry.send(target_association).disassociate(source.hash_key) if target_entry && target_association
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
61
67
|
private
|
62
68
|
|
63
69
|
# The target class name, either inferred through the association's name or specified in options.
|
@@ -28,7 +28,7 @@ module Dynamoid
|
|
28
28
|
# unsaved changes will be saved. Doesn't delete an associated model from
|
29
29
|
# DynamoDB.
|
30
30
|
def delete
|
31
|
-
|
31
|
+
disassociate_source
|
32
32
|
disassociate
|
33
33
|
target
|
34
34
|
end
|
@@ -90,7 +90,7 @@ module Dynamoid
|
|
90
90
|
|
91
91
|
# @private
|
92
92
|
def associate(hash_key)
|
93
|
-
|
93
|
+
disassociate_source
|
94
94
|
source.update_attribute(source_attribute, Set[hash_key])
|
95
95
|
end
|
96
96
|
|
@@ -109,7 +109,7 @@ module Dynamoid
|
|
109
109
|
def find_target
|
110
110
|
return if source_ids.empty?
|
111
111
|
|
112
|
-
target_class.find(source_ids.first)
|
112
|
+
target_class.find(source_ids.first, raise_error: false)
|
113
113
|
end
|
114
114
|
|
115
115
|
def target=(object)
|
data/lib/dynamoid/criteria.rb
CHANGED
@@ -9,12 +9,19 @@ module Dynamoid
|
|
9
9
|
|
10
10
|
# @private
|
11
11
|
module ClassMethods
|
12
|
-
%i[where all first last each record_limit scan_limit batch start scan_index_forward find_by_pages project pluck].each do |meth|
|
12
|
+
%i[where consistent all first last delete_all destroy_all each record_limit scan_limit batch start scan_index_forward find_by_pages project pluck].each do |meth|
|
13
13
|
# Return a criteria chain in response to a method that will begin or end a chain. For more information,
|
14
14
|
# see Dynamoid::Criteria::Chain.
|
15
15
|
#
|
16
16
|
# @since 0.2.0
|
17
17
|
define_method(meth) do |*args, &blk|
|
18
|
+
# Don't use keywork arguments delegating (with **kw). It works in
|
19
|
+
# different way in different Ruby versions: <= 2.6, 2.7, 3.0 and in some
|
20
|
+
# future 3.x versions. Providing that there are no downstream methods
|
21
|
+
# with keyword arguments in Chain.
|
22
|
+
#
|
23
|
+
# https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
24
|
+
|
18
25
|
chain = Dynamoid::Criteria::Chain.new(self)
|
19
26
|
if args
|
20
27
|
chain.send(meth, *args, &blk)
|
@@ -110,7 +110,7 @@ module Dynamoid
|
|
110
110
|
query.update(args.symbolize_keys)
|
111
111
|
|
112
112
|
# we should re-initialize keys detector every time we change query
|
113
|
-
@key_fields_detector = KeyFieldsDetector.new(@query, @source)
|
113
|
+
@key_fields_detector = KeyFieldsDetector.new(@query, @source, forced_index_name: @forced_index_name)
|
114
114
|
|
115
115
|
self
|
116
116
|
end
|
@@ -358,6 +358,36 @@ module Dynamoid
|
|
358
358
|
self
|
359
359
|
end
|
360
360
|
|
361
|
+
# Force the index name to use for queries.
|
362
|
+
#
|
363
|
+
# By default allows the library to select the most appropriate index.
|
364
|
+
# Sometimes you have more than one index which will fulfill your query's
|
365
|
+
# needs. When this case occurs you may want to force an order. This occurs
|
366
|
+
# when you are searching by hash key, but not specifying a range key.
|
367
|
+
#
|
368
|
+
# class Comment
|
369
|
+
# include Dynamoid::Document
|
370
|
+
#
|
371
|
+
# table key: :post_id
|
372
|
+
# range_key :author_id
|
373
|
+
#
|
374
|
+
# field :post_date, :datetime
|
375
|
+
#
|
376
|
+
# global_secondary_index name: :time_sorted_comments, hash_key: :post_id, range_key: post_date, projected_attributes: :all
|
377
|
+
# end
|
378
|
+
#
|
379
|
+
#
|
380
|
+
# Comment.where(post_id: id).with_index(:time_sorted_comments).scan_index_forward(false)
|
381
|
+
#
|
382
|
+
# @return [Dynamoid::Criteria::Chain]
|
383
|
+
def with_index(index_name)
|
384
|
+
raise Dynamoid::Errors::InvalidIndex, "Unknown index #{index_name}" unless @source.find_index_by_name(index_name)
|
385
|
+
|
386
|
+
@forced_index_name = index_name
|
387
|
+
@key_fields_detector = KeyFieldsDetector.new(@query, @source, forced_index_name: index_name)
|
388
|
+
self
|
389
|
+
end
|
390
|
+
|
361
391
|
# Allows to use the results of a search as an enumerable over the results
|
362
392
|
# found.
|
363
393
|
#
|
@@ -24,10 +24,11 @@ module Dynamoid
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def initialize(query, source)
|
27
|
+
def initialize(query, source, forced_index_name: nil)
|
28
28
|
@query = query
|
29
29
|
@source = source
|
30
30
|
@query = Query.new(query)
|
31
|
+
@forced_index_name = forced_index_name
|
31
32
|
@result = find_keys_in_query
|
32
33
|
end
|
33
34
|
|
@@ -54,6 +55,8 @@ module Dynamoid
|
|
54
55
|
private
|
55
56
|
|
56
57
|
def find_keys_in_query
|
58
|
+
return match_forced_index if @forced_index_name
|
59
|
+
|
57
60
|
match_table_and_sort_key ||
|
58
61
|
match_local_secondary_index ||
|
59
62
|
match_global_secondary_index_and_sort_key ||
|
@@ -133,6 +136,16 @@ module Dynamoid
|
|
133
136
|
}
|
134
137
|
end
|
135
138
|
end
|
139
|
+
|
140
|
+
def match_forced_index
|
141
|
+
idx = @source.find_index_by_name(@forced_index_name)
|
142
|
+
|
143
|
+
{
|
144
|
+
hash_key: idx.hash_key,
|
145
|
+
range_key: idx.range_key,
|
146
|
+
index_name: idx.name,
|
147
|
+
}
|
148
|
+
end
|
136
149
|
end
|
137
150
|
end
|
138
151
|
end
|
data/lib/dynamoid/errors.rb
CHANGED
data/lib/dynamoid/fields.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dynamoid/fields/declare'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
# All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
|
5
7
|
# specified with field, then they will be ignored.
|
@@ -43,15 +45,15 @@ module Dynamoid
|
|
43
45
|
# end
|
44
46
|
#
|
45
47
|
# Its type determines how it is coerced when read in and out of the
|
46
|
-
#
|
48
|
+
# data store. You can specify +string+, +integer+, +number+, +set+, +array+,
|
47
49
|
# +map+, +datetime+, +date+, +serialized+, +raw+, +boolean+ and +binary+
|
48
50
|
# or specify a class that defines a serialization strategy.
|
49
51
|
#
|
50
52
|
# By default field type is +string+.
|
51
53
|
#
|
52
54
|
# Set can store elements of the same type only (it's a limitation of
|
53
|
-
# DynamoDB itself). If a set should store elements only some particular
|
54
|
-
# type +of+ option should be specified:
|
55
|
+
# DynamoDB itself). If a set should store elements only of some particular
|
56
|
+
# type then +of+ option should be specified:
|
55
57
|
#
|
56
58
|
# field :hobbies, :set, of: :string
|
57
59
|
#
|
@@ -126,41 +128,31 @@ module Dynamoid
|
|
126
128
|
# user.age # => 21 - integer
|
127
129
|
# user.age_before_type_cast # => '21' - string
|
128
130
|
#
|
131
|
+
# There is also an option +alias+ which allows to use another name for a
|
132
|
+
# field:
|
133
|
+
#
|
134
|
+
# class User
|
135
|
+
# include Dynamoid::Document
|
136
|
+
#
|
137
|
+
# field :firstName, :string, alias: :first_name
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# user = User.new(firstName: 'Michael')
|
141
|
+
# user.firstName # Michael
|
142
|
+
# user.first_name # Michael
|
143
|
+
#
|
129
144
|
# @param name [Symbol] name of the field
|
130
145
|
# @param type [Symbol] type of the field (optional)
|
131
146
|
# @param options [Hash] any additional options for the field type (optional)
|
132
147
|
#
|
133
148
|
# @since 0.2.0
|
134
149
|
def field(name, type = :string, options = {})
|
135
|
-
named = name.to_s
|
136
150
|
if type == :float
|
137
151
|
Dynamoid.logger.warn("Field type :float, which you declared for '#{name}', is deprecated in favor of :number.")
|
138
152
|
type = :number
|
139
153
|
end
|
140
|
-
self.attributes = attributes.merge(name => { type: type }.merge(options))
|
141
|
-
|
142
|
-
# should be called before `define_attribute_methods` method because it defines a getter itself
|
143
|
-
warn_about_method_overriding(name, name)
|
144
|
-
warn_about_method_overriding("#{named}=", name)
|
145
|
-
warn_about_method_overriding("#{named}?", name)
|
146
|
-
warn_about_method_overriding("#{named}_before_type_cast?", name)
|
147
154
|
|
148
|
-
|
149
|
-
|
150
|
-
generated_methods.module_eval do
|
151
|
-
define_method(named) { read_attribute(named) }
|
152
|
-
define_method("#{named}?") do
|
153
|
-
value = read_attribute(named)
|
154
|
-
case value
|
155
|
-
when true then true
|
156
|
-
when false, nil then false
|
157
|
-
else
|
158
|
-
!value.nil?
|
159
|
-
end
|
160
|
-
end
|
161
|
-
define_method("#{named}=") { |value| write_attribute(named, value) }
|
162
|
-
define_method("#{named}_before_type_cast") { read_attribute_before_type_cast(named) }
|
163
|
-
end
|
155
|
+
Dynamoid::Fields::Declare.new(self, name, type, options).call
|
164
156
|
end
|
165
157
|
|
166
158
|
# Declare a table range key.
|
@@ -273,8 +265,7 @@ module Dynamoid
|
|
273
265
|
options[:timestamps] || (options[:timestamps].nil? && Dynamoid::Config.timestamps)
|
274
266
|
end
|
275
267
|
|
276
|
-
private
|
277
|
-
|
268
|
+
# @private
|
278
269
|
def generated_methods
|
279
270
|
@generated_methods ||= begin
|
280
271
|
Module.new.tap do |mod|
|
@@ -282,12 +273,6 @@ module Dynamoid
|
|
282
273
|
end
|
283
274
|
end
|
284
275
|
end
|
285
|
-
|
286
|
-
def warn_about_method_overriding(method_name, field_name)
|
287
|
-
if instance_methods.include?(method_name.to_sym)
|
288
|
-
Dynamoid.logger.warn("Method #{method_name} generated for the field #{field_name} overrides already existing method")
|
289
|
-
end
|
290
|
-
end
|
291
276
|
end
|
292
277
|
|
293
278
|
# You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
|
@@ -309,6 +294,10 @@ module Dynamoid
|
|
309
294
|
def write_attribute(name, value)
|
310
295
|
name = name.to_sym
|
311
296
|
|
297
|
+
unless attribute_is_present_on_model?(name)
|
298
|
+
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
|
299
|
+
end
|
300
|
+
|
312
301
|
if association = @associations[name]
|
313
302
|
association.reset
|
314
303
|
end
|
@@ -405,5 +394,10 @@ module Dynamoid
|
|
405
394
|
send("#{type}=", self.class.name)
|
406
395
|
end
|
407
396
|
end
|
397
|
+
|
398
|
+
def attribute_is_present_on_model?(attribute_name)
|
399
|
+
setter = "#{attribute_name}=".to_sym
|
400
|
+
respond_to?(setter)
|
401
|
+
end
|
408
402
|
end
|
409
403
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Fields
|
5
|
+
# @private
|
6
|
+
class Declare
|
7
|
+
def initialize(source, name, type, options)
|
8
|
+
@source = source
|
9
|
+
@name = name.to_sym
|
10
|
+
@type = type
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
# Register new field metadata
|
16
|
+
@source.attributes = @source.attributes.merge(
|
17
|
+
@name => { type: @type }.merge(@options)
|
18
|
+
)
|
19
|
+
|
20
|
+
# Should be called before `define_attribute_methods` method because it
|
21
|
+
# defines an attribute getter itself
|
22
|
+
warn_about_method_overriding
|
23
|
+
|
24
|
+
# Dirty API
|
25
|
+
@source.define_attribute_method(@name)
|
26
|
+
|
27
|
+
# Generate getters and setters as well as other helper methods
|
28
|
+
generate_instance_methods
|
29
|
+
|
30
|
+
# If alias name specified - generate the same instance methods
|
31
|
+
if @options[:alias]
|
32
|
+
generate_instance_methods_for_alias
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def warn_about_method_overriding
|
39
|
+
warn_if_method_exists(@name)
|
40
|
+
warn_if_method_exists("#{@name}=")
|
41
|
+
warn_if_method_exists("#{@name}?")
|
42
|
+
warn_if_method_exists("#{@name}_before_type_cast?")
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_instance_methods
|
46
|
+
# only local variable is visible in `module_eval` block
|
47
|
+
name = @name
|
48
|
+
|
49
|
+
@source.generated_methods.module_eval do
|
50
|
+
define_method(name) { read_attribute(name) }
|
51
|
+
define_method("#{name}?") do
|
52
|
+
value = read_attribute(name)
|
53
|
+
case value
|
54
|
+
when true then true
|
55
|
+
when false, nil then false
|
56
|
+
else
|
57
|
+
!value.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
define_method("#{name}=") { |value| write_attribute(name, value) }
|
61
|
+
define_method("#{name}_before_type_cast") { read_attribute_before_type_cast(name) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_instance_methods_for_alias
|
66
|
+
# only local variable is visible in `module_eval` block
|
67
|
+
name = @name
|
68
|
+
|
69
|
+
alias_name = @options[:alias].to_sym
|
70
|
+
|
71
|
+
@source.generated_methods.module_eval do
|
72
|
+
alias_method alias_name, name
|
73
|
+
alias_method "#{alias_name}=", "#{name}="
|
74
|
+
alias_method "#{alias_name}?", "#{name}?"
|
75
|
+
alias_method "#{alias_name}_before_type_cast", "#{name}_before_type_cast"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def warn_if_method_exists(method)
|
80
|
+
if @source.instance_methods.include?(method.to_sym)
|
81
|
+
Dynamoid.logger.warn("Method #{method} generated for the field #{@name} overrides already existing method")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/dynamoid/indexes.rb
CHANGED
@@ -154,6 +154,16 @@ module Dynamoid
|
|
154
154
|
index
|
155
155
|
end
|
156
156
|
|
157
|
+
# Returns an index by its name
|
158
|
+
#
|
159
|
+
# @param name [string, symbol] the name of the index to lookup
|
160
|
+
# @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
|
161
|
+
def find_index_by_name(name)
|
162
|
+
string_name = name.to_s
|
163
|
+
indexes.each_value.detect{ |i| i.name.to_s == string_name }
|
164
|
+
end
|
165
|
+
|
166
|
+
|
157
167
|
# Returns true iff the provided hash[,range] key combo is a local
|
158
168
|
# secondary index.
|
159
169
|
#
|
data/lib/dynamoid/loadable.rb
CHANGED
@@ -10,7 +10,7 @@ module Dynamoid
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
# Reload an object from the database -- if you suspect the object has changed in the
|
13
|
+
# Reload an object from the database -- if you suspect the object has changed in the data store and you need those
|
14
14
|
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
15
15
|
#
|
16
16
|
# @return [Dynamoid::Document] the document this method was called on
|
@@ -23,7 +23,7 @@ module Dynamoid
|
|
23
23
|
options[:range_key] = range_value
|
24
24
|
end
|
25
25
|
|
26
|
-
self.attributes = self.class.find(hash_key, options).attributes
|
26
|
+
self.attributes = self.class.find(hash_key, **options).attributes
|
27
27
|
@associations.values.each(&:reset)
|
28
28
|
self
|
29
29
|
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -8,11 +8,12 @@ require 'dynamoid/persistence/import'
|
|
8
8
|
require 'dynamoid/persistence/update_fields'
|
9
9
|
require 'dynamoid/persistence/upsert'
|
10
10
|
require 'dynamoid/persistence/save'
|
11
|
+
require 'dynamoid/persistence/update_validations'
|
11
12
|
|
12
13
|
# encoding: utf-8
|
13
14
|
module Dynamoid
|
14
|
-
#
|
15
|
-
#
|
15
|
+
# Persistence is responsible for dumping objects to and marshalling objects from the data store. It tries to reserialize
|
16
|
+
# values to be of the same type as when they were passed in, based on the fields in the class.
|
16
17
|
module Persistence
|
17
18
|
extend ActiveSupport::Concern
|
18
19
|
|
@@ -111,7 +112,7 @@ module Dynamoid
|
|
111
112
|
|
112
113
|
if created_successfuly && self.options[:expires]
|
113
114
|
attribute = self.options[:expires][:field]
|
114
|
-
Dynamoid.adapter.update_time_to_live(table_name
|
115
|
+
Dynamoid.adapter.update_time_to_live(table_name, attribute)
|
115
116
|
end
|
116
117
|
end
|
117
118
|
|
@@ -276,6 +277,9 @@ module Dynamoid
|
|
276
277
|
# +update_fields+ uses the +UpdateItem+ operation so it saves changes and
|
277
278
|
# loads an updated document back with one HTTP request.
|
278
279
|
#
|
280
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
281
|
+
# attributes is not on the model
|
282
|
+
#
|
279
283
|
# @param hash_key_value [Scalar value] hash key
|
280
284
|
# @param range_key_value [Scalar value] range key (optional)
|
281
285
|
# @param attrs [Hash]
|
@@ -324,6 +328,9 @@ module Dynamoid
|
|
324
328
|
# +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
|
325
329
|
# an updated document back with one HTTP request.
|
326
330
|
#
|
331
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
332
|
+
# attributes is not on the model
|
333
|
+
#
|
327
334
|
# @param hash_key_value [Scalar value] hash key
|
328
335
|
# @param range_key_value [Scalar value] range key (optional)
|
329
336
|
# @param attrs [Hash]
|
@@ -491,6 +498,9 @@ module Dynamoid
|
|
491
498
|
#
|
492
499
|
# user.update_attributes(age: 27, last_name: 'Tylor')
|
493
500
|
#
|
501
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
502
|
+
# attributes is not on the model
|
503
|
+
#
|
494
504
|
# @param attributes [Hash] a hash of attributes to update
|
495
505
|
# @return [true|false] Whether updating successful or not
|
496
506
|
# @since 0.2.0
|
@@ -507,6 +517,9 @@ module Dynamoid
|
|
507
517
|
# Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
|
508
518
|
# fails.
|
509
519
|
#
|
520
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
521
|
+
# attributes is not on the model
|
522
|
+
#
|
510
523
|
# @param attributes [Hash] a hash of attributes to update
|
511
524
|
def update_attributes!(attributes)
|
512
525
|
attributes.each { |attribute, value| write_attribute(attribute, value) }
|
@@ -519,6 +532,9 @@ module Dynamoid
|
|
519
532
|
#
|
520
533
|
# user.update_attribute(:last_name, 'Tylor')
|
521
534
|
#
|
535
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
536
|
+
# attributes is not on the model
|
537
|
+
#
|
522
538
|
# @param attribute [Symbol] attribute name to update
|
523
539
|
# @param value [Object] the value to assign it
|
524
540
|
# @return [Dynamoid::Document] self
|
@@ -786,6 +802,10 @@ module Dynamoid
|
|
786
802
|
@destroyed = true
|
787
803
|
|
788
804
|
Dynamoid.adapter.delete(self.class.table_name, hash_key, options)
|
805
|
+
|
806
|
+
self.class.associations.each do |name, options|
|
807
|
+
send(name).disassociate_source
|
808
|
+
end
|
789
809
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
790
810
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
|
791
811
|
end
|
@@ -4,8 +4,8 @@ module Dynamoid
|
|
4
4
|
module Persistence
|
5
5
|
# @private
|
6
6
|
class UpdateFields
|
7
|
-
def self.call(*args)
|
8
|
-
new(*args).call
|
7
|
+
def self.call(*args, **options)
|
8
|
+
new(*args, **options).call
|
9
9
|
end
|
10
10
|
|
11
11
|
def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
|
@@ -17,6 +17,8 @@ module Dynamoid
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def call
|
20
|
+
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
21
|
+
|
20
22
|
if Dynamoid::Config.timestamps
|
21
23
|
@attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
|
22
24
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Persistence
|
5
|
+
# @private
|
6
|
+
module UpdateValidations
|
7
|
+
def self.validate_attributes_exist(model_class, attributes)
|
8
|
+
model_attributes = model_class.attributes.keys
|
9
|
+
|
10
|
+
attributes.each do |attr_name, _|
|
11
|
+
unless model_attributes.include?(attr_name)
|
12
|
+
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{attr_name} does not exist in #{model_class}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -4,8 +4,8 @@ module Dynamoid
|
|
4
4
|
module Persistence
|
5
5
|
# @private
|
6
6
|
class Upsert
|
7
|
-
def self.call(*args)
|
8
|
-
new(*args).call
|
7
|
+
def self.call(*args, **options)
|
8
|
+
new(*args, **options).call
|
9
9
|
end
|
10
10
|
|
11
11
|
def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
|
@@ -17,6 +17,8 @@ module Dynamoid
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def call
|
20
|
+
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
21
|
+
|
20
22
|
if Dynamoid::Config.timestamps
|
21
23
|
@attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
|
22
24
|
end
|
data/lib/dynamoid/validations.rb
CHANGED
@@ -50,7 +50,10 @@ module Dynamoid
|
|
50
50
|
class PresenceValidator < ActiveModel::EachValidator
|
51
51
|
# Validate the record for the record and value.
|
52
52
|
def validate_each(record, attr_name, value)
|
53
|
-
|
53
|
+
# Use keyword argument `options` because it was a Hash in Rails < 6.1
|
54
|
+
# and became a keyword argument in 6.1. This way it works in both
|
55
|
+
# cases.
|
56
|
+
record.errors.add(attr_name, :blank, **options) if not_present?(value)
|
54
57
|
end
|
55
58
|
|
56
59
|
private
|
data/lib/dynamoid/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamoid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Symonds
|
@@ -18,10 +18,10 @@ authors:
|
|
18
18
|
- Brian Glusman
|
19
19
|
- Peter Boling
|
20
20
|
- Andrew Konchin
|
21
|
-
autorequire:
|
21
|
+
autorequire:
|
22
22
|
bindir: bin
|
23
23
|
cert_chain: []
|
24
|
-
date:
|
24
|
+
date: 2021-02-02 00:00:00.000000000 Z
|
25
25
|
dependencies:
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activemodel
|
@@ -244,6 +244,7 @@ files:
|
|
244
244
|
- lib/dynamoid/dynamodb_time_zone.rb
|
245
245
|
- lib/dynamoid/errors.rb
|
246
246
|
- lib/dynamoid/fields.rb
|
247
|
+
- lib/dynamoid/fields/declare.rb
|
247
248
|
- lib/dynamoid/finders.rb
|
248
249
|
- lib/dynamoid/identity_map.rb
|
249
250
|
- lib/dynamoid/indexes.rb
|
@@ -254,6 +255,7 @@ files:
|
|
254
255
|
- lib/dynamoid/persistence/import.rb
|
255
256
|
- lib/dynamoid/persistence/save.rb
|
256
257
|
- lib/dynamoid/persistence/update_fields.rb
|
258
|
+
- lib/dynamoid/persistence/update_validations.rb
|
257
259
|
- lib/dynamoid/persistence/upsert.rb
|
258
260
|
- lib/dynamoid/primary_key_type_mapping.rb
|
259
261
|
- lib/dynamoid/railtie.rb
|
@@ -269,10 +271,10 @@ licenses:
|
|
269
271
|
- MIT
|
270
272
|
metadata:
|
271
273
|
bug_tracker_uri: https://github.com/Dynamoid/dynamoid/issues
|
272
|
-
changelog_uri: https://github.com/Dynamoid/dynamoid/tree/v3.
|
273
|
-
source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.
|
274
|
-
documentation_uri: https://rubydoc.info/gems/dynamoid/3.
|
275
|
-
post_install_message:
|
274
|
+
changelog_uri: https://github.com/Dynamoid/dynamoid/tree/v3.7.0/CHANGELOG.md
|
275
|
+
source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.7.0
|
276
|
+
documentation_uri: https://rubydoc.info/gems/dynamoid/3.7.0
|
277
|
+
post_install_message:
|
276
278
|
rdoc_options: []
|
277
279
|
require_paths:
|
278
280
|
- lib
|
@@ -287,8 +289,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
287
289
|
- !ruby/object:Gem::Version
|
288
290
|
version: '0'
|
289
291
|
requirements: []
|
290
|
-
rubygems_version: 3.
|
291
|
-
signing_key:
|
292
|
+
rubygems_version: 3.2.0
|
293
|
+
signing_key:
|
292
294
|
specification_version: 4
|
293
295
|
summary: Dynamoid is an ORM for Amazon's DynamoDB
|
294
296
|
test_files: []
|