rspec 的自定义唯一性验证错误
Error in custom uniqueness validation for rspec
我正在尝试为狂欢扩展中的自定义验证创建一个 rspec 测试(如 gem)
我需要验证 variants
的唯一性
option values
用于 product
(所有 Spree
型号)
下面是模型的基本结构(虽然是spree的一部分,一个基于rails的电商大厦):
class Product
has_many :variants
has_many :option_values, through: :variants #defined in the spree extension, not in actual spree core
has_many :product_option_types
has_many :option_types, through: :product_option_types
end
class Variant
belongs_to :product, touch: true
has_many :option_values_variants
has_many :option_values, through: option_values
end
class OptionType
has_many :option_values
has_many :product_option_types
has_many :products, through: :product_option_types
end
class OptionValue
belongs_to :option_type
has_many :option_value_variants
has_many :variants, through: :option_value_variants
end
所以我创建了一个自定义验证来检查特定产品的变体选项值的唯一性。那是一个产品(比方说 product1)可以有很多变体。并且具有选项值的变体可以说(红色(Option_type:颜色)和圆形(Option_type:形状))对于该产品必须是唯一的
无论如何这是自定义验证器
validate :uniqueness_of_option_values
def uniqueness_of_option_values
#The problem is in product.variants, When I use it the product.variants collection is returning be empty. And I don't get why.
product.variants.each do |v|
#This part inside the each block doesn't matter though for here.
variant_option_values = v.option_values.ids
this_option_values = option_values.collect(&:id)
matches_with_another_variant = (variant_option_values.length == this_option_values.length) && (variant_option_values - this_option_values).empty?
if !option_values.empty? && !(persisted? && v.id == id) && matches_with_another_variant
errors.add(:base, :already_created)
end
end
end
最后是规格
require 'spec_helper'
describe Spree::Variant do
let(:product) { FactoryBot.create(:product) }
let(:variant1) { FactoryBot.create(:variant, product: product) }
describe "#option_values" do
context "on create" do
before do
@variant2 = FactoryBot.create(:variant, product: product, option_values: variant1.option_values)
end
it "should validate that option values are unique for every variant" do
#This is the main test. This should return false according to my uniqueness validation. But its not since in the custom uniqueness validation method product.variants returns empty and hence its not going inside the each block.
puts @variant2.valid?
expect(true).to be true #just so that the test will pass. Not actually what I want to put here
end
end
end
end
任何人都知道这里出了什么问题。提前致谢
我猜是怎么回事。我认为解决方法是使用以下行更改您的验证:
product.variants.reload.each do |v|
我认为令人高兴的是,当您在测试中调用 variant1
时,它正在 运行 对 variant1 进行验证,这会在产品对象上调用 variants
。这将查询数据库中的相关变体,并得到一个空结果。但是,由于 variant2 具有相同的实际产品对象,该产品对象不会重新查询数据库,并且(错误地)记住它的 variants 是一个空结果。
可能使您的测试 运行 的另一个更改是将您的测试更改如下:
before do
@variant2 = FactoryBot.create(:variant, product_id: product.id, option_values: variant1.option_values)
end
它很微妙,我想知道它是否有效。这会在 variant2 上设置 product_id
字段,但不会将关联的 product
对象设置为与 variant1 具有的实际相同的 product
对象。 (实际上,这更有可能发生在您的实际代码中,即产品对象不在变体对象之间共享。)
您的正确解决方案(如果一切正确)的另一件事是重新加载,但将所有保存代码(和更新代码)放入事务中。这样就不会有两个变体的竞争条件会发生冲突,因为在事务中第一个必须完成验证并在第二个进行验证之前保存,因此它一定会检测到刚刚保存的另一个.
一些建议的调试技巧:
- 如果可能,请查看日志以了解何时进行了查询。您可能已经发现第二次验证没有查询变体。
- 勾选
object_id
。您可能已经发现产品对象实际上是同一个对象。
- 同时检查
new_record?
以确保在测试变体 2 之前已保存变体 1。我认为它确实保存了,但很高兴知道你检查过。
我正在尝试为狂欢扩展中的自定义验证创建一个 rspec 测试(如 gem)
我需要验证 variants
的唯一性
option values
用于 product
(所有 Spree
型号)
下面是模型的基本结构(虽然是spree的一部分,一个基于rails的电商大厦):
class Product
has_many :variants
has_many :option_values, through: :variants #defined in the spree extension, not in actual spree core
has_many :product_option_types
has_many :option_types, through: :product_option_types
end
class Variant
belongs_to :product, touch: true
has_many :option_values_variants
has_many :option_values, through: option_values
end
class OptionType
has_many :option_values
has_many :product_option_types
has_many :products, through: :product_option_types
end
class OptionValue
belongs_to :option_type
has_many :option_value_variants
has_many :variants, through: :option_value_variants
end
所以我创建了一个自定义验证来检查特定产品的变体选项值的唯一性。那是一个产品(比方说 product1)可以有很多变体。并且具有选项值的变体可以说(红色(Option_type:颜色)和圆形(Option_type:形状))对于该产品必须是唯一的
无论如何这是自定义验证器
validate :uniqueness_of_option_values
def uniqueness_of_option_values
#The problem is in product.variants, When I use it the product.variants collection is returning be empty. And I don't get why.
product.variants.each do |v|
#This part inside the each block doesn't matter though for here.
variant_option_values = v.option_values.ids
this_option_values = option_values.collect(&:id)
matches_with_another_variant = (variant_option_values.length == this_option_values.length) && (variant_option_values - this_option_values).empty?
if !option_values.empty? && !(persisted? && v.id == id) && matches_with_another_variant
errors.add(:base, :already_created)
end
end
end
最后是规格
require 'spec_helper'
describe Spree::Variant do
let(:product) { FactoryBot.create(:product) }
let(:variant1) { FactoryBot.create(:variant, product: product) }
describe "#option_values" do
context "on create" do
before do
@variant2 = FactoryBot.create(:variant, product: product, option_values: variant1.option_values)
end
it "should validate that option values are unique for every variant" do
#This is the main test. This should return false according to my uniqueness validation. But its not since in the custom uniqueness validation method product.variants returns empty and hence its not going inside the each block.
puts @variant2.valid?
expect(true).to be true #just so that the test will pass. Not actually what I want to put here
end
end
end
end
任何人都知道这里出了什么问题。提前致谢
我猜是怎么回事。我认为解决方法是使用以下行更改您的验证:
product.variants.reload.each do |v|
我认为令人高兴的是,当您在测试中调用 variant1
时,它正在 运行 对 variant1 进行验证,这会在产品对象上调用 variants
。这将查询数据库中的相关变体,并得到一个空结果。但是,由于 variant2 具有相同的实际产品对象,该产品对象不会重新查询数据库,并且(错误地)记住它的 variants 是一个空结果。
可能使您的测试 运行 的另一个更改是将您的测试更改如下:
before do
@variant2 = FactoryBot.create(:variant, product_id: product.id, option_values: variant1.option_values)
end
它很微妙,我想知道它是否有效。这会在 variant2 上设置 product_id
字段,但不会将关联的 product
对象设置为与 variant1 具有的实际相同的 product
对象。 (实际上,这更有可能发生在您的实际代码中,即产品对象不在变体对象之间共享。)
您的正确解决方案(如果一切正确)的另一件事是重新加载,但将所有保存代码(和更新代码)放入事务中。这样就不会有两个变体的竞争条件会发生冲突,因为在事务中第一个必须完成验证并在第二个进行验证之前保存,因此它一定会检测到刚刚保存的另一个.
一些建议的调试技巧:
- 如果可能,请查看日志以了解何时进行了查询。您可能已经发现第二次验证没有查询变体。
- 勾选
object_id
。您可能已经发现产品对象实际上是同一个对象。 - 同时检查
new_record?
以确保在测试变体 2 之前已保存变体 1。我认为它确实保存了,但很高兴知道你检查过。