【问题标题】:Rails accepts_nested_attributes_for saves no nested records if one record is invalid如果一条记录无效,Rails Accepts_nested_attributes_for 不保存任何嵌套记录
【发布时间】:2014-03-13 22:08:18
【问题描述】:

如果其中一条记录未通过验证,则使用嵌套关联创建记录将无法保存任何关联记录。

class Podcast < ActiveRecord::Base
  has_many :episodes, inverse_of: :podcast
  accepts_nested_attributes_for :episodes
end

class Episode < ActiveRecord::Base
  belongs_to :podcast, inverse_of: :episodes
  validates :podcast, :some_attr, presence: true
end

# Creates a podcast with one episode.
case_1 = Podcast.create {
  title: 'title'
  episode_attributes: [
    {title: "ep1", some_attr: "some_attr"}, # <- Valid Episode
  ]
}

# Creates a podcast without any episodes.
case_2 = Podcast.create {
  title: 'title'
  episode_attributes: [
    {title: "ep1", some_attr: "some_attr"}, # <- Valid Episode
    {title: "ep2"}                          # <- Invalid Episode
  ]
}

我希望case_1 能够成功保存一个已创建的剧集。 我希望case_2 做以下两件事之一:

  • 保存一集
  • 由于验证错误而无法保存。

相反,播客会保存,但两集都没有。

我希望在保存任何有效剧集的同时保存播客。

我想通过将接受嵌套属性行更改为来拒绝无效剧集

accepts_nested_attributes_for :episodes, reject_if: proc { |attributes| !Episode.new(attributes).valid? }

但是每一集都是无效的,因为它们还没有podcast_id,所以它们会失败validates :podcast, presence: true

【问题讨论】:

    标签: ruby-on-rails nested-attributes


    【解决方案1】:

    试试这个模式:在你的 accept_nested_attributes_for 指令 (docs) 中使用 :reject_if 参数并传递一个方法来发现属性是否有效。这可以让您将验证卸载到 Episode 模型。

    类似...

    accepts_nested_attributes_for :episodes, :reject_if => :reject_episode_attributes?
    def reject_episode_attributes?( attributes )
        !Episode.attributes_are_valid?( attributes )
    end
    

    然后在 Episode 中创建一个方法来测试你喜欢的那些。您甚至可以创建一条新记录并使用现有的验证。

    def self.attributes_are_valid?( attributes )
        new_e = Episode.new( attributes )
        new_e.valid?
    end
    

    【讨论】:

      【解决方案2】:

      您可以使用validates_associated 导致第二个选项(Fail to save with validation errors)

      class Podcast < ActiveRecord::Base
        has_many :episodes, inverse_of: :podcast
        validates_associated :episodes
        accepts_nested_attributes_for :episodes
      end
      

      更新:

      要执行选项一(保存一集),您可以执行以下操作: 1.添加 validates_associated :episodes 2. @podcast 保存失败后,在控制器的创建动作中添加代码。首先,检查@podcast.errors 对象以查看失败是否是由剧集的验证错误引起的(并且只有那个),否则正常处理。如果是由剧集的验证错误引起的,请执行@podcast.episodes.each {|e| 之类的操作@podcast.episodes.delete(e) unless e.errors.empty?} 然后再次保存。

      这看起来像:

      def create
       @podcast = Podcast.new(params[:podcast])
       if @podcast.save
         redirect_to @podcast
       else
         if #some conditions looking at @podcast.errors to see that it's failed because of the validates episodes
           @podcast.episodes.each do |episode|
             @podcast.episodes.delete(episode) unless episode.errors.empty?
           end
           if @podcast.save
             redirect_to @podcast
           else
             render :new
           end
         else
           render :new
         end
       end
      end
      

      【讨论】:

      • 谢谢,这可行,但我应该清楚我想要第一个选项。
      • 您可以将其与控制器代码结合使用,该控制器代码将(在剧集验证失败时)删除损坏的剧集对象并重试保存。然后,如果需要,将它们放回原处并返回给用户并显示验证错误,或者如果愿意,则忽略它们。
      • 我只存储嵌套的剧集属性,从播客哈希中清除它们,创建播客,然后遍历每个剧集哈希以创建剧集。我不确定如何编码您所描述的内容。
      【解决方案3】:

      要获得第一个选项,请尝试为您的剧集开启自动保存功能。

      class Podcast < ActiveRecord::Base
        has_many :episodes, inverse_of: :podcast, autosave: true
        accepts_nested_attributes_for :episodes
      end
      

      【讨论】:

      • 这没有解决。它与autosave: true 具有相同的行为。
      猜你喜欢
      • 1970-01-01
      • 2018-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-05
      • 1970-01-01
      相关资源
      最近更新 更多