has_many :through and the case of the missing include

tl;dr – you need to specify the “inverse_of” option on your has_many/belongs_to relationship to get your children fully populated

So I came across an interesting situation the other day, where my includes() statement was preloading data, but then when accessing it I would see another SQL query.

require 'active_record'

class Parent < ActiveRecord::Base
attr_accessible :name
has_many :parent_children, inverse_of: :children
has_many :children, through: :parent_children

default_scope includes(:parent_children)
end

class ParentChildren < ActiveRecord::Base
attr_accessible :parent_id, :child_id
belongs_to :parents, inverse_of: :parent_children
belongs_to :children

default_scope includes(:children)
end

class Child < ActiveRecord::Base
attr_accessible :name
has_many :parent_children
has_many :parents, through: :parent_children
end

Parent.all.each {|p| p.children.each {|c| puts c.name}}

Without the :inverse_of here, each call to write the childs name will result in a new SQL query to get the data, even though this data has already been prefetched (eager loaded).