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”.
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.