ActiveRecord associations and the valid? flag when building

I was debugging a mysterious bug using RSpec and it took a while to figure out what exactly was going on. I thought that I would document my findings here, as I could not find much information on the subject and ended up digging into the ActiveRecord source code.

The problem itself may not be that interesting if you are a seasoned Rails developer, but the larger point that I am trying to make is that you should not be scared to look into the source code if you do not understand something. I have even ended up deep in the MRI C code at times to get a better understanding of certain functionality.

The line that seemed to be failing was a simple shovel << of a child object into its associated parent object. The association simply was not being created.

The << operator

If you are not familiar with the ActiveRecord << method, below is a brief explanation.

@category.products << @product

Is the same as calling:

@product.category_id = @category.id
@product.save!

You can look at its actual definition in the ActiveRecord source code.

#.../gems/activerecord-3.2.18/lib/active_record/associations/collection_proxy.rb

def << (*records)
  proxy_association.concat(records) && self
end
...

The code in question

Say we have a model ProductCategory, which has many Products (the idea of Products is actually composed of a set of Single Table Inheritance (also known as STI) models but I will leave it out for brevity. However at the time, this did add some complexity to the debugging process and was considered as a potential point of failure). Each product also has many variants.

It would look something like this in the models:

class ProductCategory < ActiveRecord::Base
  has_many :products
end

class Product < ActiveRecord::Base
  belongs_to :product_category
  has_many :variants
end

class Variant < ActiveRecord::Base
  belongs_to :product
end

In the controller, I was creating a product, then trying to add variants to the newly created product, and finally add the product to the product category.

The nested form was submitting all the required data for the related objects to be created.

ProductController < ApplicationController
  def create
    @category = ProductCategory.find(params[:product_category_id])
    @product = create_sti_product(params[:product])
    @product.try_add_variants params[:products][:variants]
    @category.products << @product
  end
end

The method try_add_variants on the product model may seem unneccessary, and like something that could be handled in the controller but bear in mind that this was an STI setup and the different Product models each provided their own implementation of the method.

class Product < ActiveRecord::Base
...
  def try_add_variants(variants_params)
  ...
    variant_params.each do |v|
      variant = variants.build v
      variant.save if variant.valid?
    end
  end
end

Note that ‘variants’ in this case is actually product.variants but is called directly on the instantiated product, so the reference to products can be omitted.

I had a very simple controller spec that looked like this:

...
it "creates a product" do
  expect {
    post :create, product_params
  }.to change(category.products, :count).by(1)
end

This was my spec that I used to debug the problem, and was failing, so that was good. Automated tests are the best way to debug a problem, even if you just use it to drive the application, which is much faster than trying to do it manually through a browser.

I found that the try_add_variants method was causing the product to become invalid.

This would set the valid? flag to false, and prevent it (or anything related to it) from being saved.

p @product.valid? # true
@product.try_add_variants params[:products][:variants]
p @product.valid? # false

At first this seemed strange because the try_add_variants did not *really* touch the products, only the decendants of the products.

This invalid state in the variant object is stored, and would cascade up to the product valid? flag, causing it to be set to false too.

I decided that I would like to find the piece of code in ActiveRecord that was responsible for setting the valid? flag of the parent objects. I used pry-debugger, and followed the execution of the code deep into ActiveRecord. I was happy to eventually stumble onto this code below.

A look inside ActiveRecord

The actual code that deals with setting the parent valid? flag when calling build can be found here:

# .../gems/activerecord-3.2.18/lib/active_record/autosave_association.rb

# Returns whether or not the association is valid and applies any errors to
# the parent, self, if it wasn't. Skips any :autosave
# enabled records if they're marked_for_destruction? or destroyed.

def association_valid?(reflection, record)
  return true if record.destroyed? || record.marked_for_destruction?

  unless valid = record.valid?
    if reflection.options[:autosave]
      record.errors.each do |attribute, message|
        attribute = "#{[reflection.name](http://reflection.name/)}.#{attribute}"
        errors[attribute] << message
        errors[attribute].uniq!
      end
    else
      errors.add([reflection.name](http://reflection.name/))
    end
  end
  valid
end

I then commented out the entire definition of the method and just returned true. Indeed, the error moved from the model to the database which also had a constraint on it. The debugging p statements in the client application both evaluated to true so I was sure that this was the piece of code responsible for setting the flag.

I often edit the actual gem source code to add debugging statements to it. You can find the path of the gem quite easily with bundler. In this case you can do:

bundle show activerecord

After you are done debugging, you can restore the gem to its former state by calling:

gem pristine activerecord

About the Author

Emile Swarts

Senior Software Engineer at Made Tech. All about big beards, beers and text editors from the seventies.

Avatar for Emile Swarts

We are hiring! Find out more about a career at Made Tech.

Download a copy of our new book

Legacy technology is one of the biggest threats to public sector organisations.
Whether you’ve started your journey already or don’t know where to begin, this 160-page book has been written to guide you to define and implement the right approach for your organisation.