简体   繁体   中英

Destroy on blank nested attribute

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM