Validation on has_many :through relations

When I validate the association on a belongs_to association, I try to validate the model rather than the ID, so I would favor

validates :child

over

validates :child_id

However, I’ve noticed an annoying behavior if I am working with a join model for a has_many :through relation. If I validate the association instead of the foreign key, I get validation errors even when the setup for the models looks fine. Is there a best practice for dealing with this issue? I don’t like dialing back the validation requirements just to make the code “work”.

Just so I’m clear we have:

class Parent
  has_many :children
  has_many :grandchildren, through: :children
end

class Child
  belongs_to :parent
  has_many :grandchildren
end

class Grandchild
  belongs_to :child
  has_one :parent, through: :child
end

So when you save a grandchild are you checking the parent is valid? or is present?

I only ask because I wouldn’t expect to find a grandchild_id or parent_id on either end of a has_many :through.

I think I didn’t quite explain the situation correctly. This isn’t a typical parent-child-grandchild relationship, the relationship is two models that both have many of each other. Imagine a scenario with managers and employees where employees work on multiple teams, so they have multiple managers (and the managers have many employees). This example has terrible names, but does illustrate the problem:

class Manager
   has_many :employee_managers
   has_many :employees, through: :employee_managers
 end

class Employee 
  has_many :employee_managers
  has_many :managers, through: :employee_managers
end

# join model
class EmployeeManager
   belongs_to :employee
   belongs_to :manager

  # this causes a problem, but I don't get why
  validates :employee, presence: true
  validates :manager, presence: true

  # this doesn't, but isn't as thorough
  validates :employee_id, presence: true
  validates :manager_id, presence: true
end

I’m not using both those validation styles at the same time, but I have them both in there for comparison purposes.

Are you able to use has_and_belongs_to_many instead? Then you don’t have to define the EmployeeManager class at all. As you would only interact through the Manager and Employee classes, I think it’s impossible to end up with rows in your EmployeeManager table with only one side of the association.

So using has_and_belongs_to_many you’d have the following code:

class Manager
   has_and_belongs_to_many :employees
 end

class Employee 
  has_and_belongs_to_many :managers
end

You still need the third table and it should be call mangers_employees so your migration would be:

class CreateManagersAndEmployees < ActiveRecord::Migration
  def change
    create_table :managers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :employees do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :managers_employees do |t|
      t.belongs_to :manager
      t.belongs_to :employee
    end
  end
end

I can give that a try in some cases. However, some of these join models have other attributes (beyond the two IDs of the owning models), so I do have some situations that I have to use has_many :through.

Hmm, it does look like the validation you want should work. Whats the validation error you get when it’s setup correctly?