How do I set foreign_keys on a has_many: :through association in Rails?

42 Views Asked by At

I have 2 tables, Item and ItemBelonging. I'm trying to create tree relationships between items. For every descendant of an item I create a item_belonging to join the item and the descendent item.

Item {
  id:,
  ...
}

ItemBelonging {
  id:,
  item_id:,
  descendant_id:,
  ...
}

I am able to successfully call item.descendants to receive all descendants of an item. I have set up the relationships like this:

class ItemBelonging < ApplicationRecord
  belongs_to :descendant, :class_name => 'Item'
  ...
end

class Item < ApplicationRecord
  has_many :descendants, :through => :item_belongings, :source => :descendant
  ...
end

The problem is that I would like to be able to call item.ancestors to get all ancestors of an item using the same ItemBelonging relationship. I am able to do this like so:

  def ancestors
    Item.joins(:item_belongings).where("item_belongings.descendant_id = ?", id)
  end

But how do I create this relationship using has_many/belongs_to? This does not work:

class ItemBelonging < ApplicationRecord
  belongs_to :descendant, :class_name => 'Item'
  belongs_to :ancestor, :class_name => 'Item', :foreign_key => :item_id
  ...
end

class Item < ApplicationRecord
  has_many :descendants, :through => :item_belongings, :source => :descendant
  has_many :ancestors, :through => :item_belongings, :source => :ancestor, :foreign_key => :descendant_id
  ...
end

My end goal is to be able to call Item.includes(:descendants) and Item.includes(:ancestors). How do I get the ancestors relationship working?

1

There are 1 best solutions below

1
mechnicov On

With ancestry gem you can easily use such DSL

class Item < ApplicationRecord
  has_ancestry
end

And then create few records

parent = Item.create
first_child = Item.create(parent: parent)
second_child = Item.create(parent: parent)
first_child_child = Item.create(parent: first_child)

And use such methods out of box

parent.children # => [first_child, second_child]
parent.descendants # => [first_child, second_child, first_child_child]
first_child.siblings # => [first_child, second_child]
first_child.subtree # => [first_child, first_child_child]
first_child_child.parent # => first_child
first_child_child.root # => parent
first_child_child.ancestors # => [parent, first_child]
first_child_child.path # => [parent, first_child, first_child_child]