'Create grandchildren associations in the same Rails model

I want to get something like this in one class using ActiveRecord:

grandfather = Person.create(name: "Grandfather")
son = Person.create(name: "Son", parent: grandfather)
grandson = Person.create(name: "Grandson", parent: son)

grandfather.children.map(&:name)
=> ["Son"]

grandfather.grandchildren.map(&:name)
=> ["Grandson"]

I wrote the children association this way:

class Person < ActiveRecord::Base
  belongs_to :parent, class_name: "Person", foreign_key: "parent_id"
  has_many :children, class_name: "Person", foreign_key: "parent_id"
end

And it works, but I got stuck with grandchildren. Any ideas?



Solution 1:[1]

You can start with something like this:

Models

Person
Child
Grandchild

A Person has_many :children
A Child belongs_to :person
A Child has_many :grandchildren
A Grandchild belongs_to :child

Till now we haven't established the relationship between Person and Grandchild, so we basically need to use Child relationship to establish this relationship with has_many :through associations, So:

A Person has_many :grandchildren, through: :children
A Grandchild belongs_to: person, through: :children

I have not tested this code
Hope this helps

Solution 2:[2]

I tried to defined by association has_many :grandchildren, through: :children, but failed. Rails complains SystemStackError: stack level too deep

But you can defined as a method,

def grandchildren
    Person.where(parent: children)
end

Solution 3:[3]

I tried this and this works for me to find grand children of a Human.

class Human < ApplicationRecord
  has_many :children, :foreign_key => "parent_id" , :class_name => "Human"
  belongs_to :parent, :class_name => "Human", optional: true
  has_many :grand_children,  class_name: :Human, through: :children, source: :children
end

But I couldn't find way to reverse the association to find grand parent of a human. I tried:

  belongs_to :grand_parent, class_name: :Human, optional: true

but got nil class.

I fix it by following

class Human < ApplicationRecord
  has_many :children, :foreign_key => "parent_id" , :class_name => "Human"
  belongs_to :parent, :class_name => "Human", optional: true
  has_many :grand_children,  class_name: :Human, through: :children, source: :children
  has_one :grand_parent, class_name: :Human, through: :parent, source: :parent
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 new2cpp
Solution 3