Ruby on rails rspec的自定义唯一性验证出错

Ruby on rails rspec的自定义唯一性验证出错,ruby-on-rails,rspec,spree,Ruby On Rails,Rspec,Spree,我正在尝试创建一个rspec测试,以便在像gem这样的spree扩展中进行自定义验证 我需要验证变量的唯一性 产品所有Spree型号的选项值 以下是Model Salt的基本结构,尽管它们是spree的一部分,spree是一个基于rails的电子商务建筑: class Product has_many :variants has_many :option_values, through: :variants #defined in the spree extension, not in

我正在尝试创建一个rspec测试,以便在像gem这样的spree扩展中进行自定义验证 我需要验证变量的唯一性 产品所有Spree型号的选项值 以下是Model Salt的基本结构,尽管它们是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,可以有很多变体。还有一个带有选项值的变体,可以说RedOption\u type:Color和CircleOption\u type:Shape对于该产品是唯一的

无论如何,这是自定义验证器

 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的验证,该验证调用产品对象上的变量。这将查询数据库中的相关变量,并得到一个空结果。但是,由于variant2具有相同的实际产品对象,因此该产品对象不会重新查询数据库,并且错误地记住其变体是空结果

另一个可能使测试运行的更改是按如下方式更改测试:

before do
  @variant2 = FactoryBot.create(:variant, product_id: product.id, option_values: variant1.option_values)
end
这是微妙的,我想知道它是否有效。这将在variant2上设置product_id字段,但不会将关联的product对象设置为variant1具有的实际相同的product对象。实际上,这更可能发生在您的实际代码中,即产品对象不在变量对象之间共享

如果所有这些都正确,正确解决方案的另一件事是重新加载,但将所有保存代码和更新代码放在事务中。这样就不会有两个变量的竞争条件发生冲突,因为在一个事务中,第一个变量必须在第二个变量进行验证之前完成验证和保存,所以它一定会检测到刚刚保存的另一个变量

一些建议的调试技术:

如果可能,请查看日志以查看何时进行查询。您可能发现第二次验证没有查询变体。 检查对象id。您可能发现产品对象实际上是同一个对象。 还要检查新的记录吗?确保在测试variant2之前保存variant1。我认为它确实节省了,但如果知道你检查了它,那就太好了。
我马上注意到您正在通过let设置variant1,并设置@variant2,然后调用@variant.valid?但是@variant从未设置过。@MarlinPierce。。哦,我在这里写错了。我的意思是@variant2 only.OMG有效。。非常感谢你。将其更改为produc_id:product.id有效。所以你提到了比赛条件。这就是这里发生的事情吗?我不知道怎么做。你在测试独特性。如果另一条记录尚未在数据库中,但同时正在验证,该怎么办?下一个变体尚未保存,因此没有一个检测到非唯一性,两个都保存。