Skip to content

Commit bf054a9

Browse files
committed
Fix transactional operations and dump partition/sort keys when they are
of non-native DynamoDB type
1 parent 2a3a9dd commit bf054a9

File tree

13 files changed

+438
-12
lines changed

13 files changed

+438
-12
lines changed

lib/dynamoid/transaction_write/delete_with_instance.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ def observable_by_user_result
3535
end
3636

3737
def action_request
38-
key = { @model_class.hash_key => @model.hash_key }
38+
key = { @model_class.hash_key => dump_attribute(@model_class.hash_key, @model.hash_key) }
3939

4040
if @model_class.range_key?
41-
key[@model_class.range_key] = @model.range_value
41+
key[@model_class.range_key] = dump_attribute(@model_class.range_key, @model.range_value)
4242
end
4343

4444
{
@@ -55,6 +55,11 @@ def validate_model!
5555
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
5656
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
5757
end
58+
59+
def dump_attribute(name, value)
60+
options = @model_class.attributes[name]
61+
Dumping.dump_field(value, options)
62+
end
5863
end
5964
end
6065
end

lib/dynamoid/transaction_write/delete_with_primary_key.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ def observable_by_user_result
3434
end
3535

3636
def action_request
37-
key = { @model_class.hash_key => @hash_key }
37+
key = { @model_class.hash_key => dump_attribute(@model_class.hash_key, @hash_key) }
3838

3939
if @model_class.range_key?
40-
key[@model_class.range_key] = @range_key
40+
key[@model_class.range_key] = dump_attribute(@model_class.range_key, @range_key)
4141
end
4242

4343
{
@@ -54,6 +54,11 @@ def validate_primary_key!
5454
raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
5555
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
5656
end
57+
58+
def dump_attribute(name, value)
59+
options = @model_class.attributes[name]
60+
Dumping.dump_field(value, options)
61+
end
5762
end
5863
end
5964
end

lib/dynamoid/transaction_write/destroy.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ def observable_by_user_result
5454
end
5555

5656
def action_request
57-
key = { @model_class.hash_key => @model.hash_key }
57+
key = { @model_class.hash_key => dump_attribute(@model_class.hash_key, @model.hash_key) }
5858

5959
if @model_class.range_key?
60-
key[@model_class.range_key] = @model.range_value
60+
key[@model_class.range_key] = dump_attribute(@model_class.range_key, @model.range_value)
6161
end
6262

6363
{
@@ -74,6 +74,11 @@ def validate_model!
7474
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
7575
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
7676
end
77+
78+
def dump_attribute(name, value)
79+
options = @model_class.attributes[name]
80+
Dumping.dump_field(value, options)
81+
end
7782
end
7883
end
7984
end

lib/dynamoid/transaction_write/save.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ def action_request_to_update
121121
changes_dumped = Dynamoid::Dumping.dump_attributes(changes, @model_class.attributes)
122122

123123
# primary key to look up an item to update
124-
key = { @model_class.hash_key => @model.hash_key }
125-
key[@model_class.range_key] = @model.range_value if @model_class.range_key?
124+
key = { @model_class.hash_key => dump_attribute(@model_class.hash_key, @model.hash_key) }
125+
key[@model_class.range_key] = dump_attribute(@model_class.range_key, @model.range_value) if @model_class.range_key?
126126

127127
# Build UpdateExpression and keep names and values placeholders mapping
128128
# in ExpressionAttributeNames and ExpressionAttributeValues.
@@ -159,6 +159,11 @@ def touch_model_timestamps(skip_created_at:)
159159
@model.updated_at = timestamp unless @options[:touch] == false && !@was_new_record
160160
@model.created_at ||= timestamp unless skip_created_at
161161
end
162+
163+
def dump_attribute(name, value)
164+
options = @model_class.attributes[name]
165+
Dumping.dump_field(value, options)
166+
end
162167
end
163168
end
164169
end

lib/dynamoid/transaction_write/update_fields.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ def action_request
4646
builder = UpdateRequestBuilder.new(@model_class)
4747

4848
# primary key to look up an item to update
49-
builder.hash_key = @hash_key
50-
builder.range_key = @range_key if @model_class.range_key?
49+
builder.hash_key = dump_attribute(@model_class.hash_key, @hash_key)
50+
builder.range_key = dump_attribute(@model_class.range_key, @range_key) if @model_class.range_key?
5151

5252
# changed attributes to persist
5353
changes = @attributes.dup
@@ -111,6 +111,11 @@ def add_timestamps(attributes, skip_created_at: false)
111111
result
112112
end
113113

114+
def dump_attribute(name, value)
115+
options = @model_class.attributes[name]
116+
Dumping.dump_field(value, options)
117+
end
118+
114119
class UpdateRequestBuilder
115120
attr_writer :hash_key, :range_key, :condition_expression
116121

lib/dynamoid/transaction_write/upsert.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@ def action_request
4444
changes_dumped = Dynamoid::Dumping.dump_attributes(changes, @model_class.attributes)
4545

4646
# primary key to look up an item to update
47-
key = { @model_class.hash_key => @hash_key }
48-
key[@model_class.range_key] = @range_key if @model_class.range_key?
47+
partition_key_dumped = dump(@model_class.hash_key, @hash_key)
48+
key = { @model_class.hash_key => partition_key_dumped }
49+
50+
if @model_class.range_key?
51+
sort_key_dumped = dump(@model_class.range_key, @range_key)
52+
key[@model_class.range_key] = sort_key_dumped
53+
end
4954

5055
# Build UpdateExpression and keep names and values placeholders mapping
5156
# in ExpressionAttributeNames and ExpressionAttributeValues.
@@ -91,6 +96,11 @@ def add_timestamps(attributes, skip_created_at: false)
9196
result[:updated_at] ||= timestamp
9297
result
9398
end
99+
100+
def dump(name, value)
101+
options = @model_class.attributes[name]
102+
Dumping.dump_field(value, options)
103+
end
94104
end
95105
end
96106
end

spec/dynamoid/transaction_write/create_spec.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,35 @@ def around_save_callback
356356
expect(obj).to be_changed
357357
end
358358

359+
it 'uses dumped value of partition key to create item' do
360+
klass = new_class(partition_key: { name: :published_on, type: :date }) do
361+
field :name
362+
end
363+
klass.create_table
364+
365+
described_class.execute do |txn|
366+
txn.create klass, published_on: '2018-10-07'.to_date, name: 'Alex'
367+
end
368+
369+
obj = klass.last
370+
expect(obj.name).to eql 'Alex'
371+
end
372+
373+
it 'uses dumped value of sort key to create item' do
374+
klass = new_class do
375+
range :activated_on, :date
376+
field :name
377+
end
378+
klass.create_table
379+
380+
described_class.execute do |txn|
381+
txn.create klass, activated_on: Date.today, name: 'Alex'
382+
end
383+
384+
obj = klass.last
385+
expect(obj.name).to eql 'Alex'
386+
end
387+
359388
describe 'callbacks' do
360389
before do
361390
ScratchPad.clear
@@ -699,4 +728,33 @@ def around_save_callback
699728
expect(obj_to_save).not_to be_persisted
700729
expect(obj_to_save).to be_changed
701730
end
731+
732+
it 'uses dumped value of partition key to create item' do
733+
klass = new_class(partition_key: { name: :published_on, type: :date }) do
734+
field :name
735+
end
736+
klass.create_table
737+
738+
described_class.execute do |txn|
739+
txn.create! klass, published_on: '2018-10-07'.to_date, name: 'Alex'
740+
end
741+
742+
obj = klass.last
743+
expect(obj.name).to eql 'Alex'
744+
end
745+
746+
it 'uses dumped value of sort key to create item' do
747+
klass = new_class do
748+
range :activated_on, :date
749+
field :name
750+
end
751+
klass.create_table
752+
753+
described_class.execute do |txn|
754+
txn.create! klass, activated_on: Date.today, name: 'Alex'
755+
end
756+
757+
obj = klass.last
758+
expect(obj.name).to eql 'Alex'
759+
end
702760
end

spec/dynamoid/transaction_write/delete_spec.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,37 @@
137137
expect(obj).not_to be_destroyed
138138
end
139139

140+
it 'uses dumped value of partition key to delete item' do
141+
klass = new_class(partition_key: { name: :published_on, type: :date }) do
142+
field :name
143+
end
144+
obj = klass.create!(published_on: '2018-10-07'.to_date, name: 'Alex')
145+
146+
expect {
147+
described_class.execute do |txn|
148+
txn.delete obj
149+
end
150+
}.to change(klass, :count).by(-1)
151+
152+
expect(klass.exists?(obj.published_on)).to eql false
153+
end
154+
155+
it 'uses dumped value of sort key to delete item' do
156+
klass = new_class do
157+
range :activated_on, :date
158+
field :name
159+
end
160+
obj = klass.create!(activated_on: Date.today, name: 'Alex')
161+
162+
expect {
163+
described_class.execute do |txn|
164+
txn.delete obj
165+
end
166+
}.to change(klass, :count).by(-1)
167+
168+
expect(klass.exists?([[obj.id, obj.activated_on]])).to eql false
169+
end
170+
140171
describe 'callbacks' do
141172
it 'does not run any callback' do
142173
klass_with_callbacks = new_class do
@@ -285,6 +316,37 @@ def around_destroy_callback
285316
end
286317
end
287318

319+
it 'uses dumped value of partition key to delete item' do
320+
klass = new_class(partition_key: { name: :published_on, type: :date }) do
321+
field :name
322+
end
323+
obj = klass.create!(published_on: '2018-10-07'.to_date, name: 'Alex')
324+
325+
expect {
326+
described_class.execute do |txn|
327+
txn.delete klass, obj.published_on
328+
end
329+
}.to change(klass, :count).by(-1)
330+
331+
expect(klass.exists?(obj.published_on)).to eql false
332+
end
333+
334+
it 'uses dumped value of sort key to delete item' do
335+
klass = new_class do
336+
range :activated_on, :date
337+
field :name
338+
end
339+
obj = klass.create!(activated_on: Date.today, name: 'Alex')
340+
341+
expect {
342+
described_class.execute do |txn|
343+
txn.delete klass, obj.id, obj.activated_on
344+
end
345+
}.to change(klass, :count).by(-1)
346+
347+
expect(klass.exists?([[obj.id, obj.activated_on]])).to eql false
348+
end
349+
288350
describe 'callbacks' do
289351
it 'does not run any callback' do
290352
klass_with_callbacks = new_class do

spec/dynamoid/transaction_write/destroy_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,37 @@ def around_destroy_callback
197197
expect(obj).not_to be_destroyed
198198
end
199199

200+
it 'uses dumped value of partition key to destroy item' do
201+
klass = new_class(partition_key: { name: :published_on, type: :date }) do
202+
field :name
203+
end
204+
obj = klass.create!(published_on: '2018-10-07'.to_date, name: 'Alex')
205+
206+
expect(klass.exists?(obj.published_on)).to eql true
207+
208+
described_class.execute do |txn|
209+
txn.destroy obj
210+
end
211+
212+
expect(klass.exists?(obj.published_on)).to eql false
213+
end
214+
215+
it 'uses dumped value of sort key to destroy item' do
216+
klass = new_class do
217+
range :activated_on, :date
218+
field :name
219+
end
220+
obj = klass.create!(activated_on: Date.today, name: 'Alex')
221+
222+
expect(klass.exists?([[obj.id, obj.activated_on]])).to eql true
223+
224+
described_class.execute do |txn|
225+
txn.destroy obj
226+
end
227+
228+
expect(klass.exists?([[obj.id, obj.activated_on]])).to eql false
229+
end
230+
200231
describe 'callbacks' do
201232
before do
202233
ScratchPad.clear

0 commit comments

Comments
 (0)