Skip to content

Commit ff2342f

Browse files
committed
@wip Implement class method delete()
1 parent 9bfec3c commit ff2342f

File tree

2 files changed

+199
-1
lines changed

2 files changed

+199
-1
lines changed

lib/dynamoid/persistence.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,90 @@ def inc(hash_key_value, range_key_value = nil, counters)
455455
Inc.call(self, hash_key_value, range_key_value, counters)
456456
self
457457
end
458+
459+
# Delete a model.
460+
#
461+
# Delete a model by given partition key:
462+
#
463+
# User.delete(user_id)
464+
#
465+
# Delete a model by given partition and sort keys:
466+
#
467+
# User.delete(user_id, sort_key)
468+
#
469+
# Delete multiple models by given partition keys:
470+
#
471+
# User.delete([id1, id2, id3])
472+
#
473+
# Delete multiple models by given partition and sort keys:
474+
#
475+
# User.delete([[id1, sk1], [id2, sk2], [id3, sk3]])
476+
#
477+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
478+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
479+
# required but has value +nil+ or is missing.
480+
#
481+
# @return nil
482+
def delete(*args)
483+
if args.empty?
484+
raise Dynamoid::Errors::MissingHashKey
485+
end
486+
487+
# TODO: warn if passed excessive arguments
488+
# TODO: handle a case when sort key isn't declared but given in arguments
489+
if args[0].is_a?(Array)
490+
# given multiple keys:
491+
keys = args[0] # ignore other arguments
492+
493+
494+
if keys[0].is_a?(Array)
495+
# compound primary key
496+
497+
ids = []
498+
range_key = []
499+
500+
# assume all elements are pairs, that's arrays
501+
keys.each do |pk, sk|
502+
raise Dynamoid::Errors::MissingHashKey if pk.nil?
503+
raise Dynamoid::Errors::MissingRangeKey if self.range_key? && sk.nil?
504+
505+
partition_key_dumped = Dumping.dump_field(pk, self.attributes[self.hash_key])
506+
sort_key_dumped = Dumping.dump_field(sk, self.attributes[self.range_key])
507+
508+
ids << partition_key_dumped
509+
range_key << sort_key_dumped
510+
end
511+
else
512+
# simple primary key
513+
514+
ids = []
515+
range_key = nil
516+
517+
keys.each do |pk|
518+
raise Dynamoid::Errors::MissingHashKey if pk.nil?
519+
520+
partition_key_dumped = Dumping.dump_field(pk, self.attributes[self.hash_key])
521+
ids << partition_key_dumped
522+
end
523+
end
524+
525+
options = range_key ? { range_key: range_key } : {}
526+
Dynamoid.adapter.delete(self.table_name, ids, options)
527+
else
528+
# Model.delete(partition_key, sort_key)
529+
partition_key, sort_key = args
530+
531+
raise Dynamoid::Errors::MissingHashKey if partition_key.nil?
532+
raise Dynamoid::Errors::MissingRangeKey if self.range_key? && sort_key.nil?
533+
534+
options = sort_key ? { range_key: Dumping.dump_field(sort_key, self.attributes[self.range_key]) } : {}
535+
partition_key_dumped = Dumping.dump_field(partition_key, self.attributes[self.hash_key])
536+
537+
Dynamoid.adapter.delete(self.table_name, partition_key_dumped, options)
538+
end
539+
540+
nil
541+
end
458542
end
459543

460544
# Update document timestamps.

spec/dynamoid/persistence/delete_spec.rb

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,121 @@
44
require 'fixtures/persistence'
55

