Как заставить сопоставители использовать определенный набор значений для поля?

У меня есть модель купона, которая может иметь несколько валют с одним и тем же кодом купона.

Поэтому я проверяю это так:

validates :code, presence: true, uniqueness: { case_sensitive: false, scope: :currency }, length: { in: 1..40 }

и протестировать так:

it { should validate_uniqueness_of(:code).case_insensitive.scoped_to(:currency) }

Но также купон переписывает атрибут валюты, чтобы принять как символ валюты ($), так и код ISO (USD). Он переписывает символ в соответствующий код ISO:

# Convert currency symbols to ISO code
def currency=(value)
  return write_attribute(:currency, nil) if value.blank?
  write_attribute(:currency, currency_to_iso(value.strip))
end

def currency_to_iso(symbol_or_iso)
  return unless symbol_or_iso
  (Money::Currency.find(symbol_or_iso) || Money::Currency.find_by_symbol(symbol_or_iso)).try(:iso_code)
end

Если код валюты неверен, он преобразуется в nil.

Итак, shoulda-matchers выдает ошибку:

   Coupon did not properly validate that :code is case-insensitively unique
   within the scope of :currency.
     After taking the given Coupon, whose :code is ‹"t8u"›, and saving it
     as the existing record, then making a new Coupon and setting its :code
     to ‹"t8u"› as well and its :currency to a different value, ‹"dummy
     value"› (read back as ‹nil›), the matcher expected the new Coupon to
     be valid, but it was invalid instead, producing these validation
     errors:

     * code: ["has already been taken"]

Как заставить совпадения использовать только реальные коды валюты, такие как FFaker::Currency или предопределенный список?


person Peter Adrianov    schedule 04.03.2016    source источник


Ответы (1)


Я погрузился в код shoulda и обнаружил, что он использует предопределенный набор значений для validate_uniqueness_of и нет никакого способа изменить его (кроме взлома класса Shoulda::Matchers::Util).

def self.dummy_value_for(column_type, array: false)
  if array
    [dummy_value_for(column_type, array: false)]
  else
    case column_type
    when :integer
      0
    when :date
      Date.new(2100, 1, 1)
    when :datetime, :timestamp
      DateTime.new(2100, 1, 1)
    when :time
      Time.new(2100, 1, 1)
    when :uuid
      SecureRandom.uuid
    when :boolean
      true
    else
      'dummy value'
    end
  end
end

Поэтому я построил следующий обходной путь:

context 'coupon code should be unique for the same currency' do
  before { create(:coupon_amount, currency: 'USD', code: 'aaa') }
  it 'allows to create a coupon with the same code and another currency' do
    create(:coupon_amount, currency: 'EUR', code: 'aaa')
  end
  it 'does not allow to create a coupon with the same code and currency' do
    expect { create(:coupon_amount, currency: 'USD', code: 'aaa') }.to(
      raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Code has already been taken')
    )
  end
end

Он подтверждает уникальность правильными ценностями и работает как шарм.

person Peter Adrianov    schedule 04.03.2016