Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

before_destroy is not firing from update_attributes

I have a student that has many courses. In the student#update action and form, I accept a list of course_ids. When that list changes, I'd like to call a certain function. The code I have does get called if the update_attributes creates a course_student, but does not get called if the update_attributes destroys a course_student. Can I get this to fire, or do I have to detect the changes myself?

# app/models/student.rb
class Student < ActiveRecord::Base
  belongs_to :teacher
  has_many :grades
  has_many :course_students, :dependent => :destroy
  has_many :courses, :through => :course_students
  has_many :course_efforts, :through => :course_efforts

  # Uncommenting this line has no effect:
  #accepts_nested_attributes_for :course_students, :allow_destroy => true

  #attr_accessible :first_name, :last_name, :email, :course_students_attributes

  validates_presence_of :first_name, :last_name
...
end

# app/models/course_student.rb
class CourseStudent < ActiveRecord::Base
  after_create  :reseed_queues
  before_destroy :reseed_queues

  belongs_to :course
  belongs_to :student

  private

  def reseed_queues
    logger.debug "******** attempting to reseed queues"
    self.course.course_efforts.each do |ce|
      ce.reseed
    end
  end

end

# app/controllers/students_controller.rb

  def update
    params[:student][:course_ids] ||= []
    respond_to do |format|
      if @student.update_attributes(params[:student])
        format.html { redirect_to(@student, :notice => 'Student was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @student.errors, :status => :unprocessable_entity }
      end
    end
  end
like image 982
Chris Avatar asked Aug 22 '10 17:08

Chris


2 Answers

It turns out that this behavior is documented, right on the has_many method. From the API documentation:

collection=objects Replaces the collections content by deleting and adding objects as appropriate. If the :through option is true callbacks in the join models are triggered except destroy callbacks, since deletion is direct.

I'm not sure what "since deletion is direct" means, but there we have it.

like image 144
Chris Avatar answered Oct 22 '22 21:10

Chris


When a record is deleted using update/update_attributes, it fires delete method instead of destroy.

@student.update_attributes(params[:student])

Delete method skips callbacks and therefore after_create / before_destroy would not be called. Instead of this accepts_nested_attributes_for can be used which deletes the record and also support callbacks.

accepts_nested_attributes_for :courses, allow_destroy: true

@student.update_attributes(courses_attributes: [ {id: student_course_association_id, _destroy: 1 } ])

like image 3
Kshitij Avatar answered Oct 22 '22 23:10

Kshitij