Skip to content

Commit 7269191

Browse files
committed
Add through relationships tests
1 parent b6021a0 commit 7269191

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# SPDX-FileCopyrightText: 2019 ash_postgres contributors <https://github.com/ash-project/ash_postgres/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule AshPostgres.Test.ThroughRelationshipsTest do
6+
use AshPostgres.RepoCase, async: false
7+
8+
require Ash.Query
9+
10+
setup do
11+
school_1 = create_school("School One")
12+
school_2 = create_school("School Two")
13+
14+
classroom_1 = create_classroom("Math 101", school_1.id)
15+
classroom_2 = create_classroom("Science 101", school_1.id)
16+
classroom_3 = create_classroom("History 101", school_2.id)
17+
18+
teacher_1 = create_teacher("Mr. Smith")
19+
teacher_2 = create_teacher("Ms. Johnson")
20+
teacher_3 = create_teacher("Dr. Williams")
21+
teacher_4 = create_teacher("Prof. Adams")
22+
23+
student_1 = create_student("Alice", classroom_1.id)
24+
student_2 = create_student("Bob", classroom_2.id)
25+
26+
%{
27+
school_1: school_1,
28+
school_2: school_2,
29+
classroom_1: classroom_1,
30+
classroom_2: classroom_2,
31+
classroom_3: classroom_3,
32+
teacher_1: teacher_1,
33+
teacher_2: teacher_2,
34+
teacher_3: teacher_3,
35+
teacher_4: teacher_4,
36+
student_1: student_1,
37+
student_2: student_2
38+
}
39+
end
40+
41+
describe "has_many through relationships" do
42+
test "loads teachers through classrooms -> classroom_teachers -> teacher", setup do
43+
%{school_1: school_1, classroom_1: classroom_1, classroom_2: classroom_2} = setup
44+
%{teacher_1: teacher_1, teacher_2: teacher_2, teacher_3: teacher_3} = setup
45+
46+
assign_teacher(classroom_1.id, teacher_1.id)
47+
assign_teacher(classroom_1.id, teacher_2.id)
48+
assign_teacher(classroom_2.id, teacher_2.id)
49+
assign_teacher(classroom_2.id, teacher_3.id)
50+
51+
school_with_teachers = Ash.load!(school_1, :teachers)
52+
53+
teacher_names =
54+
school_with_teachers.teachers
55+
|> Enum.map(& &1.name)
56+
|> Enum.sort()
57+
58+
assert teacher_names == ["Dr. Williams", "Mr. Smith", "Ms. Johnson"]
59+
end
60+
61+
test "has_many through with no results", setup do
62+
%{school_2: school_2} = setup
63+
64+
school_with_teachers = Ash.load!(school_2, :teachers)
65+
assert school_with_teachers.teachers == []
66+
end
67+
68+
test "has_many through with empty intermediate results", setup do
69+
%{school_1: school_1} = setup
70+
71+
school_with_teachers = Ash.load!(school_1, :teachers)
72+
assert school_with_teachers.teachers == []
73+
end
74+
75+
test "3-hop path: school -> classrooms -> classroom_teachers -> teacher", setup do
76+
%{school_1: school_1, classroom_1: classroom_1} = setup
77+
%{teacher_1: teacher_1} = setup
78+
79+
assign_teacher(classroom_1.id, teacher_1.id)
80+
81+
school_with_teachers = Ash.load!(school_1, :teachers)
82+
83+
assert length(school_with_teachers.teachers) == 1
84+
assert hd(school_with_teachers.teachers).name == "Mr. Smith"
85+
end
86+
end
87+
88+
describe "many_to_many through atom list" do
89+
test "retired_teachers loads only teachers with retired_at set", setup do
90+
%{classroom_1: classroom_1} = setup
91+
%{teacher_1: teacher_1, teacher_2: teacher_2} = setup
92+
93+
assign_teacher(classroom_1.id, teacher_1.id)
94+
assign_teacher(classroom_1.id, teacher_2.id)
95+
96+
classroom_with_retired = Ash.load!(classroom_1, :retired_teachers)
97+
98+
assert length(classroom_with_retired.retired_teachers) == 1
99+
assert hd(classroom_with_retired.retired_teachers).name == teacher_1.name
100+
end
101+
102+
test "retired_teachers returns empty when no teachers are retired", setup do
103+
%{classroom_1: classroom_1} = setup
104+
%{teacher_1: teacher_1} = setup
105+
106+
assign_teacher(classroom_1.id, teacher_1.id)
107+
108+
classroom_with_retired = Ash.load!(classroom_1, :retired_teachers)
109+
assert classroom_with_retired.retired_teachers == []
110+
end
111+
112+
test "teacher many_to_many classrooms via atom list through", setup do
113+
%{classroom_1: classroom_1, classroom_2: classroom_2} = setup
114+
%{teacher_1: teacher_1} = setup
115+
116+
assign_teacher(classroom_1.id, teacher_1.id)
117+
assign_teacher(classroom_2.id, teacher_1.id)
118+
119+
teacher_with_classrooms = Ash.load!(teacher_1, :classrooms)
120+
121+
classroom_names =
122+
teacher_with_classrooms.classrooms
123+
|> Enum.map(& &1.name)
124+
|> Enum.sort()
125+
126+
assert classroom_names == ["Math 101", "Science 101"]
127+
end
128+
end
129+
130+
describe "has_one through relationships" do
131+
test "active_teacher loads the non-retired teacher", setup do
132+
%{classroom_1: classroom_1} = setup
133+
%{teacher_1: teacher_1, teacher_2: teacher_2} = setup
134+
135+
assign_teacher(classroom_1.id, teacher_1.id)
136+
assign_teacher(classroom_1.id, teacher_2.id)
137+
138+
classroom_loaded = Ash.load!(classroom_1, :active_teacher)
139+
assert classroom_loaded.active_teacher.name == teacher_2.name
140+
end
141+
142+
test "student teacher through classroom active_teacher", setup do
143+
%{classroom_1: classroom_1} = setup
144+
%{teacher_1: teacher_1, teacher_2: teacher_2} = setup
145+
%{student_1: student_1} = setup
146+
147+
assign_teacher(classroom_1.id, teacher_1.id)
148+
assign_teacher(classroom_1.id, teacher_2.id)
149+
150+
student_with_teacher = Ash.load!(student_1, :teacher)
151+
assert student_with_teacher.teacher.name == teacher_2.name
152+
end
153+
end
154+
155+
describe "aggregates on through relationships" do
156+
test "school teacher_count counts all teachers through path", setup do
157+
%{school_1: school_1, classroom_1: classroom_1, classroom_2: classroom_2} = setup
158+
%{teacher_1: teacher_1, teacher_2: teacher_2} = setup
159+
160+
assign_teacher(classroom_1.id, teacher_1.id)
161+
assign_teacher(classroom_2.id, teacher_1.id)
162+
assign_teacher(classroom_2.id, teacher_2.id)
163+
164+
school_with_agg =
165+
AshPostgres.Test.Through.School
166+
|> Ash.Query.filter(id == ^school_1.id)
167+
|> Ash.Query.load([:classroom_count, :teacher_count, :teacher_count_via_path])
168+
|> Ash.read_one!()
169+
170+
assert school_with_agg.classroom_count == 2
171+
assert school_with_agg.teacher_count == 3
172+
assert school_with_agg.teacher_count_via_path == 3
173+
end
174+
175+
test "school retired_teacher_count counts only retired", setup do
176+
%{school_1: school_1, classroom_1: classroom_1} = setup
177+
%{teacher_1: teacher_1, teacher_2: teacher_2, teacher_3: teacher_3} = setup
178+
179+
assign_teacher(classroom_1.id, teacher_1.id)
180+
assign_teacher(classroom_1.id, teacher_2.id)
181+
182+
school_with_agg =
183+
AshPostgres.Test.Through.School
184+
|> Ash.Query.filter(id == ^school_1.id)
185+
|> Ash.Query.load(:retired_teacher_count)
186+
|> Ash.Query.load(:active_teacher_count)
187+
|> Ash.read_one!()
188+
189+
assert school_with_agg.retired_teacher_count == 1
190+
assert school_with_agg.active_teacher_count == 1
191+
192+
assign_teacher(classroom_1.id, teacher_3.id)
193+
194+
school_with_agg =
195+
AshPostgres.Test.Through.School
196+
|> Ash.Query.filter(id == ^school_1.id)
197+
|> Ash.Query.load(:retired_teacher_count)
198+
|> Ash.Query.load(:active_teacher_count)
199+
|> Ash.read_one!()
200+
201+
assert school_with_agg.retired_teacher_count == 2
202+
assert school_with_agg.active_teacher_count == 1
203+
end
204+
205+
test "multiple schools with aggregates", setup do
206+
%{school_1: school_1, school_2: school_2} = setup
207+
%{classroom_1: classroom_1, classroom_2: classroom_2, classroom_3: classroom_3} = setup
208+
209+
%{teacher_1: teacher_1, teacher_2: teacher_2, teacher_3: teacher_3, teacher_4: teacher_4} =
210+
setup
211+
212+
assign_teacher(classroom_1.id, teacher_1.id)
213+
assign_teacher(classroom_1.id, teacher_2.id)
214+
assign_teacher(classroom_2.id, teacher_3.id)
215+
assign_teacher(classroom_3.id, teacher_2.id)
216+
assign_teacher(classroom_3.id, teacher_3.id)
217+
assign_teacher(classroom_3.id, teacher_4.id)
218+
219+
[school_one, school_two] =
220+
AshPostgres.Test.Through.School
221+
|> Ash.Query.load([:classroom_count, :teacher_count, :teacher_count_via_path])
222+
|> Ash.Query.filter(id in [^school_1.id, ^school_2.id])
223+
|> Ash.Query.sort(:name)
224+
|> Ash.read!()
225+
226+
assert school_one.name == "School One"
227+
assert school_one.classroom_count == 2
228+
assert school_one.teacher_count == 3
229+
assert school_one.teacher_count_via_path == 3
230+
231+
assert school_two.name == "School Two"
232+
assert school_two.classroom_count == 1
233+
assert school_two.teacher_count == 3
234+
assert school_two.teacher_count_via_path == 3
235+
end
236+
237+
test "students know their active teacher", setup do
238+
%{school_1: school_1, classroom_1: classroom_1, classroom_2: classroom_2} = setup
239+
240+
%{teacher_1: teacher_1, teacher_2: teacher_2, teacher_3: teacher_3, teacher_4: teacher_4} =
241+
setup
242+
243+
%{student_1: student_1, student_2: student_2} = setup
244+
245+
assign_teacher(classroom_1.id, teacher_1.id)
246+
assign_teacher(classroom_2.id, teacher_2.id)
247+
248+
student_1 = Ash.load!(student_1, [:teacher, :retired_teacher_count])
249+
student_2 = Ash.load!(student_2, [:teacher, :retired_teacher_count])
250+
251+
assign_teacher(classroom_1.id, teacher_3.id)
252+
assign_teacher(classroom_2.id, teacher_4.id)
253+
254+
student_1 = Ash.load!(student_1, [:teacher, :retired_teacher_count])
255+
student_2 = Ash.load!(student_2, [:teacher, :retired_teacher_count])
256+
257+
assert student_1.teacher.name == teacher_3.name
258+
assert student_1.retired_teacher_count == 1
259+
assert student_2.teacher.name == teacher_4.name
260+
assert student_2.retired_teacher_count == 1
261+
262+
school_1 = Ash.load!(school_1, [:retired_teacher_count, :active_teacher_count])
263+
264+
assert school_1.retired_teacher_count == 2
265+
assert school_1.active_teacher_count == 2
266+
end
267+
end
268+
269+
defp create_school(name) do
270+
AshPostgres.Test.Through.School
271+
|> Ash.Changeset.for_create(:create, %{name: name})
272+
|> Ash.create!()
273+
end
274+
275+
defp create_classroom(name, school_id) do
276+
AshPostgres.Test.Through.Classroom
277+
|> Ash.Changeset.for_create(:create, %{name: name, school_id: school_id})
278+
|> Ash.create!()
279+
end
280+
281+
defp create_teacher(name) do
282+
AshPostgres.Test.Through.Teacher
283+
|> Ash.Changeset.for_create(:create, %{name: name})
284+
|> Ash.create!()
285+
end
286+
287+
defp create_student(name, classroom_id) do
288+
AshPostgres.Test.Through.Student
289+
|> Ash.Changeset.for_create(:create, %{name: name, classroom_id: classroom_id})
290+
|> Ash.create!()
291+
end
292+
293+
defp assign_teacher(classroom_id, teacher_id, opts \\ []) do
294+
attrs =
295+
%{classroom_id: classroom_id, teacher_id: teacher_id}
296+
|> Map.merge(Map.new(opts))
297+
298+
AshPostgres.Test.Through.ClassroomTeacher
299+
|> Ash.Changeset.for_create(:assign, attrs)
300+
|> Ash.create!()
301+
end
302+
end

0 commit comments

Comments
 (0)