Fix unique validator for vouche code

Paranoia doesn't support unique validation including deleted records:
  https://github.com/rubysherpas/paranoia/pull/333
We use a custom validator, ScopedUniquenessValidator to avoid the issue
This commit is contained in:
Gaetan Craig-Riou
2024-11-13 14:01:56 +11:00
committed by Rachel Arnould
parent 9ab2a3ae3d
commit 73819a4638
6 changed files with 68 additions and 4 deletions

View File

@@ -4,6 +4,6 @@ module Vouchers
class FlatRate < Voucher
include FlatRatable
validates :code, uniqueness: { scope: :enterprise_id }
validates_with ScopedUniquenessValidator
end
end

View File

@@ -5,7 +5,7 @@ module Vouchers
validates :amount,
presence: true,
numericality: { greater_than: 0, less_than_or_equal_to: 100 }
validates :code, uniqueness: { scope: :enterprise_id }
validates_with ScopedUniquenessValidator
def display_value
ActionController::Base.helpers.number_to_percentage(amount, precision: 2)

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: false
# paranoia doesn't support unique validation including deleted records:
# https://github.com/rubysherpas/paranoia/pull/333
# We use a custom validator to fix the issue, so we don't need to fork/patch the gem
module Vouchers
class ScopedUniquenessValidator < ActiveModel::Validator
def validate(record)
@record = record
return unless unique_voucher_code_per_enterprise?
record.errors.add :code, :taken, value: @record.code
end
private
def unique_voucher_code_per_enterprise?
query = Voucher.with_deleted.where(code: @record.code, enterprise_id: @record.enterprise_id)
query = query.where.not(id: @record.id) unless @record.id.nil?
query.present?
end
end
end

View File

@@ -8,7 +8,7 @@ RSpec.describe Vouchers::FlatRate do
it { is_expected.to validate_presence_of(:amount) }
it { is_expected.to validate_numericality_of(:amount).is_greater_than(0) }
it { is_expected.to validate_uniqueness_of(:code).scoped_to(:enterprise_id) }
it_behaves_like 'has a unique code per enterprise', "voucher_flat_rate"
end
describe '#compute_amount' do

View File

@@ -12,7 +12,7 @@ RSpec.describe Vouchers::PercentageRate do
.is_greater_than(0)
.is_less_than_or_equal_to(100)
end
it { is_expected.to validate_uniqueness_of(:code).scoped_to(:enterprise_id) }
it_behaves_like 'has a unique code per enterprise', "voucher_percentage_rate"
end
describe '#compute_amount' do

View File

@@ -0,0 +1,39 @@
# frozen_string_literal: true
shared_examples_for 'has a unique code per enterprise' do |voucher_type|
describe "code" do
let(:code) { "super_code" }
let(:enterprise) { create(:enterprise) }
it "is unique per enterprise" do
voucher = create(voucher_type, code:, enterprise:)
expect(voucher).to be_valid
expect_voucher_with_same_enterprise_to_be_invalid(voucher_type)
expect_voucher_with_other_enterprise_to_be_valid(voucher_type)
end
context "with deleted voucher" do
it "is unique per enterprise" do
create(voucher_type, code:, enterprise:).destroy!
expect_voucher_with_same_enterprise_to_be_invalid(voucher_type)
expect_voucher_with_other_enterprise_to_be_valid(voucher_type)
end
end
end
def expect_voucher_with_same_enterprise_to_be_invalid(voucher_type)
new_voucher = build(voucher_type, code:, enterprise: )
expect(new_voucher).not_to be_valid
expect(new_voucher.errors.full_messages).to include("Code has already been taken")
end
def expect_voucher_with_other_enterprise_to_be_valid(voucher_type)
other_voucher = build(voucher_type, code:, enterprise: create(:enterprise) )
expect(other_voucher).to be_valid
end
end