Rails 以复杂形式验证多个子模型

发布于 2024-08-02 06:21:12 字数 1606 浏览 3 评论 0原文

我以复杂的形式使用多个子模型(a la http://railsforum.com/viewtopic .php?id=28447)。该表单运行良好,但在将表单数据接受到数据库之前,我需要验证子模型的属性。我想出了一种最有效但非常笨拙的方法来做到这一点。似乎必须有更好的方法,所以我寻求建议......

基本上一个人有多个分布。分布具有(除其他外)百分比属性。对于给定的人,他们的分配总数必须达到 100% 才有效。这对我来说是“交易”,但我认为我应该先给验证者一个机会。

我尝试将其编写为自定义验证器,但验证器仅适用于已保存到数据库的数据。它没有检查表单提交的参数。换句话说,我能够通过保存的表单输入无效的百分比,然后由于模型中已有错误数据,后续编辑全部失败。

接下来,我在 Person 模型中扩展了 update_attributes,添加了一个事务:

def update_attributes(params)
  retval = true
  self.transaction do
    retval = super

    unless distributions_exactly_100?
      retval = false
      errors.add_to_base("Distribution must add up to exactly 100%")
      raise ActiveRecord::Rollback
    end
  end
  retval
end

retval 业务很丑陋,但这或多或少有效(有时,当发现错误并重新渲染时,表单中会缺少一些待处理的分配)。还有一个额外的细微差别让我相信这是一个糟糕的方法:如果我的分布关联是使用辅助方法定义的,如下所示,我无法在我的 update_attributes() (或在distributions_exactly_100?),因为它们会访问数据库,而不是在一组刚刚分配但尚未提交的分布上进行操作。

 has_many :distributions do
   def for_month_and_year(month, year)
     find :all, :conditions => ['month = ? and year = ?', month, year]
   end

   def total_for_month_and_year(month, year)
     sum :percentage, :conditions => ['month = ? and year = ?', month, year]
   end

   ...

   def years_and_months
     ds = find(:all, :order => 'year DESC, month DESC')
     (ds.collect {|d| [d.year, d.month]}).uniq
   end

 end

我唯一能想到的另一件事是在进入 update_attributes 的过程中将参数本身作为文本进行处理。但这是错误的。 :)

还有其他人对整个儿童集合进行验证吗?正确的做法是什么?

I am using multiple child models in a complex form (a la http://railsforum.com/viewtopic.php?id=28447). The form works great, but I need to validate a property of the set of child models before accepting the form data into the database. I've come up with one mostly-working, very-kludgy way of doing this. Seems like there has to be a better way, so I'm asking for suggestions...

Basically a Person has_many Distributions. The Distribution has (among other things) a percentage attribute. For a given person, their distributions must total 100% to be valid. This screams "transaction" to me, but I figured I should give the validators a shot first.

I tried writing this as a custom validator, but the validator only works on data already saved to the database. It did not check the parameters submitted by the form. In other words I was able to enter invalid percentages via the form, which were saved, and then subsequent edits all failed because of the bad data already in the model.

Next I extended update_attributes in my Person model, adding a transaction:

def update_attributes(params)
  retval = true
  self.transaction do
    retval = super

    unless distributions_exactly_100?
      retval = false
      errors.add_to_base("Distribution must add up to exactly 100%")
      raise ActiveRecord::Rollback
    end
  end
  retval
end

The retval business is ugly but this more or less works (sometimes some of the pending distributions are missing from the form, when it finds an error and re-renders). There is an additional nuance that convinces me this is a poor approach: if my distirbutions association is defined with helper methods, as below, I can't use the helper methods in my update_attributes() (or in distributions_exactly_100?), because they go to the database rather than operating on the set of just-assigned-but-not-yet-committed distributions.

 has_many :distributions do
   def for_month_and_year(month, year)
     find :all, :conditions => ['month = ? and year = ?', month, year]
   end

   def total_for_month_and_year(month, year)
     sum :percentage, :conditions => ['month = ? and year = ?', month, year]
   end

   ...

   def years_and_months
     ds = find(:all, :order => 'year DESC, month DESC')
     (ds.collect {|d| [d.year, d.month]}).uniq
   end

 end

The only other thing I can think of is to process the params themselves, as text, on the way into update_attributes. But that is just Wrong. :)

Anyone else doing validation on a whole collection of children? What's the right way to go about it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

人│生佛魔见 2024-08-09 06:21:12

我不建议在 update_attributes 中设置错误。将验证保留在正常位置。

回到您的问题,您是否可以更改验证检查以处理内存中的分布,而不是在数据库上执行计算?

# in Person model
validate :ensure_distributions_equal_100

def ensure_distributions_equal_100
  percent = distributions.map(&:percent).sum
  if percent != 100
    errors.add_to_base("Distribution must add up to exactly 100%, they are #{percent}")
  end
end

I don't recommend setting errors in update_attributes. Keep validations in their normal place.

Back to your problem, can you change the validation check to work on the distributions in memory rather than performing the calculation on the database?

# in Person model
validate :ensure_distributions_equal_100

def ensure_distributions_equal_100
  percent = distributions.map(&:percent).sum
  if percent != 100
    errors.add_to_base("Distribution must add up to exactly 100%, they are #{percent}")
  end
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文