如何让 shoulda-matchers 为字段使用特定的一组值?
How to make shoulda-matchers to use specific set of values for a field?
我有一个优惠券模型,它可以有多种货币和相同的优惠券代码。
所以我这样验证它:
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"]
如何让 shoulda-matches 只使用真正的货币代码,例如 FFaker::Currency
或预定义的列表?
我深入研究了 shoulda 代码,发现它使用了 validate_uniqueness_of
的预定义值集,并且无法更改它(除了黑客攻击 Shoulda::Matchers::Util
class)。
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
它用正确的值验证唯一性,并且像魅力一样工作。
我有一个优惠券模型,它可以有多种货币和相同的优惠券代码。
所以我这样验证它:
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"]
如何让 shoulda-matches 只使用真正的货币代码,例如 FFaker::Currency
或预定义的列表?
我深入研究了 shoulda 代码,发现它使用了 validate_uniqueness_of
的预定义值集,并且无法更改它(除了黑客攻击 Shoulda::Matchers::Util
class)。
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
它用正确的值验证唯一性,并且像魅力一样工作。