Jonathan Bennett

How to connect the dots in your relationships

One of the things that makes Rails “magical” is that it’s default behaviour is most likely what you would want. One place that this doesn’t happen is when connecting both sides of an association when it isn’t following the normal Rails conventions. An example of this is a self referential association:

class User < ApplicationRecord
  has_many :subordinates, class_name: "User", foreign_key: :manager_id
  belongs_to :manager, class_name: "User", optional: true
end

manager = User.new
employee = manager.subordinates.build
puts employee.manager # nil ?!?

Well that won’t do. Whats going wrong?

What happens normally?

Rails generally does a good job of automatically guessing what the relationships are based on their names.

Normally, if you had a Users class with has_many :posts, rails would:

  1. Assume the Post class based on the association name.
  2. Assume the foreign key field user_id based on the User class.
  3. Connect to the association user the Post class.

This works fine in the simple use case, and we can solve problems #1 and #2 by adding the class_name and foreign_key options but that doesn’t resolve #3.

Fixing the relationship

To fix the relationship, we need to explicitly tell Rails which association is on the other side of the subordinates association:

class User < ApplicationRecord
  has_many :subordinates, class_name: "User", foreign_key: :manager_id, inverse_of: :manager
  belongs_to :manager, class_name: "User", optional: true
end

We can test this by printing out the object IDs and see that the match, meaning this is one single instance:

manager = User.new
employee = manager.subordinates.build
puts manager.object_id # 1234
puts employee.manager.object_id # 1234