Skip to content

Associations with :validate => true are validating stale associations #3450

Closed
janv opened this Issue Oct 27, 2011 · 1 comment

2 participants

@janv
janv commented Oct 27, 2011

I have two models that looks like this (reduced to the parts relevant to this example):

class Car < ActiveRecord::Base
  belongs_to :model
  validates_presence_of :model_id
end

class Repair < ActiveRecord::Base
  belongs_to :car, :validate => true

  def new_car_attributes=(attributes)
    self.car ||= @new_car = Car.new(attributes)
  end

  def new_car
    @new_car || Car.new(:owner_id => customer_id)
  end
end

I am creating a repair like this:

customer = FactoryGirl.create(:customer)
car = FactoryGirl.create(:car, :owner => customer)

params= {"repair"=>{
  "new_car_attributes"=>{
    "manufacturer_id"=>"", "owner_id"=>customer.id, :kilometers => 123456},
  "car_id"=>car.id}}
repair = Repair.new(params["repair"])

Now, if I try to save the repair,it fails because "Car is invalid".
Reason for this is, that the validation validates the new car (with invalid attributes, missing model_id).

Expected behavior:

  • Assignment to car_id puts the association in a stale state. Trying to access repair.car loads the correct car (the one with the id that I assigned to car_id)
  • The validation of the association also recgnizes this (should happen naturally/automatically) and loads the correct car

Actual behavior:

  • The validation of the association does not recognize that the current target of the association is stale and tries to validate the car that is currently stored there, the invalid one that was created by the #new_car_attributes= setter

If I access #car first and validate the Repair afterwards, everything is fine because the accessor has updated the target of the association reflection to the correct car.

The blame is to be put somewhere here:

# activerecord-3.1.1/lib/active_record/autosave_association.rb: 263

# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
# turned on for the association.
def validate_single_association(reflection)
  association = association_instance_get(reflection.name)
  record      = association && association.target
  association_valid?(reflection, record) if record
end

The record that is validated here is obviously not the same I would get if I called the association accessor on the Model (repair.car in our example)
This should fix it:

def validate_single_association(reflection)
  association = association_instance_get(reflection.name)
  record      = association && association.read
  association_valid?(reflection, record) if record
end

( a similar change would need to be made in AutosaveAssociation#associated_records_to_validate_or_save)

@janv
janv commented Nov 27, 2011

I could not come up with a case where the current implementation of AutosaveAssociation#associated_records_to_validate_or_save actually leads to trouble, so this patch only includes the fix for validate_single_association

@janv janv added a commit to janv/rails that referenced this issue Nov 27, 2011
@janv janv Test case and fix for rails/rails#3450
Asssigning a parent id to a belongs_to association actually updates the object that is validated when the association has :validates => true
cba5a3a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.