66
RSpec.describe Dynamoid::Persistence do
7-
describe 'delete' do
7+
describe '.delete' do
8+
let(:klass_with_composite_key) do
9+
new_class do
10+
range :age, :integer
11+
field :name
12+
end
13+
end
14+
15+
it 'deletes an item' do
16+
klass = new_class
17+
obj = klass.create!
18+
19+
expect { klass.delete(obj.id) }.to change { klass.exists? obj.id }.from(true).to(false)
20+
end
21+
22+
it 'deletes multiple items when given multiple partition keys' do
23+
klass = new_class
24+
obj1 = klass.create!
25+
obj2 = klass.create!
26+
27+
klass.delete([obj1.id, obj2.id])
28+
29+
expect(klass.exists?(obj1.id)).to eql(false)
30+
expect(klass.exists?(obj2.id)).to eql(false)
31+
end
32+
33+
it 'deletes multiple items when given multiple partition and sort keys' do
34+
obj1 = klass_with_composite_key.create!(age: 1)
35+
obj2 = klass_with_composite_key.create!(age: 2)
36+
37+
klass_with_composite_key.delete([[obj1.id, obj1.age], [obj2.id, obj2.age]])
38+
39+
expect(klass_with_composite_key.exists?([[obj1.id, obj1.age]])).to eql(false)
40+
expect(klass_with_composite_key.exists?([[obj2.id, obj2.age]])).to eql(false)
41+
end
42+
43+
it 'uses dumped value of partition key to delete item' do
44+
klass = new_class(partition_key: { name: :published_on, type: :date })
45+
46+
obj = klass.create!(published_on: '2018-10-07'.to_date)
47+
48+
expect {
49+
klass.delete(obj.published_on)
50+
}.to change {
51+
klass.where(published_on: obj.published_on).first
52+
}.to(nil)
53+
end
54+
55+
it 'uses dumped value of sort key to delete item' do
56+
klass = new_class do
57+
range :activated_on, :date
58+
end
59+
60+
obj = klass.create!(activated_on: Date.today)
61+
62+
expect {
63+
klass.delete(obj.id, obj.activated_on)
64+
}.to change {
65+
klass.where(id: obj.id, activated_on: obj.activated_on).first
66+
}.to(nil)
67+
end
68+
69+
it 'does not raise exception when model was concurrently deleted' do
70+
klass = new_class
71+
klass.create_table
72+
73+
expect(klass.delete("not-existing-id")).to eql(nil)
74+
end
75+
76+
describe 'primary key validation' do
77+
context 'simple primary key' do
78+
it 'requires partition key to be specified' do
79+
klass = new_class
80+
expect { klass.delete(nil) }.to raise_exception(Dynamoid::Errors::MissingHashKey)
81+
end
82+
end
83+
84+
context 'composite key' do
85+
it 'requires partition key to be specified' do
86+
expect { klass_with_composite_key.delete(nil, 1) }.to raise_exception(Dynamoid::Errors::MissingHashKey)
87+
end
88+
89+
it 'requires sort key to be specified' do
90+
expect { klass_with_composite_key.delete("abc", nil) }.to raise_exception(Dynamoid::Errors::MissingRangeKey)
91+
expect { klass_with_composite_key.delete("abc") }.to raise_exception(Dynamoid::Errors::MissingRangeKey)
92+
end
93+
end
94+
end
95+
96+
context 'when table arn is specified', remove_constants: [:Payment] do
97+
it 'uses given table ARN in requests instead of a table name', config: { create_table_on_save: false } do
98+
# Create table manually because CreateTable doesn't accept ARN as a
99+
# table name. Add namespace to have this table removed automativally.
100+
table_name = :"#{Dynamoid::Config.namespace}_purchases"
101+
Dynamoid.adapter.create_table(table_name, :id)
102+
103+
table = Dynamoid.adapter.describe_table(table_name)
104+
expect(table.arn).to be_present
105+
106+
Payment = Class.new do # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
107+
include Dynamoid::Document
108+
109+
table arn: table.arn
110+
end
111+
112+
payment = Payment.create!
113+
114+
expect {
115+
Payment.delete(payment.id)
116+
}.to send_request_matching(:DeleteItem, { TableName: table.arn })
117+
end
118+
end
119+
end
120+
121+
describe '#delete' do
8122
let(:klass_with_composite_key) do
9123
new_class do
10124
range :age, :integer

0 commit comments

Comments
 (0)