Techioz Blog

Rails で has_many::through 関連付けに外部キーを設定するにはどうすればよいですか?

概要

Item と ItemsBelonging という 2 つのテーブルがあります。アイテム間にツリー関係を作成しようとしています。アイテムのすべての子孫に対して、アイテムと子孫アイテムを結合する item_belonging を作成します。

Item {
  id:,
  ...
}

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

item.descendants を正常に呼び出して、項目のすべての子孫を受け取ることができます。次のような関係を設定しました。

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

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

問題は、同じ itemBelonging 関係を使用して item.ancestors を呼び出してアイテムのすべての祖先を取得できるようにしたいことです。私はこれを次のように行うことができます:

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

しかし、has_many/belongs_to を使用してこの関係を作成するにはどうすればよいでしょうか?これは動作しません:

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

私の最終目標は、Item.includes(:descendants) と Items.includes(:ancestors) を呼び出せるようにすることです。先祖関係をうまく機能させるにはどうすればよいですか?

解決策

ancestry gem を使用すると、このような DSL を簡単に使用できます

class Item < ApplicationRecord
  has_ancestry
end

そして、いくつかのレコードを作成します

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

そして、そのようなメソッドをすぐに使用できます

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]