ActiveRecordで作った木構造のリーフのみ取りたい。
やりたい
has_many
関連がempty?
を返すようなレコードを上から一気に取りたいですね。
前提
- Rails 4.2.6
class Node < ActiveRecord::Base
belongs_to :owner
belongs_to :parent, class_name: Node, foreign_key: :node_id
has_many :nodes
# 略
end
Node
は必ずowner
をもち、木の取得に使いますので、この話では木自体の取得は扱いません(データベースを用いた木構造のさまざまな話はSQLアンチパターンに色々載っていておもしろいですよ)
クエリがいっぱい出るメソッド
owner
からルートを取得し、以下のようなメソッドで掘っていく方法があります。
class Node < ActiveRecord::Base
# 略
def leaf?
nodes.empty?
end
def pick_leaves(picked = [])
return picked << self if leaf?
nodes.includes(:nodes).inject(picked) do |a, node|
node.pick_leaves(a)
end
end
end
(オーバーフローするかしら)
1クエリでとる
今回は木の構造は問題にされておらず、ただリーフが欲しいだけですので、比較的に簡単に取得できます。
owner
配下のNode
をマトにして、子ノードを持たないノードを返す、というクエリをなげます。
class Owner < ActiveRecord::Base
has_many :nodes
def leaves
nodes.includes(:nodes).references(:nodes).where(nodes_nodes: {id: nil})
end
end
includes(:nodes).references(:nodes)
でLEFT OUTER JOIN
を行います。全てのNode
から子ノードが参照できる状態になるので、子ノードがいない、つまりリーフを抽出します。
なおjoins
を用いたINNER JOIN
では、逆にリーフが除外されます。