@@ -37,15 +37,24 @@ def find_or_create_by_path(path, attributes = {})
3737 end
3838
3939 def find_all_by_generation ( generation_level )
40- s = _ct . base_class . joins ( <<-SQL . squish )
41- INNER JOIN (
42- SELECT descendant_id
43- FROM #{ _ct . quoted_hierarchy_table_name }
44- WHERE ancestor_id = #{ _ct . quote ( id ) }
45- GROUP BY descendant_id
46- HAVING MAX(#{ _ct . quoted_hierarchy_table_name } .generations) = #{ generation_level . to_i }
47- ) #{ _ct . t_alias_keyword } descendants ON (#{ _ct . quoted_table_name } .#{ _ct . base_class . primary_key } = descendants.descendant_id)
48- SQL
40+ hierarchy_table = self . class . hierarchy_class . arel_table
41+ model_table = self . class . arel_table
42+
43+ # Build the subquery
44+ descendants_subquery = hierarchy_table
45+ . project ( hierarchy_table [ :descendant_id ] )
46+ . where ( hierarchy_table [ :ancestor_id ] . eq ( id ) )
47+ . group ( hierarchy_table [ :descendant_id ] )
48+ . having ( hierarchy_table [ :generations ] . maximum . eq ( generation_level . to_i ) )
49+ . as ( 'descendants' )
50+
51+ # Build the join
52+ join_source = model_table
53+ . join ( descendants_subquery )
54+ . on ( model_table [ _ct . base_class . primary_key ] . eq ( descendants_subquery [ :descendant_id ] ) )
55+ . join_sources
56+
57+ s = _ct . base_class . joins ( join_source )
4958 _ct . scope_with_order ( s )
5059 end
5160
@@ -72,14 +81,23 @@ def root
7281 end
7382
7483 def leaves
75- s = joins ( <<-SQL . squish )
76- INNER JOIN (
77- SELECT ancestor_id
78- FROM #{ _ct . quoted_hierarchy_table_name }
79- GROUP BY ancestor_id
80- HAVING MAX(#{ _ct . quoted_hierarchy_table_name } .generations) = 0
81- ) #{ _ct . t_alias_keyword } leaves ON (#{ _ct . quoted_table_name } .#{ primary_key } = leaves.ancestor_id)
82- SQL
84+ hierarchy_table = hierarchy_class . arel_table
85+ model_table = arel_table
86+
87+ # Build the subquery for leaves (nodes with no children)
88+ leaves_subquery = hierarchy_table
89+ . project ( hierarchy_table [ :ancestor_id ] )
90+ . group ( hierarchy_table [ :ancestor_id ] )
91+ . having ( hierarchy_table [ :generations ] . maximum . eq ( 0 ) )
92+ . as ( 'leaves' )
93+
94+ # Build the join
95+ join_source = model_table
96+ . join ( leaves_subquery )
97+ . on ( model_table [ primary_key ] . eq ( leaves_subquery [ :ancestor_id ] ) )
98+ . join_sources
99+
100+ s = joins ( join_source )
83101 _ct . scope_with_order ( s . readonly ( false ) )
84102 end
85103
@@ -123,22 +141,41 @@ def lowest_common_ancestor(*descendants)
123141 end
124142
125143 def find_all_by_generation ( generation_level )
126- s = joins ( <<-SQL . squish )
127- INNER JOIN (
128- SELECT #{ primary_key } as root_id
129- FROM #{ _ct . quoted_table_name }
130- WHERE #{ _ct . quoted_parent_column_name } IS NULL
131- ) #{ _ct . t_alias_keyword } roots ON (1 = 1)
132- INNER JOIN (
133- SELECT ancestor_id, descendant_id
134- FROM #{ _ct . quoted_hierarchy_table_name }
135- GROUP BY ancestor_id, descendant_id
136- HAVING MAX(generations) = #{ generation_level . to_i }
137- ) #{ _ct . t_alias_keyword } descendants ON (
138- #{ _ct . quoted_table_name } .#{ primary_key } = descendants.descendant_id
139- AND roots.root_id = descendants.ancestor_id
140- )
141- SQL
144+ hierarchy_table = hierarchy_class . arel_table
145+ model_table = arel_table
146+
147+ # Build the roots subquery
148+ roots_subquery = model_table
149+ . project ( model_table [ primary_key ] . as ( 'root_id' ) )
150+ . where ( model_table [ _ct . parent_column_sym ] . eq ( nil ) )
151+ . as ( 'roots' )
152+
153+ # Build the descendants subquery
154+ descendants_subquery = hierarchy_table
155+ . project (
156+ hierarchy_table [ :ancestor_id ] ,
157+ hierarchy_table [ :descendant_id ]
158+ )
159+ . group ( hierarchy_table [ :ancestor_id ] , hierarchy_table [ :descendant_id ] )
160+ . having ( hierarchy_table [ :generations ] . maximum . eq ( generation_level . to_i ) )
161+ . as ( 'descendants' )
162+
163+ # Build the joins
164+ # Note: We intentionally use a cartesian product join (CROSS JOIN) here.
165+ # This allows us to find all nodes at a specific generation level across all root nodes.
166+ # The 1=1 condition creates this cartesian product in a database-agnostic way.
167+ join_roots = model_table
168+ . join ( roots_subquery )
169+ . on ( Arel . sql ( '1 = 1' ) )
170+
171+ join_descendants = join_roots
172+ . join ( descendants_subquery )
173+ . on (
174+ model_table [ primary_key ] . eq ( descendants_subquery [ :descendant_id ] )
175+ . and ( roots_subquery [ :root_id ] . eq ( descendants_subquery [ :ancestor_id ] ) )
176+ )
177+
178+ s = joins ( join_descendants . join_sources )
142179 _ct . scope_with_order ( s )
143180 end
144181
@@ -151,6 +188,7 @@ def find_by_path(path, attributes = {}, parent_id = nil)
151188
152189 scope = where ( path . pop )
153190 last_joined_table = _ct . table_name
191+
154192 path . reverse . each_with_index do |ea , idx |
155193 next_joined_table = "p#{ idx } "
156194 scope = scope . joins ( <<-SQL . squish )
@@ -161,6 +199,7 @@ def find_by_path(path, attributes = {}, parent_id = nil)
161199 scope = _ct . scoped_attributes ( scope , ea , next_joined_table )
162200 last_joined_table = next_joined_table
163201 end
202+
164203 scope . where ( "#{ last_joined_table } .#{ _ct . parent_column_name } " => parent_id ) . readonly ( false ) . first
165204 end
166205
0 commit comments