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