I would like to destroy a nested model if its attributes are blanked out in the form for the parent model - however, it appears that the ActiveRecord::Callbacks
are not called if the model is blank.
class Artist < ActiveRecord::Base
using_access_control
attr_accessible :bio, :name, :tour_dates_attributes
has_many :tour_dates, :dependent => :destroy
accepts_nested_attributes_for :tour_dates, :reject_if => lambda { |a| a[:when].blank? || a[:where].blank? }, :allow_destroy => true
validates :bio, :name :presence => true
def to_param
name
end
end
and
class TourDate < ActiveRecord::Base
validates :address, :when, :where, :artist_id, :presence => true
attr_accessible :address, :artist_id, :when, :where
belongs_to :artist
before_save :destroy_if_blank
private
def destroy_if_blank
logger.info "destroy_if_blank called"
end
end
I have a form for Artist which uses fields_for
to show the fields for the artist's associated tour dates, which works for editing and adding new tour dates, but if I merely blank out a tour date (to delete it), destroy_if_blank
is never called. Presumably the Artist controller's @artist.update_attributes(params[:artist])
line doesn't consider a blank entity worth updating.
Am I missing something? Is there a way around this?
I would keep the :reject_if block but insert :_destroy => 1 into the attributes hash if your conditions are met. (This is useful in the cases where it's not convenient to add _destroy to the form code.)
You have to do an extra check to see if the record exists in order to return the right value but the following seems to work in all cases for me.
accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true
def reject_tour(attributes)
exists = attributes['id'].present?
empty = attributes.slice(:when, :where).values.all?(&:blank?)
attributes.merge!({:_destroy => 1}) if exists and empty # destroy empty tour
return (!exists and empty) # reject empty attributes
end
You could apply when all attributes are blank by just changing the empty
calculation to:
empty = attributes.except(:id).values.all?(&:blank?)
I managed to do something like this today. Like @shuriu says, your best option is to remove the reject_if
option and handle destruction yourself. mark_for_destruction
comes in handy :
class Artist < ActiveRecord::Base
accepts_nested_attributes_for :tour_dates
before_validation :mark_tour_dates_for_destruction
def mark_tour_dates_for_destruction
tour_dates.each do |tour_date|
if tour_date.when.blank? || tour_date.where.blank?
tour_date.mark_for_destruction
end
end
end
end
You have code that says the record should be ignored if the 'where' or the 'when' is blank, on the accepts_nested _attributes line, remove the reject_if
and your destroy_if blank will likely be called.
Typically to destroy, you would set a _destroy
attribute on the nested record, check out the docs http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Also, just used cocoon for some of this today, and thought it was awesome, https://github.com/nathanvda/cocoon
Similar to Steve Kenworthy's answer, no local variables.
accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true
def reject_tour(attributes)
if attributes[:when].blank? || attributes[:where].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
With your current code it's not possible, because of the reject_if
option passed to accepts_nested_attributes_for
.
As Christ Mohr said, the easiest way is to set the _destroy
attribute for the nested model when updating the parent, and the nested model will be destroyed. Refer to the docs for more info on this, or this railscast .
Or you can use a gem like cocoon, or awesome_nested_fields.
To do specifically what you want, you should remove the reject_if
option, and handle the logic in a callback inside the parent object. It should check for blank values in the tour_dates_attributes and destroy the nested model. But tread carefully...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.