问题
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