DEV Community

Shamila TP
Shamila TP

Posted on

Self Referential Associations in Rails - simpler than it sounds

Tbh I had no idea this was even a thing until recently. I've been working with Rails for a while now and somehow never came across it.

So let me explain it the way I understood it.

You know how we normally do associations in Rails, User has many Posts, Post belongs to User. Two different models, two different tables. Simple.

But what if a model needs to reference itself? Like same table, same model, but pointing to another record in the same table?

That's all a self referential association is.

The example that made it click for me was Employee and Manager. A manager is just another employee right? So both live in the same employees table.

Let's build it:

rails new self_referential --api
cd self_referential

rails g model Employee name:string manager_id:integer
rails db:migrate

Enter fullscreen mode Exit fullscreen mode

The key column here is manager_id — it just points back to the same table.

Now if you go to Rails console and try alice.manager, you'll get a NoMethodError. Makes sense, we haven't told Rails anything yet.

Here's where you add this to the model:

class Employee < ApplicationRecord
  belongs_to :manager, class_name: "Employee",
                       foreign_key: :manager_id,
                       optional: true

  has_many :subordinates, class_name: "Employee",
                          foreign_key: :manager_id
end

Enter fullscreen mode Exit fullscreen mode

The reason we need class_name and foreign_key, Rails tries to guess the table from the association name. belongs_to :manager makes Rails look for a managers table. That doesn't exist. So we just tell it explicitly, look in employees table, use manager_id column. That's it.

Now this works:

john  = Employee.create!(name: "John")
alice = Employee.create!(name: "Alice", manager: john)
bob   = Employee.create!(name: "Bob",   manager: john)
carol = Employee.create!(name: "Carol", manager: alice)

alice.manager.name             # => "John"
john.subordinates.map(&:name)  # => ["Alice", "Bob"]
carol.manager.name             # => "Alice"
john.manager                   # => nil
Enter fullscreen mode Exit fullscreen mode

One table, one model, navigate relationships in any direction.

Once you get this pattern you'll start seeing it everywhere, comment replies with parent_id, users following other users, categories with subcategories. Same idea every time.

honestly, it sounds scarier than it is. Just a column pointing back to the same table with a bit of guidance for Rails.

Top comments (0)