Rails:防止创建父模型并显示子模型验证错误

14

我已经被这个问题困扰了一段时间,对于嵌套模型和验证如何共同工作,我已经彻底困惑了自己。

在下面的代码中,我的目标是如果子模型(Content)的验证失败,则父模型(Image或Video)的创建会失败。目前,虽然子模型未被保存且验证错误无法得到处理,但父模型却被保存。如果没有验证错误,那么一切都按预期工作。

#Image.rb
has_one     :content,
as:         :contentable,
inverse_of: :contentable,
dependent:  :destroy

#Video.rb
has_one     :content,
as:         :contentable,
inverse_of: :contentable,
dependent:  :destroy

#Content.rb
belongs_to   :contentable,
inverse_of:  :content,
polymorphic: true

validate     :all_good?

def all_good?
  errors.add(:base, "Nope!")
  return false
end
任何线索或洞见都非常感激!

你正在使用哪个版本的Rails? - dnsh
@Dinesh Rails 4.2.6 - geoboy
为什么你要像这样设置 - “推荐的方法”除非你提出一个最终目标,否则实际上是不可行的 - 分离项目的原因是什么 - 在你的应用程序中,模型/架构看起来是什么样子,需要进行这种分离? - MageeWorld
我认为你的代码中重要的部分缺失了。你必须向我们展示如何持久化这些对象。例如,如果你先创建 Image 并保存它,然后再添加一个子对象,Rails 无法撤消此次 save 操作。那么你就需要将所有操作封装在一个事务中,并抛出错误来触发回滚。 - slowjack2k
@slowjack2k 我基本上是在保存图像或视频之前,设置内容的关联,因此它并没有被保存。 - geoboy
显示剩余2条评论
3个回答

6

Rails有一个特殊的验证叫做validates_associated,用于确保相关记录是有效的。如果相关记录无效,则父记录也将无效,并且与之关联的错误将添加到其错误列表中。

在您的Image和Video类中都添加以下内容:

validates_associated :content

现在,如果内容关联无效,则视频或图像将不会被保存。
video = Video.new
video.content = Content.new
video.save #=> false
video.valid? #=> false
video.errors #=> [:content => "is invalid"]

感谢您抽出时间回复。不幸的是,那并不起作用。它仍然会继续保存记录(如果我插入validates_presence_of:content,则会说“内容不能为空”)。嵌套是否会导致这种情况?此外,根据文档:“注意:如果尚未分配关联,则此验证不会失败。如果要确保关联存在且保证有效,则还需要使用validates_presence_of”。 - geoboy

4

简短回答

添加图片和视频模型:

accepts_nested_attributes_for :content

证明

我很确定我知道这个问题的答案,但不确定它是否适用于多态关联(我以前没有使用过),所以我设置了一个小测试。

创建了与你设置相同的模型,但加上了名称属性,并且添加了一个验证来测试失败情况。

class Image < ActiveRecord::Base
  has_one     :content,
              as:         :contentable,
              inverse_of: :contentable,
              dependent:  :destroy

  validates_length_of :name, maximum: 10
end

class Content < ActiveRecord::Base
  belongs_to   :contentable,
               inverse_of:  :content,
               polymorphic: true

  validates_length_of :name, maximum: 10
end

接下来按照以下方式设置迁移:

class CreateImages < ActiveRecord::Migration
  def change
    create_table :images do |t|
      t.string :name

      t.timestamps null: false
    end
  end
end


class CreateContents < ActiveRecord::Migration
  def change
    create_table :contents do |t|
      t.string :name
      t.references :contentable, polymorphic: true, index: true

      t.timestamps null: false
    end
  end
end

接下来编写一个RSpec测试,以确保如果子对象无法保存,则父对象不会被保存,并且验证错误会向上传递。

  it 'should not save image if content is invalid' do
    image = Image.new()
    image.name = 'this is ok'
    expect(image).to be_valid

    content = Content.new()
    content.name = 'a string that should fail validation'
    image.content = content

    expect(image).to_not be_valid
    image.save

    expect(image).to_not be_persisted
    expect(content).to_not be_persisted

    expect(image.errors.count).to eq(1)
    expect(image.content.errors[:name][0]).to include('is too long')
  end

我进行了测试,结果失败了。

接下来,将以下行添加到图像(和视频)中。

  accepts_nested_attributes_for :content

测试现在是通过的 - 即如果子级未通过验证,则父级也将未通过验证,并且不会保存。

0
你需要在自定义验证中引发异常。做类似以下的操作:
before_save :ensure_all_good

def ensure_all_good
 try saving stuff to chile
 failed? raise nope
end

有趣的想法。不过,我难道不能通过验证和错误对象来实现它吗?这是更传统的 Rails 方式。 - geoboy
我不会在我的验证器中实现更新子项的逻辑,因为那样会产生误导。因此,我更喜欢使用 before_save 进行更新,并在那里处理它。你总是可以将这两个请求放在一个事务中,以确保它们不会被提交。 - Mudit Dalmia
@geoboy 我刚刚遇到了类似的问题,子模型的错误没有传递到父模型。在您的情况下,由于验证位于内容模型内部,因此父模型的视频和图像将无法查看来自内容的错误。 您可能需要查看以下链接,以了解如何将子模型的错误合并到父级中: https://dev59.com/NHE85IYBdhLWcg3wpFIa - Mudit Dalmia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接