1+ require "spec_helper"
2+
3+ RSpec . describe "has_closure_tree_roots" do
4+ let! ( :post ) { Post . create! ( title : "Test Post" ) }
5+ let! ( :post_reloaded ) { post . class . find ( post . id ) } # Ensures we're starting fresh
6+
7+ before do
8+ # Create a structure like this:
9+ # Post
10+ # |- Comment1
11+ # | |- Reply1-1
12+ # | |- Reply1-2
13+ # | |- Reply1-2-1
14+ # |- Comment2
15+ # |- Reply2-1
16+
17+ @comment1 = Comment . create! ( body : "Top comment 1" , post : post )
18+ @comment2 = Comment . create! ( body : "Top comment 2" , post : post )
19+
20+ @reply1_1 = Comment . create! ( body : "Reply 1-1" , post : post , parent : @comment1 )
21+ @reply1_2 = Comment . create! ( body : "Reply 1-2" , post : post , parent : @comment1 )
22+ @reply2_1 = Comment . create! ( body : "Reply 2-1" , post : post , parent : @comment2 )
23+
24+ @reply1_2_1 = Comment . create! ( body : "Reply 1-2-1" , post : post , parent : @reply1_2 )
25+ end
26+
27+ context "with basic config" do
28+ it "loads all root comments in a constant number of queries" do
29+ expect do
30+ roots = post_reloaded . comments_including_tree
31+ expect ( roots . size ) . to eq 2
32+ expect ( roots [ 0 ] . body ) . to eq "Top comment 1"
33+ expect ( roots [ 1 ] . body ) . to eq "Top comment 2"
34+ expect ( roots [ 0 ] . children [ 0 ] . body ) . to eq "Reply 1-1"
35+ expect ( roots [ 0 ] . children [ 1 ] . body ) . to eq "Reply 1-2"
36+ expect ( roots [ 0 ] . children [ 1 ] . children [ 0 ] . body ) . to eq "Reply 1-2-1"
37+ end . to_not exceed_query_limit ( 2 )
38+ end
39+
40+ it "eager loads inverse association to post" do
41+ expect do
42+ roots = post_reloaded . comments_including_tree
43+ expect ( roots [ 0 ] . post ) . to eq post
44+ expect ( roots [ 1 ] . post ) . to eq post
45+ expect ( roots [ 0 ] . children [ 0 ] . post ) . to eq post
46+ expect ( roots [ 0 ] . children [ 1 ] . children [ 0 ] . post ) . to eq post
47+ end . to_not exceed_query_limit ( 2 )
48+ end
49+
50+ it "memoizes by assoc_map" do
51+ post_reloaded . comments_including_tree . first . body = "changed1"
52+ expect ( post_reloaded . comments_including_tree . first . body ) . to eq "changed1"
53+ expect ( post_reloaded . comments_including_tree ( true ) . first . body ) . to eq "Top comment 1"
54+ end
55+
56+ it "works if true passed on first call" do
57+ expect ( post_reloaded . comments_including_tree ( true ) . first . body ) . to eq "Top comment 1"
58+ end
59+
60+ it "loads all nodes plus single association in a constant number of queries" do
61+ # Add some attributes to test with - similar to contracts in the root spec
62+ @comment1 . update! ( likes_count : 10 )
63+ @comment2 . update! ( likes_count : 5 )
64+ @reply1_1 . update! ( likes_count : 3 )
65+ @reply1_2 . update! ( likes_count : 7 )
66+ @reply2_1 . update! ( likes_count : 2 )
67+ @reply1_2_1 . update! ( likes_count : 4 )
68+
69+ expect do
70+ roots = post_reloaded . comments_including_tree
71+ expect ( roots . size ) . to eq 2
72+ expect ( roots [ 0 ] . body ) . to eq "Top comment 1"
73+ expect ( roots [ 0 ] . likes_count ) . to eq 10
74+ expect ( roots [ 0 ] . children [ 1 ] . likes_count ) . to eq 7
75+ expect ( roots [ 0 ] . children [ 1 ] . children [ 0 ] . likes_count ) . to eq 4
76+ expect ( roots [ 1 ] . children [ 0 ] . body ) . to eq "Reply 2-1"
77+ end . to_not exceed_query_limit ( 2 )
78+ end
79+
80+ it "loads all nodes and nested associations in a constant number of queries" do
81+ # Create some nested associations to test with
82+ user1 = User . create! ( email :
"[email protected] " ) 83+ user2 = User . create! ( email :
"[email protected] " ) 84+ user3 = User . create! ( email :
"[email protected] " ) 85+
86+ # Create comment_likes instead of using contracts
87+ @comment1 . comment_likes . create! ( user : user1 )
88+ @comment2 . comment_likes . create! ( user : user2 )
89+ @reply1_1 . comment_likes . create! ( user : user3 )
90+
91+ expect do
92+ roots = post_reloaded . comments_including_tree ( comment_likes : :user )
93+ expect ( roots . size ) . to eq 2
94+ expect ( roots [ 0 ] . body ) . to eq "Top comment 1"
95+ expect ( roots [ 0 ] . comment_likes . first . user . email ) . to eq "[email protected] " 96+ expect ( roots [ 1 ] . comment_likes . first . user . email ) . to eq "[email protected] " 97+ expect ( roots [ 0 ] . children [ 0 ] . comment_likes . first . user . email ) . to eq "[email protected] " 98+ end . to_not exceed_query_limit ( 4 ) # Without optimization, this would scale with number of nodes
99+ end
100+
101+ context "with no comment roots" do
102+ let ( :empty_post ) { Post . create! ( title : "Empty Post" ) }
103+
104+ it "should return empty array" do
105+ expect ( empty_post . comments_including_tree ) . to eq ( [ ] )
106+ end
107+ end
108+ end
109+
110+ context "when comment is destroyed" do
111+ it "properly maintains the hierarchy" do
112+ @comment1 . destroy
113+ roots = post_reloaded . comments_including_tree
114+ expect ( roots . size ) . to eq 1
115+ expect ( roots [ 0 ] . body ) . to eq "Top comment 2"
116+ expect ( roots [ 0 ] . children [ 0 ] . body ) . to eq "Reply 2-1"
117+ end
118+ end
119+
120+ context "when comment is added after initial load" do
121+ it "includes the new comment when reloaded" do
122+ roots = post_reloaded . comments_including_tree
123+ expect ( roots . size ) . to eq 2
124+
125+ new_comment = Comment . create! ( body : "New top comment" , post : post )
126+
127+ # Should be memoized, so still 2
128+ expect ( post_reloaded . comments_including_tree . size ) . to eq 2
129+
130+ # With true, should reload and find 3
131+ expect ( post_reloaded . comments_including_tree ( true ) . size ) . to eq 3
132+ end
133+ end
134+
135+ context "with nested comment creation" do
136+ it "properly builds the hierarchy" do
137+ # Create a new root comment with nested children
138+ new_comment = Comment . new ( body : "New root" , post : post )
139+ reply1 = Comment . new ( body : "New reply 1" , post : post )
140+ reply2 = Comment . new ( body : "New reply 2" , post : post )
141+
142+ new_comment . children << reply1
143+ new_comment . children << reply2
144+
145+ new_comment . save!
146+
147+ roots = post_reloaded . comments_including_tree ( true )
148+ new_root = roots . find { |r | r . body == "New root" }
149+
150+ expect ( new_root . children . size ) . to eq 2
151+ expect ( new_root . children . map ( &:body ) ) . to include ( "New reply 1" , "New reply 2" )
152+ end
153+ end
154+ end
0 commit comments