dynamoid 3.6.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|