圆形一对一模型保存呼叫,双方都有在线验证

时间:2015-02-05 20:39:57

标签: ruby-on-rails validation factory-bot

鉴于FooBar模型之间存在one-to-one关系并且彼此存在验证:

class Foo < ActiveRecord::Base
  has_one :foo_bar
  has_one :bar, through: :foo_bar, inverse_of: :foo

  validates :bar, presence: true
end

class FooBar < ActiveRecord::Base
  belongs_to :foo
  belongs_to :bar
end

class Bar < ActiveRecord::Base
  has_one :foo_bar
  has_one :foo, through: :foo_bar, inverse_of: :bar

  validates :foo, presence: true
end

他们的工厂:

FactoryGirl.define do
  factory :foo do
    foo_bar
    bar { |foo| build(:bar, foo_bar: foo_bar, foo: foo) }
  end

  factory :bar do
    foo_bar
    foo { |bar| build(:foo, foo_bar: foo_bar, bar: bar) }
  end

  factory :foo_bar do
  end
end

当我尝试使用Foo创建BarFactoryGirl.create(:foo)的实例时,我得到SystemStackError: stack level too deep

原因是:

  1. 保存foo对象时,会查看关联的bar
  2. A-公顷! bar未保存,我们需要先保存
  3. 在保存bar时,它会查看foo(尚未保存)和 决定保存它
  4. 等它试图相互保存,导致无限循环
  5. 一种解决方案是在关联的一端添加autosave: false,如:

    class Bar 
      has_one :foo, through: :foo_bar, inverse_of: :bar, autosave: false
    end
    

    这允许创建Foo,但会阻止create(:bar),因为不会保存foo关联,从而导致无效Bar记录(因为验证)。

    如何解决这个问题,以便foobar工厂都有效?

3 个答案:

答案 0 :(得分:1)

这是一个概念性问题。考虑一下 - 你在国外,你丢失了你的文件。你去你的大使馆,他们想看到你的机票签发新护照。你去航空公司,他们希望你的护照签发机票副本。这是一个僵局,一个鸡与蛋的问题。

解决问题的唯一方法是重新定义它。您可以决定要验证关系,但至少在其中一个方向上,您只有在创建后才会进行验证。

class Foo < ActiveRecord::Base
  has_one :foo_bar
  has_one :bar, through: :foo_bar, inverse_of: :foo

  validates :bar, presence: true, on: :update
end

因此,在我们的例子中,航空公司仍然不会在没有护照的情况下向您发放您的机票,但是大使馆会在没有机票的情况下给您护照。此外,如果您已经拥有护照并因任何原因前往大使馆,他们会要求查看门票以确认。

答案 1 :(得分:0)

我认为validates :foo, presence: true最终会使用validates_associated

文档说明:不要在关联的两端使用validates_associated。他们会在无限循环中互相称呼。

http://guides.rubyonrails.org/active_record_validations.html#validates-associated

基本上,一个人必须是追随者...也许你可以解释一下你的用例并得到一些数据模型反馈?

答案 2 :(得分:0)

我同意模型结构似乎没有了。最好的解决方案是通过不同的关联可能达到你想要的效果。但是,如果这不是一个选项,我会抛出一个可能让你到处找到的想法。

您可以覆盖自动保存并自行检查状态。你仍然需要依赖另一个,但你可以在你的Foo模型中做这样的事情:

def autosave_associated_records_for_bar
  #validate data
  #save bar
  #probably need to save ids on foobar
end

注意:执行此操作时应小心,确保您了解效果(验证器可能无法运行等)

您可以在此处的源代码中查看有关AutosaveAssociation的更多信息:https://github.com/rails/rails/blob/master/activerecord/lib/active_record/autosave_association.rb

也许那里的东西会有所帮助。