【问题标题】:How to validate a nested model object based on the state of the parent object?如何根据父对象的状态验证嵌套模型对象?
【发布时间】:2011-08-23 22:05:48
【问题描述】:

我正在用 Rails 编写向导表单;例如一个模型对象的多个输入页面。

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

这里审查的对象是“参与者”,它有一个“地址”

我的问题是,当用户试图通过地址输入屏幕时,我只想验证嵌套对象(地址)。这目前通过 Participant 模型上名为“current_step”的属性进行跟踪

所以我有一个参与者:

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

还有地址:

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.address.participant 为空,因为地址还没有用于查找其父对象的participant_id 外键。

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

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

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

【问题讨论】:

  • 只是猜测,但您是否尝试过验证 :address, :if => Proc.new {|participant|参与者.current_step == '名称' }
  • 我尝试的第一件事 - 但如上所述(也许不太清楚,我承认)地址对象在保存之前没有参与者,所以我在 nil 上调用 current_step 时出错类。
  • 将 :inverse_of 添加到您的关系中,因此地址对象在构建时将具有对其参与者的引用。

标签: ruby-on-rails ruby ruby-on-rails-3 validation activerecord


【解决方案1】:

在您的 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

【讨论】:

  • 好主意。我调用属性skip_validation 并使用:unless 而不是:if,所以默认是验证。到目前为止效果很好。
  • 好吧,几个月前我删除了我的 rails 向导,并且从那以后解决了更多嵌套模型的问题,但是很高兴回到这个问题上,看看什么似乎是解决问题的可靠答案。谢谢。
  • 我不喜欢使用skip_validation 是因为你真的不知道为什么要使用它。我遇到了我无法弄清楚的代码,而编写它的开发人员已经忘记了。 :*(
【解决方案2】:

在调用participant.valid? 之前将其分配到控制器中怎么样:

@participant.address.participant = @participant

【讨论】:

    【解决方案3】:

    尽管 ActiveRecord 的“验证”方法非常方便,但没有什么可以阻止您“自己动手”,也许使用 before_save 钩子。您可以将Address 上的validates_presence_of 验证器替换为before_save 挂钩,该挂钩仅在特定条件下进行验证。那么accepts_nested_attributes_for 大概就看不到了。

    【讨论】:

      猜你喜欢
      • 2020-12-02
      • 1970-01-01
      • 1970-01-01
      • 2017-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-09
      • 2020-03-20
      相关资源
      最近更新 更多