@@ -37,15 +37,24 @@ def find_or_create_by_path(path, attributes = {})
37
37
end
38
38
39
39
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 )
49
58
_ct . scope_with_order ( s )
50
59
end
51
60
@@ -72,14 +81,23 @@ def root
72
81
end
73
82
74
83
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 )
83
101
_ct . scope_with_order ( s . readonly ( false ) )
84
102
end
85
103
@@ -123,22 +141,41 @@ def lowest_common_ancestor(*descendants)
123
141
end
124
142
125
143
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 )
142
179
_ct . scope_with_order ( s )
143
180
end
144
181
@@ -151,6 +188,7 @@ def find_by_path(path, attributes = {}, parent_id = nil)
151
188
152
189
scope = where ( path . pop )
153
190
last_joined_table = _ct . table_name
191
+
154
192
path . reverse . each_with_index do |ea , idx |
155
193
next_joined_table = "p#{ idx } "
156
194
scope = scope . joins ( <<-SQL . squish )
@@ -161,6 +199,7 @@ def find_by_path(path, attributes = {}, parent_id = nil)
161
199
scope = _ct . scoped_attributes ( scope , ea , next_joined_table )
162
200
last_joined_table = next_joined_table
163
201
end
202
+
164
203
scope . where ( "#{ last_joined_table } .#{ _ct . parent_column_name } " => parent_id ) . readonly ( false ) . first
165
204
end
166
205
0 commit comments