Rails accepts_nested_attributes_for saves no nested records if one record is invalid

旧街凉风 提交于 2020-01-30 06:20:14

问题


Creating a record with nested associations fails to save any of the associated records if one of the records fails validations.

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
  ]
}

I'd expect case_1 to save successfully with one created episode. I'd expect case_2 to do one of two things:

  • Save with one episode
  • Fail to save with validation errors.

Instead the podcast saves but neither episode does.

I'd like the podcast to save with any valid episode saved as well.

I thought to reject invalid episodes by changing the accepts nested attributes line to

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

but every episode would be invalid because they don't yet have podcast_id's, so they would fail validates :podcast, presence: true


回答1:


Try this pattern: Use the :reject_if param in your accepts_nested_attributes_for directive (docs) and pass a method to discover if the attributes are valid. This would let you offload the validation to the Episode model.

Something like...

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

Then in Episode you make a method that tests those however you like. You could even create a new record and use existing validations.

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



回答2:


You can use validates_associated to cause the second option (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

UPDATE:

To do option one (save with one episode) you could do something like this: 1. Add the validates_associated :episodes 2. Add code in your controller's create action after the save of @podcast fails. First, inspect the @podcast.errors object to see if the failure is caused by validation error of episodes (and only that) otherwise handle as normal. If caused by validation error on episodes then do something like @podcast.episodes.each {|e| @podcast.episodes.delete(e) unless e.errors.empty?} Then save again.

This would look something like:

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:


To get the first option, try turning autosaving on for your episodes.

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


来源:https://stackoverflow.com/questions/22392026/rails-accepts-nested-attributes-for-saves-no-nested-records-if-one-record-is-inv

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!