Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Use of marked_for_destruction? in validation callback breaks autosave association behavior

#5041

I have a debate model, which should have at least 2 sides.
Here's some code:

has_many :sides, :dependent => :destroy
accepts_nested_attributes_for :sides, :allow_destroy => true
validate :has_enough_sides
# ...
def has_enough_sides
    unmarked_sides = sides.reject { |side| side.marked_for_destruction? }
    errors[:sides] << "A debate must have at least 2 sides." if unmarked_sides.size < 2
end

When I comment out "validate :has_enough_sides", changes are made to the nested sides when the debate model is updated with a nested form, as a normal nested form should. When I don't use the "marked_for_destruction?" function it also works fine.

However, when I include that validation, with the use of "marked_for_destruction?", all changes made to the nested models are ignored and no SQL is actually run, and the debate still saves and validates.

Oddly enough, if a side is invalid, the form is rendered again with the attributes of the original sides as if no changes were made.

I've looked through the source code and I'm pretty sure the problem is in here, but I cannot trace it: http://github.com/rails/rails/blob/76f024ac8db82490a99c71d0d8951d67...

Reported by Aaron Lifton · July 4th, 2010 @ 04:42 AM

State: resolved
Milestone: none
Assigned to: Neeraj Singh Neeraj Singh
Importance: Low

Activity

  1. Neeraj Singh
  2. Neeraj Singh
    Neeraj Singh

    I am not able to reproduce your case with rails edge. Here is my setup.

    ActiveRecord::Schema.define(:version => 20100707175023) do
    
      create_table "questions", :force => true do |t|
        t.text    "name"
        t.integer "survey_id"
      end
    
      create_table "surveys", :force => true do |t|
        t.string "name"
      end
    
    end
    class Question < ActiveRecord::Base
      belongs_to :survey
    end
    class Survey < ActiveRecord::Base
    
      has_many :questions, :dependent => :destroy
    
      accepts_nested_attributes_for :questions, :allow_destroy => true
    
      validate :has_enough_questions
    
      def self.lab
        Survey.delete_all
        Question.delete_all
        survey = Survey.new(:name => 'cool_survey')
        q1 = survey.questions.build(:name => 'q1')
        q2 = survey.questions.build(:name => 'q2')
        survey.save!
        
        survey = Survey.first
        survey.update_attributes({:name => 'super_cool_survery', :questions_attributes => { '0' => {:id => q1.id.to_s, :name => 'q11'},
        '1' => {:id => q2.id.to_s, :name => 'q22'}}})
        puts survey.questions(true).inspect
      end
    
      private
    
      def has_enough_questions
        unmarked_questions = questions.reject { |q| q.marked_for_destruction? }
        errors[:questions] << "A survery must have at least 2 questions." if unmarked_questions.size < 2
      end
    
    end
    

    Just run Survey.lab and you will see the updated result. You can also see the sql statements for the update.

    July 7th, 2010 @ 09:54 PM

  3. Aaron Lifton
    Aaron Lifton

    Great, thanks a lot for helping and trying to reproduce the error.

    July 8th, 2010 @ 11:06 PM

  4. Neeraj Singh
    Neeraj Singh

    @Aaron Is your problem gone or are you still able to reproduce this on rails edge?

    July 8th, 2010 @ 11:09 PM

  5. Aaron Lifton
    Aaron Lifton

    Well I was on Rails 3.0.0.beta4, I'll install rails edge and let you know.

    July 11th, 2010 @ 05:20 PM

  6. Subba
    Subba
    • Assigned user set to Neeraj Singh

    Neeraj,
    i tried the above case it seems working for me also.
    as you pointed out 4642 fixed this.

    
    
    class Car < ActiveRecord::Base
      has_many :brakes
      accepts_nested_attributes_for :brakes, :allow_destroy => true
      validate :has_enough_brakes
    
      def has_enough_brakes
        unmarked_sides = brakes.reject { |side| side.marked_for_destruction? }
        if unmarked_sides.size < 2
          errors[:sides] << "A debate must have at least 2 sides."
        end
      end
    
      def self.lab
        car   = Car.new(:name => 'Toyota')
        car.brakes.build(:name => "middle")
        car.brakes.build(:name => "disc")
        car.save
        left  = car.brakes.create(:name => "left")
        car.update_attributes({
          :name => 'Toyota',
          :brakes_attributes => {
            "0" => {
              "name" => "right"
            },
            "1" =>{
              "id" => left.id.to_s,
              "_destroy" => "1"
            }
          }
        })
      end
    
    end
    

    July 16th, 2010 @ 09:29 PM

  7. Neeraj Singh
    Neeraj Singh
    • State changed from new to resolved

    marking this ticket as resolved. If it is still an issue please report it and it can be reopened.

    July 16th, 2010 @ 09:33 PM