Skip to content

Commit

Permalink
Validate primary key in #save
Browse files Browse the repository at this point in the history
  • Loading branch information
andrykonchin committed Nov 1, 2024
1 parent 41232c2 commit 6ad9e13
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 8 deletions.
7 changes: 7 additions & 0 deletions lib/dynamoid/transaction_write/save.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def initialize(model, **options)
end

def on_registration
validate_model!

unless @valid = @model.valid?
if @options[:raise_error]
raise Dynamoid::Errors::DocumentNotValid, @model
Expand Down Expand Up @@ -72,6 +74,11 @@ def action_request

private

def validate_model!
raise Dynamoid::Errors::MissingHashKey if !@was_new_record && @model.hash_key.nil?
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
end

def action_request_to_create
@model.hash_key = SecureRandom.uuid if @model.hash_key.nil?
touch_model_timestamps(skip_created_at: false)
Expand Down
14 changes: 14 additions & 0 deletions spec/dynamoid/transaction_write/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ def around_save_callback
end
end

describe 'primary key validation' do
context 'composite key' do
it 'requires sort key to be specified' do
klass_with_composite_key.create_table

expect {
described_class.execute do |txn|
txn.create klass_with_composite_key, name: 'Alex', age: nil
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end

describe 'timestamps' do
before do
klass.create_table
Expand Down
39 changes: 39 additions & 0 deletions spec/dynamoid/transaction_write/delete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,45 @@
end
end

describe 'primary key validation' do
context 'simple primary key' do
it 'requires partition key to be specified' do
obj = klass.create!(name: 'one')
obj.id = nil

expect {
described_class.execute do |txn|
txn.delete obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
it 'requires partition key to be specified' do
obj = klass_with_composite_key.create!(name: 'one', age: 1)
obj.id = nil

expect {
described_class.execute do |txn|
txn.delete obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end

it 'requires sort key to be specified' do
obj = klass_with_composite_key.create!(name: 'one', age: 1)
obj.age = nil

expect {
described_class.execute do |txn|
txn.delete obj
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end

context 'when an issue detected on the DynamoDB side' do
it 'does not roll back the changes when a model to delete does not exist' do
obj1 = klass.create!(name: 'one', id: '1')
Expand Down
39 changes: 39 additions & 0 deletions spec/dynamoid/transaction_write/destroy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,45 @@ def around_destroy_callback
end
end

describe 'primary key validation' do
context 'simple primary key' do
it 'requires partition key to be specified' do
obj = klass.create!(name: 'one')
obj.id = nil

expect {
described_class.execute do |txn|
txn.destroy obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
it 'requires partition key to be specified' do
obj = klass_with_composite_key.create!(name: 'one', age: 1)
obj.id = nil

expect {
described_class.execute do |txn|
txn.destroy obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end

it 'requires sort key to be specified' do
obj = klass_with_composite_key.create!(name: 'one', age: 1)
obj.age = nil

expect {
described_class.execute do |txn|
txn.destroy obj
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end

context 'when an issue detected on the DynamoDB side' do
it 'does not raise exception and does not roll back the transaction when a model to destroy does not exist' do
obj_to_destroy = klass.create!(name: 'one', id: '1')
Expand Down
58 changes: 58 additions & 0 deletions spec/dynamoid/transaction_write/save_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,64 @@ def around_save_callback
end
end

describe 'primary key validation' do
context 'simple primary key' do
context 'persisted model' do
it 'requires partition key to be specified' do
obj = klass.create!(name: 'Alex')
obj.id = nil
obj.name = 'Alex [Updated]'

expect {
described_class.execute do |txn|
txn.save obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end
end

context 'composite key' do
context 'new model' do
it 'requires sort key to be specified' do
obj = klass_with_composite_key.new name: 'Alex', age: nil

expect {
described_class.execute do |txn|
txn.save obj
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end

context 'persisted model' do
it 'requires partition key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)
obj.id = nil
obj.name = 'Alex [Updated]'

expect {
described_class.execute do |txn|
txn.save obj
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end

it 'requires sort key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)
obj.age = nil
obj.name = 'Alex [Updated]'

expect {
described_class.execute do |txn|
txn.save obj
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end
end

describe 'timestamps' do
context 'new model' do
before do
Expand Down
39 changes: 39 additions & 0 deletions spec/dynamoid/transaction_write/update_attributes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,45 @@ def around_save_callback
end
end

describe 'primary key validation' do
context 'simple primary key' do
it 'requires partition key to be specified' do
obj = klass.create!(name: 'Alex')
obj.id = nil

expect {
described_class.execute do |txn|
txn.update_attributes obj, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
it 'requires partition key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)
obj.id = nil

expect {
described_class.execute do |txn|
txn.update_attributes obj, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end

it 'requires sort key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)
obj.age = nil

expect {
described_class.execute do |txn|
txn.update_attributes obj, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end

describe 'timestamps' do
it 'sets updated_at if Config.timestamps=true', config: { timestamps: true } do
obj = klass.create!
Expand Down
36 changes: 36 additions & 0 deletions spec/dynamoid/transaction_write/update_fields_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,42 @@
end
end

describe 'primary key validation' do
context 'simple primary key' do
it 'requires partition key to be specified' do
obj = klass.create!(name: 'Alex')

expect {
described_class.execute do |txn|
txn.update_fields klass_with_composite_key, nil, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
it 'requires partition key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)

expect {
described_class.execute do |txn|
txn.update_fields klass_with_composite_key, nil, 3, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end

it 'requires sort key to be specified' do
obj = klass_with_composite_key.create!(name: 'Alex', age: 3)

expect {
described_class.execute do |txn|
txn.update_fields klass_with_composite_key, obj.id, nil, name: 'Alex [Updated]'
end
}.to raise_exception(Dynamoid::Errors::MissingRangeKey)
end
end
end

describe 'timestamps' do
it 'sets updated_at if Config.timestamps=true', config: { timestamps: true } do
obj = klass.create!
Expand Down
22 changes: 14 additions & 8 deletions spec/dynamoid/transaction_write/upsert_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,6 @@
end
}.to change { klass.find(obj.id).name }.to('Alex [Updated]')
end

it 'requires partition key to be specified' do
expect {
described_class.execute do |txn|
txn.upsert klass, nil, { name: 'threethree' }
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
Expand All @@ -117,7 +109,21 @@
end
}.to change { obj.reload.name }.to('Alex [Updated]')
end
end
end

describe 'primary key validation' do
context 'simple primary key' do
it 'requires partition key to be specified' do
expect {
described_class.execute do |txn|
txn.upsert klass, nil, { name: 'threethree' }
end
}.to raise_exception(Dynamoid::Errors::MissingHashKey)
end
end

context 'composite key' do
it 'requires partition key to be specified' do
expect {
described_class.execute do |txn|
Expand Down

0 comments on commit 6ad9e13

Please sign in to comment.