2011-05-12 75 views
21

我正在写轨道向导的形式;例如一个模型对象的多个输入页面。如何根据父对象的状态验证嵌套模型对象?

我的方法的基础是Ryan Bates的Multistep form railscast:http://railscasts.com/episodes/217-multistep-forms(如果有人想知道下面的一些代码背后的原因)。

受到审查的对象这里是“参与者”,其中有一个“地址”

我的问题是,我只是想验证嵌套对象(地址),当用户试图过去将地址输入进步屏幕。

class Participant < ActiveRecord::Base 
    has_one :address 
    accepts_nested_attributes_for :address 
    validates_presence_of :first_name, :last_name, :if => self.current_step == "name" 
    ... 
    def steps = %w[ name address confirm ] # the steps the wizard will follow 
end 

和地址:这是通过所谓的“CURRENT_STEP”

所以我有一个参与者对参与者模型的属性目前跟踪

class Address < ActiveRecord::Base 
    belongs_to :participant 
    validates_presence_of :address1, :state, :suburb, :postcode #, :if => participant.current_step == "address" 
end 

这种方法的原理是在向导的每个步骤的控制器(未显示)上调用“创建”操作,并且它仅在处理每个步骤时验证模型的子集。

当前,当我完成第一个屏幕(“名称”)并尝试进入地址步骤时,地址验证将被触发,并且我将返回到带有验证错误的“名称”屏幕以显示空白地址细节。

所以我在这里尝试了很多方法,其中最后一部分是上面显示的地址验证的注释掉条件 - 我发现这不起作用,因为我只是构建参与者 - >地址对象,但不保存它们。因此@participant.address得到我的地址对象,但@participant.address.participant为空,因为地址还没有一个participant_id外键来查找它的父项。

我挣扎的原因似乎是包含了超级方便的accepts_nested_attributes_for方法。我期待使用validates_associated进行验证,但我发现accepts_nested_attributes_for标签都很好地传播了表单参数以创建嵌套的模型对象,但也确保在所有情况下方法调用participant#valid?方法进行地址验证。

所以我的困境是如何最好地使用participant#valid?方法来验证部分完整的模型,基于参与者中的current_step参数?

编辑 - 更新,删除了额外的信息,并提炼到核心问题

+0

只是一个猜测,但你试过 验证:address,:if => Proc.new {|参与者| participant.current_step =='name'} – dogenpunk 2011-05-16 19:43:50

+0

我尝试过的第一件事情之一 - 但如上所述(可能不太清楚,我承认)地址对象没有参与者,直到它被保存,所以我得到一个错误,调用current_step on无班级。 – Phantomwhale 2011-05-16 23:32:50

+0

添加:inverse_of到你的关系,所以地址对象将建立时参考其参与者。 – graywh 2013-03-30 03:35:56

回答

18

添加一个虚拟属性您Address型号:

class Address < ActiveRecord::Base 
    belongs_to :participant 
    attr_accessor :skip_validation 
    validates_presence_of :address1, :state, :suburb, :postcode, 
          :unless => :skip_validation 
end 

当设置了current_step时,在地址对象上设置虚拟属性。

class Participant < ActiveRecord::Base 
    has_one :address 
    accepts_nested_attributes_for :address 
    attr_accessor :current_step 
    validates_presence_of :first_name, :last_name, 
         :if => lambda {|r| r.current_step == "name"} 


    def current_step=(value) 
    unless (value == "address") 
     address.skip_validation = true 
    end  
    @current_step = value 
    end  
end 
+1

好主意。我调用属性'skip_validation'并使用':unless'而不是':if',所以默认是验证。迄今为止效果很好。 – Thilo 2012-01-24 10:56:31

+0

好吧,几个月前我就移除了我的导轨向导,并且自那时以来一直在处理更多的嵌套模型问题,但回过头来看看问题的确切答案是非常好的。谢谢。 – Phantomwhale 2012-01-24 22:57:23

+0

这是金子!谢谢! – 2013-03-11 18:13:44

0

如何在你的控制器,你打电话之前participant.valid?只是分配给它:

@participant.address.participant = @participant 
0

虽然ActiveRecord的“验证”方法是非常方便,有什么能阻止你“滚你自己”,或者使用before_save挂钩的。您可以将validates_presence_of验证程序替换为Address,其中before_save挂钩仅在某些情况下进行验证。然后accepts_nested_attributes_for想必不会看到它。