【问题标题】:Associations: Prevent Save if the Collection as a Whole is Invalid关联:如果整个集合无效,则阻止保存
【发布时间】:2013-11-20 15:59:41
【问题描述】:

我需要验证一个包含相关集合的模型,该集合需要 在 has_many 集合中唯一的字段。

例如,如果我有以下课程:

class Match < ActiveRecord::Base
  has_many :match_items, dependent: :destroy
  validates_associated :match_items
end

class MatchItem < ActiveRecord::Base
  belongs_to :match
  validates_uniqueness_of :user_type, scope: :match_id
end

我创建一个这样的匹配:

items = [
  MatchItem.new(user_type: 'test'),
  MatchItem.new(user_type: 'test')
]
match = Match.new(match_items: items)
match.save # Erroneously returns true

我希望匹配创建失败。

虽然 MatchItem 类中的唯一性验证器确实 阻止集合包含多个用户类型,但我更希望匹配创建失败。相反,虽然它只包含指定的匹配项之一,但匹配创建时没有错误。

我认为问题在于validates_associated 在保存 Match 之前在每个 MatchItem 上调用 valid?,但每个 MatchItem 在添加到 Match 之前 自身有效。

当 match_items 集合不包含唯一值时,防止 Match 保存的最佳方法是什么?

【问题讨论】:

  • 你为什么不能把所有东西都包装在一个 AR.transaction 块中并使用create!
  • @PhilipHallstrom 我无法使用create! 调用将其包装在事务中的原因是它不会引发错误,尽管事实上只有一个关联的匹配项是已保存。

标签: ruby-on-rails activerecord ruby-on-rails-4 associations


【解决方案1】:

我正在尝试自己理解这一点。以下出现在http://guides.rubyonrails.org/association_basics.html

4.3.4 什么时候保存对象?当您将对象分配给 has_many 关联时,该对象会自动保存(以便更新 它的外键)。如果您在一个语句中分配多个对象, 然后他们都得救了。

如果这些保存中的任何一个由于验证错误而失败,则 赋值语句返回 false 并且赋值本身是 取消。

如果父对象(声明 has_many 关联的对象)是 未保存(即 new_record? 返回 true),则子对象为 添加时未保存。协会所有未得救的成员 保存父级时会自动保存。

如果你想将一个对象分配给一个 has_many 关联而不 保存对象,使用 collection.build 方法。

作为一个实验,您能否尝试在没有任何MatchItems 的情况下创建Match,然后分配MatchItems 以查看是否没有分配?

【讨论】:

  • 如果我按照您的建议首先创建匹配项,然后使用match.match_items = items 分配匹配项,则当我尝试保存时,记录确实 失败。你认为最初的问题只是rails中的一个错误吗?
  • 我不知道这是错误还是功能,因为文档不清楚。由于关联的项目实际上不是项目的部分,因此创建它们的集合并不是自然原子的。验证取决于需要创建“主”记录的“主”ID,因此一旦开始创建过程就不容易回滚。
【解决方案2】:

看起来这是 rails 项目中的一个未解决问题 (#11851)。正如彼得帮助指出的那样,当您构建记录而不是单独创建记录时,问题就出现了。作为目前的解决方法,我只是在将所有对象分配给集合之前创建它们。

# This one fails properly
items = MatchItem.create([ { user_type: 'test' }, { user_type: 'test' } ])
Match.create!(match_items: items)

# While this one succeeds when it shouldn't
items = [
  MatchItem.new(user_type: 'test'),
  MatchItem.new(user_type: 'test')
]
match = Match.new(match_items: items)
match.save # Erroneously returns true

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-02
    • 2015-03-15
    相关资源
    最近更新 更多