preload.exs (28767B)
1 defmodule Ecto.Integration.PreloadTest do 2 use Ecto.Integration.Case, async: Application.compile_env(:ecto, :async_integration_tests, true) 3 4 alias Ecto.Integration.TestRepo 5 import Ecto.Query 6 7 alias Ecto.Integration.Post 8 alias Ecto.Integration.Comment 9 alias Ecto.Integration.Item 10 alias Ecto.Integration.Permalink 11 alias Ecto.Integration.User 12 alias Ecto.Integration.Custom 13 alias Ecto.Integration.Order 14 15 test "preload with parameter from select_merge" do 16 p1 = TestRepo.insert!(%Post{title: "p1"}) 17 TestRepo.insert!(%Comment{text: "c1", post: p1}) 18 19 comments = 20 from(c in Comment, select: struct(c, [:text])) 21 |> select_merge([c], %{post_id: c.post_id}) 22 |> preload(:post) 23 |> TestRepo.all() 24 25 assert [%{text: "c1", post: %{title: "p1"}}] = comments 26 end 27 28 test "preload has_many" do 29 p1 = TestRepo.insert!(%Post{title: "1"}) 30 p2 = TestRepo.insert!(%Post{title: "2"}) 31 p3 = TestRepo.insert!(%Post{title: "3"}) 32 33 # We use the same text to expose bugs in preload sorting 34 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 35 %Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id}) 36 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 37 %Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 38 39 assert %Ecto.Association.NotLoaded{} = p1.comments 40 41 [p3, p1, p2] = TestRepo.preload([p3, p1, p2], :comments) 42 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id() 43 assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id() 44 assert [] = p3.comments 45 end 46 47 test "preload has_many multiple times" do 48 p1 = TestRepo.insert!(%Post{title: "1"}) 49 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 50 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 51 52 [p1, p1] = TestRepo.preload([p1, p1], :comments) 53 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id() 54 55 [p1, p1] = TestRepo.preload([p1, p1], :comments) 56 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id() 57 end 58 59 test "preload has_one" do 60 p1 = TestRepo.insert!(%Post{title: "1"}) 61 p2 = TestRepo.insert!(%Post{title: "2"}) 62 p3 = TestRepo.insert!(%Post{title: "3"}) 63 64 %Permalink{id: pid1} = TestRepo.insert!(%Permalink{url: "1", post_id: p1.id}) 65 %Permalink{} = TestRepo.insert!(%Permalink{url: "2", post_id: nil}) 66 %Permalink{id: pid3} = TestRepo.insert!(%Permalink{url: "3", post_id: p3.id}) 67 68 assert %Ecto.Association.NotLoaded{} = p1.permalink 69 assert %Ecto.Association.NotLoaded{} = p2.permalink 70 71 [p3, p1, p2] = TestRepo.preload([p3, p1, p2], :permalink) 72 assert %Permalink{id: ^pid1} = p1.permalink 73 refute p2.permalink 74 assert %Permalink{id: ^pid3} = p3.permalink 75 end 76 77 test "preload belongs_to" do 78 %Post{id: pid1} = TestRepo.insert!(%Post{title: "1"}) 79 TestRepo.insert!(%Post{title: "2"}) 80 %Post{id: pid3} = TestRepo.insert!(%Post{title: "3"}) 81 82 pl1 = TestRepo.insert!(%Permalink{url: "1", post_id: pid1}) 83 pl2 = TestRepo.insert!(%Permalink{url: "2", post_id: nil}) 84 pl3 = TestRepo.insert!(%Permalink{url: "3", post_id: pid3}) 85 assert %Ecto.Association.NotLoaded{} = pl1.post 86 87 [pl3, pl1, pl2] = TestRepo.preload([pl3, pl1, pl2], :post) 88 assert %Post{id: ^pid1} = pl1.post 89 refute pl2.post 90 assert %Post{id: ^pid3} = pl3.post 91 end 92 93 test "preload multiple belongs_to" do 94 %User{id: uid} = TestRepo.insert!(%User{name: "foo"}) 95 %Post{id: pid} = TestRepo.insert!(%Post{title: "1"}) 96 %Comment{id: cid} = TestRepo.insert!(%Comment{post_id: pid, author_id: uid}) 97 98 comment = TestRepo.get!(Comment, cid) 99 comment = TestRepo.preload(comment, [:author, :post]) 100 assert comment.author.id == uid 101 assert comment.post.id == pid 102 end 103 104 test "preload belongs_to with shared parent" do 105 %Post{id: pid1} = TestRepo.insert!(%Post{title: "1"}) 106 %Post{id: pid2} = TestRepo.insert!(%Post{title: "2"}) 107 108 c1 = TestRepo.insert!(%Comment{text: "1", post_id: pid1}) 109 c2 = TestRepo.insert!(%Comment{text: "2", post_id: pid1}) 110 c3 = TestRepo.insert!(%Comment{text: "3", post_id: pid2}) 111 112 [c3, c1, c2] = TestRepo.preload([c3, c1, c2], :post) 113 assert %Post{id: ^pid1} = c1.post 114 assert %Post{id: ^pid1} = c2.post 115 assert %Post{id: ^pid2} = c3.post 116 end 117 118 test "preload many_to_many" do 119 p1 = TestRepo.insert!(%Post{title: "1"}) 120 p2 = TestRepo.insert!(%Post{title: "2"}) 121 p3 = TestRepo.insert!(%Post{title: "3"}) 122 123 # We use the same name to expose bugs in preload sorting 124 %User{id: uid1} = TestRepo.insert!(%User{name: "1"}) 125 %User{id: uid3} = TestRepo.insert!(%User{name: "2"}) 126 %User{id: uid2} = TestRepo.insert!(%User{name: "2"}) 127 %User{id: uid4} = TestRepo.insert!(%User{name: "3"}) 128 129 TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1], 130 [post_id: p1.id, user_id: uid2], 131 [post_id: p2.id, user_id: uid3], 132 [post_id: p2.id, user_id: uid4], 133 [post_id: p3.id, user_id: uid1], 134 [post_id: p3.id, user_id: uid4]] 135 136 assert %Ecto.Association.NotLoaded{} = p1.users 137 138 [p1, p2, p3] = TestRepo.preload([p1, p2, p3], :users) 139 assert [%User{id: ^uid1}, %User{id: ^uid2}] = p1.users |> sort_by_id 140 assert [%User{id: ^uid3}, %User{id: ^uid4}] = p2.users |> sort_by_id 141 assert [%User{id: ^uid1}, %User{id: ^uid4}] = p3.users |> sort_by_id 142 end 143 144 test "preload has_many through" do 145 %Post{id: pid1} = p1 = TestRepo.insert!(%Post{}) 146 %Post{id: pid2} = p2 = TestRepo.insert!(%Post{}) 147 148 %User{id: uid1} = TestRepo.insert!(%User{name: "foo"}) 149 %User{id: uid2} = TestRepo.insert!(%User{name: "bar"}) 150 151 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 152 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 153 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2}) 154 %Comment{} = TestRepo.insert!(%Comment{post_id: pid2, author_id: uid2}) 155 156 [p1, p2] = TestRepo.preload([p1, p2], :comments_authors) 157 158 # Through was preloaded 159 [u1, u2] = p1.comments_authors |> sort_by_id 160 assert u1.id == uid1 161 assert u2.id == uid2 162 163 [u2] = p2.comments_authors 164 assert u2.id == uid2 165 166 # But we also preloaded everything along the way 167 assert [c1, c2, c3] = p1.comments |> sort_by_id 168 assert c1.author.id == uid1 169 assert c2.author.id == uid1 170 assert c3.author.id == uid2 171 172 assert [c4] = p2.comments 173 assert c4.author.id == uid2 174 end 175 176 test "preload has_one through" do 177 %Post{id: pid1} = TestRepo.insert!(%Post{}) 178 %Post{id: pid2} = TestRepo.insert!(%Post{}) 179 180 %Permalink{id: lid1} = TestRepo.insert!(%Permalink{post_id: pid1, url: "1"}) 181 %Permalink{id: lid2} = TestRepo.insert!(%Permalink{post_id: pid2, url: "2"}) 182 183 %Comment{} = c1 = TestRepo.insert!(%Comment{post_id: pid1}) 184 %Comment{} = c2 = TestRepo.insert!(%Comment{post_id: pid1}) 185 %Comment{} = c3 = TestRepo.insert!(%Comment{post_id: pid2}) 186 187 [c1, c2, c3] = TestRepo.preload([c1, c2, c3], :post_permalink) 188 189 # Through was preloaded 190 assert c1.post.id == pid1 191 assert c1.post.permalink.id == lid1 192 assert c1.post_permalink.id == lid1 193 194 assert c2.post.id == pid1 195 assert c2.post.permalink.id == lid1 196 assert c2.post_permalink.id == lid1 197 198 assert c3.post.id == pid2 199 assert c3.post.permalink.id == lid2 200 assert c3.post_permalink.id == lid2 201 end 202 203 test "preload through with nil association" do 204 %Comment{} = c = TestRepo.insert!(%Comment{post_id: nil}) 205 206 c = TestRepo.preload(c, [:post, :post_permalink]) 207 assert c.post == nil 208 assert c.post_permalink == nil 209 210 c = TestRepo.preload(c, [:post, :post_permalink]) 211 assert c.post == nil 212 assert c.post_permalink == nil 213 end 214 215 test "preload has_many through-through" do 216 %Post{id: pid1} = TestRepo.insert!(%Post{}) 217 %Post{id: pid2} = TestRepo.insert!(%Post{}) 218 219 %Permalink{} = l1 = TestRepo.insert!(%Permalink{post_id: pid1, url: "1"}) 220 %Permalink{} = l2 = TestRepo.insert!(%Permalink{post_id: pid2, url: "2"}) 221 222 %User{id: uid1} = TestRepo.insert!(%User{name: "foo"}) 223 %User{id: uid2} = TestRepo.insert!(%User{name: "bar"}) 224 225 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 226 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 227 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2}) 228 %Comment{} = TestRepo.insert!(%Comment{post_id: pid2, author_id: uid2}) 229 230 # With assoc query 231 [l1, l2] = TestRepo.preload([l1, l2], :post_comments_authors) 232 233 # Through was preloaded 234 [u1, u2] = l1.post_comments_authors |> sort_by_id 235 assert u1.id == uid1 236 assert u2.id == uid2 237 238 [u2] = l2.post_comments_authors 239 assert u2.id == uid2 240 241 # But we also preloaded everything along the way 242 assert l1.post.id == pid1 243 assert l1.post.comments != [] 244 245 assert l2.post.id == pid2 246 assert l2.post.comments != [] 247 end 248 249 test "preload has_many through many_to_many" do 250 %Post{} = p1 = TestRepo.insert!(%Post{}) 251 %Post{} = p2 = TestRepo.insert!(%Post{}) 252 253 %User{id: uid1} = TestRepo.insert!(%User{name: "foo"}) 254 %User{id: uid2} = TestRepo.insert!(%User{name: "bar"}) 255 256 TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1], 257 [post_id: p1.id, user_id: uid2], 258 [post_id: p2.id, user_id: uid2]] 259 260 %Comment{id: cid1} = TestRepo.insert!(%Comment{author_id: uid1}) 261 %Comment{id: cid2} = TestRepo.insert!(%Comment{author_id: uid1}) 262 %Comment{id: cid3} = TestRepo.insert!(%Comment{author_id: uid2}) 263 %Comment{id: cid4} = TestRepo.insert!(%Comment{author_id: uid2}) 264 265 [p1, p2] = TestRepo.preload([p1, p2], :users_comments) 266 267 # Through was preloaded 268 [c1, c2, c3, c4] = p1.users_comments |> sort_by_id 269 assert c1.id == cid1 270 assert c2.id == cid2 271 assert c3.id == cid3 272 assert c4.id == cid4 273 274 [c3, c4] = p2.users_comments |> sort_by_id 275 assert c3.id == cid3 276 assert c4.id == cid4 277 278 # But we also preloaded everything along the way 279 assert [u1, u2] = p1.users |> sort_by_id 280 assert u1.id == uid1 281 assert u2.id == uid2 282 283 assert [u2] = p2.users 284 assert u2.id == uid2 285 end 286 287 ## Empties 288 289 test "preload empty" do 290 assert TestRepo.preload([], :anything_goes) == [] 291 end 292 293 test "preload has_many with no associated entries" do 294 p = TestRepo.insert!(%Post{title: "1"}) 295 p = TestRepo.preload(p, :comments) 296 297 assert p.title == "1" 298 assert p.comments == [] 299 end 300 301 test "preload has_one with no associated entries" do 302 p = TestRepo.insert!(%Post{title: "1"}) 303 p = TestRepo.preload(p, :permalink) 304 305 assert p.title == "1" 306 assert p.permalink == nil 307 end 308 309 test "preload belongs_to with no associated entry" do 310 c = TestRepo.insert!(%Comment{text: "1"}) 311 c = TestRepo.preload(c, :post) 312 313 assert c.text == "1" 314 assert c.post == nil 315 end 316 317 test "preload many_to_many with no associated entries" do 318 p = TestRepo.insert!(%Post{title: "1"}) 319 p = TestRepo.preload(p, :users) 320 321 assert p.title == "1" 322 assert p.users == [] 323 end 324 325 ## With queries 326 327 test "preload with function" do 328 p1 = TestRepo.insert!(%Post{title: "1"}) 329 p2 = TestRepo.insert!(%Post{title: "2"}) 330 p3 = TestRepo.insert!(%Post{title: "3"}) 331 332 # We use the same text to expose bugs in preload sorting 333 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 334 %Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id}) 335 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 336 %Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 337 338 assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2], 339 comments: fn _ -> TestRepo.all(Comment) end) 340 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = pe1.comments 341 assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = pe2.comments 342 assert [] = pe3.comments 343 end 344 345 test "preload many_to_many with function" do 346 p1 = TestRepo.insert!(%Post{title: "1"}) 347 p2 = TestRepo.insert!(%Post{title: "2"}) 348 p3 = TestRepo.insert!(%Post{title: "3"}) 349 350 # We use the same name to expose bugs in preload sorting 351 %User{id: uid1} = TestRepo.insert!(%User{name: "1"}) 352 %User{id: uid3} = TestRepo.insert!(%User{name: "2"}) 353 %User{id: uid2} = TestRepo.insert!(%User{name: "2"}) 354 %User{id: uid4} = TestRepo.insert!(%User{name: "3"}) 355 356 TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1], 357 [post_id: p1.id, user_id: uid2], 358 [post_id: p2.id, user_id: uid3], 359 [post_id: p2.id, user_id: uid4], 360 [post_id: p3.id, user_id: uid1], 361 [post_id: p3.id, user_id: uid4]] 362 363 wrong_preloader = fn post_ids -> 364 TestRepo.all( 365 from u in User, 366 join: pu in "posts_users", 367 where: pu.post_id in ^post_ids and pu.user_id == u.id, 368 order_by: u.id, 369 select: map(u, [:id]) 370 ) 371 end 372 373 assert_raise RuntimeError, ~r/invalid custom preload for `users` on `Ecto.Integration.Post`/, fn -> 374 TestRepo.preload([p1, p2, p3], users: wrong_preloader) 375 end 376 377 right_preloader = fn post_ids -> 378 TestRepo.all( 379 from u in User, 380 join: pu in "posts_users", 381 where: pu.post_id in ^post_ids and pu.user_id == u.id, 382 order_by: u.id, 383 select: {pu.post_id, map(u, [:id])} 384 ) 385 end 386 387 [p1, p2, p3] = TestRepo.preload([p1, p2, p3], users: right_preloader) 388 assert p1.users == [%{id: uid1}, %{id: uid2}] 389 assert p2.users == [%{id: uid3}, %{id: uid4}] 390 assert p3.users == [%{id: uid1}, %{id: uid4}] 391 end 392 393 test "preload with query" do 394 p1 = TestRepo.insert!(%Post{title: "1"}) 395 p2 = TestRepo.insert!(%Post{title: "2"}) 396 p3 = TestRepo.insert!(%Post{title: "3"}) 397 398 # We use the same text to expose bugs in preload sorting 399 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 400 %Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id}) 401 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 402 %Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 403 404 assert %Ecto.Association.NotLoaded{} = p1.comments 405 406 # With empty query 407 assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2], 408 comments: from(c in Comment, where: false)) 409 assert [] = pe1.comments 410 assert [] = pe2.comments 411 assert [] = pe3.comments 412 413 # With custom select 414 assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2], 415 comments: from(c in Comment, select: c.id, order_by: c.id)) 416 assert [^cid1, ^cid2] = pe1.comments 417 assert [^cid3, ^cid4] = pe2.comments 418 assert [] = pe3.comments 419 420 # With custom ordered query 421 assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2], 422 comments: from(c in Comment, order_by: [desc: c.text])) 423 assert [%Comment{id: ^cid2}, %Comment{id: ^cid1}] = pe1.comments 424 assert [%Comment{id: ^cid4}, %Comment{id: ^cid3}] = pe2.comments 425 assert [] = pe3.comments 426 427 # With custom ordered query with preload 428 assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2], 429 comments: {from(c in Comment, order_by: [desc: c.text]), :post}) 430 assert [%Comment{id: ^cid2} = c2, %Comment{id: ^cid1} = c1] = pe1.comments 431 assert [%Comment{id: ^cid4} = c4, %Comment{id: ^cid3} = c3] = pe2.comments 432 assert [] = pe3.comments 433 434 assert c1.post.title == "1" 435 assert c2.post.title == "1" 436 assert c3.post.title == "2" 437 assert c4.post.title == "2" 438 end 439 440 test "preload through with query" do 441 %Post{id: pid1} = p1 = TestRepo.insert!(%Post{}) 442 443 u1 = TestRepo.insert!(%User{name: "foo"}) 444 u2 = TestRepo.insert!(%User{name: "bar"}) 445 u3 = TestRepo.insert!(%User{name: "baz"}) 446 u4 = TestRepo.insert!(%User{name: "norf"}) 447 448 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u1.id}) 449 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u1.id}) 450 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u2.id}) 451 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u3.id}) 452 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u4.id}) 453 454 np1 = TestRepo.preload(p1, comments_authors: from(u in User, where: u.name == "foo")) 455 assert np1.comments_authors == [u1] 456 457 assert_raise ArgumentError, ~r/Ecto expected a map\/struct with the key `id` but got: \d+/, fn -> 458 TestRepo.preload(p1, comments_authors: from(u in User, order_by: u.name, select: u.id)) 459 end 460 461 # The subpreload order does not matter because the result is dictated by comments 462 np1 = TestRepo.preload(p1, comments_authors: from(u in User, order_by: u.name, select: %{id: u.id})) 463 assert np1.comments_authors == 464 [%{id: u1.id}, %{id: u2.id}, %{id: u3.id}, %{id: u4.id}] 465 end 466 467 ## With take 468 469 test "preload with take" do 470 p1 = TestRepo.insert!(%Post{title: "1"}) 471 p2 = TestRepo.insert!(%Post{title: "2"}) 472 _p = TestRepo.insert!(%Post{title: "3"}) 473 474 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 475 %Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id}) 476 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 477 %Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 478 479 assert %Ecto.Association.NotLoaded{} = p1.comments 480 481 posts = TestRepo.all(from Post, preload: [:comments], select: [:id, comments: [:id, :post_id]]) 482 [p1, p2, p3] = sort_by_id(posts) 483 assert p1.title == nil 484 assert p2.title == nil 485 assert p3.title == nil 486 487 assert [%{id: ^cid1, text: nil}, %{id: ^cid2, text: nil}] = sort_by_id(p1.comments) 488 assert [%{id: ^cid3, text: nil}, %{id: ^cid4, text: nil}] = sort_by_id(p2.comments) 489 assert [] = sort_by_id(p3.comments) 490 end 491 492 test "preload through with take" do 493 %Post{id: pid1} = TestRepo.insert!(%Post{}) 494 495 %User{id: uid1} = TestRepo.insert!(%User{name: "foo"}) 496 %User{id: uid2} = TestRepo.insert!(%User{name: "bar"}) 497 498 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 499 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1}) 500 %Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2}) 501 502 [p1] = TestRepo.all from Post, preload: [:comments_authors], select: [:id, comments_authors: :id] 503 [%{id: ^uid1, name: nil}, %{id: ^uid2, name: nil}] = p1.comments_authors |> sort_by_id 504 end 505 506 ## Nested 507 508 test "preload many assocs" do 509 p1 = TestRepo.insert!(%Post{title: "1"}) 510 p2 = TestRepo.insert!(%Post{title: "2"}) 511 512 assert [p2, p1] = TestRepo.preload([p2, p1], [:comments, :users]) 513 assert p1.comments == [] 514 assert p2.comments == [] 515 assert p1.users == [] 516 assert p2.users == [] 517 end 518 519 test "preload nested" do 520 p1 = TestRepo.insert!(%Post{title: "1"}) 521 p2 = TestRepo.insert!(%Post{title: "2"}) 522 523 TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 524 TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 525 TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 526 TestRepo.insert!(%Comment{text: "4", post_id: p2.id}) 527 528 assert [p2, p1] = TestRepo.preload([p2, p1], [comments: :post]) 529 assert [c1, c2] = p1.comments 530 assert [c3, c4] = p2.comments 531 assert p1.id == c1.post.id 532 assert p1.id == c2.post.id 533 assert p2.id == c3.post.id 534 assert p2.id == c4.post.id 535 end 536 537 test "preload nested via custom query" do 538 p1 = TestRepo.insert!(%Post{title: "1"}) 539 p2 = TestRepo.insert!(%Post{title: "2"}) 540 541 TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 542 TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 543 TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 544 TestRepo.insert!(%Comment{text: "4", post_id: p2.id}) 545 546 query = from(c in Comment, preload: :post, order_by: [desc: c.text]) 547 assert [p2, p1] = TestRepo.preload([p2, p1], comments: query) 548 assert [c2, c1] = p1.comments 549 assert [c4, c3] = p2.comments 550 assert p1.id == c1.post.id 551 assert p1.id == c2.post.id 552 assert p2.id == c3.post.id 553 assert p2.id == c4.post.id 554 end 555 556 test "custom preload_order" do 557 post = TestRepo.insert!(%Post{users: [%User{name: "bar"}, %User{name: "foo"}], title: "1"}) 558 559 TestRepo.insert!(%Comment{text: "2", post_id: post.id}) 560 TestRepo.insert!(%Comment{text: "1", post_id: post.id}) 561 562 post = TestRepo.preload(post, [:ordered_comments, :ordered_users]) 563 564 # asc 565 assert [%{text: "1"}, %{text: "2"}] = post.ordered_comments 566 567 # desc 568 assert [%{name: "foo"}, %{name: "bar"}] = post.ordered_users 569 end 570 571 ## Others 572 573 @tag :invalid_prefix 574 test "preload custom prefix from schema" do 575 p = TestRepo.insert!(%Post{title: "1"}) 576 p = Ecto.put_meta(p, prefix: "this_surely_does_not_exist") 577 # This preload should fail because it points to a prefix that does not exist 578 assert catch_error(TestRepo.preload(p, [:comments])) 579 end 580 581 @tag :invalid_prefix 582 test "preload custom prefix from options" do 583 p = TestRepo.insert!(%Post{title: "1"}) 584 # This preload should fail because it points to a prefix that does not exist 585 assert catch_error(TestRepo.preload(p, [:comments], prefix: "this_surely_does_not_exist")) 586 end 587 588 test "preload with binary_id" do 589 c = TestRepo.insert!(%Custom{}) 590 u = TestRepo.insert!(%User{custom_id: c.bid}) 591 592 u = TestRepo.preload(u, :custom) 593 assert u.custom.bid == c.bid 594 end 595 596 test "preload raises with association set but without id" do 597 c1 = TestRepo.insert!(%Comment{text: "1"}) 598 u1 = TestRepo.insert!(%User{name: "name"}) 599 updated = %{c1 | author: u1, author_id: nil} 600 601 assert ExUnit.CaptureLog.capture_log(fn -> 602 assert TestRepo.preload(updated, [:author]).author == u1 603 end) =~ ~r/its association key `author_id` is nil/ 604 605 assert TestRepo.preload(updated, [:author], force: true).author == nil 606 end 607 608 test "preload skips already loaded for cardinality one" do 609 %Post{id: pid} = TestRepo.insert!(%Post{title: "1"}) 610 611 c1 = %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: pid}) 612 c2 = %Comment{id: _cid} = TestRepo.insert!(%Comment{text: "2", post_id: nil}) 613 614 [c1, c2] = TestRepo.preload([c1, c2], :post) 615 assert %Post{id: ^pid} = c1.post 616 assert c2.post == nil 617 618 [c1, c2] = TestRepo.preload([c1, c2], post: :comments) 619 assert [%Comment{id: ^cid1}] = c1.post.comments 620 621 TestRepo.update_all Post, set: [title: "0"] 622 TestRepo.update_all Comment, set: [post_id: pid] 623 624 # Preloading once again shouldn't change the result 625 [c1, c2] = TestRepo.preload([c1, c2], :post) 626 assert %Post{id: ^pid, title: "1", comments: [_|_]} = c1.post 627 assert c2.post == nil 628 629 [c1, c2] = TestRepo.preload([c1, %{c2 | post_id: pid}], :post, force: true) 630 assert %Post{id: ^pid, title: "0", comments: %Ecto.Association.NotLoaded{}} = c1.post 631 assert %Post{id: ^pid, title: "0", comments: %Ecto.Association.NotLoaded{}} = c2.post 632 end 633 634 test "preload skips already loaded for cardinality many" do 635 p1 = TestRepo.insert!(%Post{title: "1"}) 636 p2 = TestRepo.insert!(%Post{title: "2"}) 637 638 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 639 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id}) 640 641 [p1, p2] = TestRepo.preload([p1, p2], :comments) 642 assert [%Comment{id: ^cid1}] = p1.comments 643 assert [%Comment{id: ^cid2}] = p2.comments 644 645 [p1, p2] = TestRepo.preload([p1, p2], comments: :post) 646 assert hd(p1.comments).post.id == p1.id 647 assert hd(p2.comments).post.id == p2.id 648 649 TestRepo.update_all Comment, set: [text: "0"] 650 651 # Preloading once again shouldn't change the result 652 [p1, p2] = TestRepo.preload([p1, p2], :comments) 653 assert [%Comment{id: ^cid1, text: "1", post: %Post{}}] = p1.comments 654 assert [%Comment{id: ^cid2, text: "2", post: %Post{}}] = p2.comments 655 656 [p1, p2] = TestRepo.preload([p1, p2], :comments, force: true) 657 assert [%Comment{id: ^cid1, text: "0", post: %Ecto.Association.NotLoaded{}}] = p1.comments 658 assert [%Comment{id: ^cid2, text: "0", post: %Ecto.Association.NotLoaded{}}] = p2.comments 659 end 660 661 test "preload keyword query" do 662 p1 = TestRepo.insert!(%Post{title: "1"}) 663 p2 = TestRepo.insert!(%Post{title: "2"}) 664 TestRepo.insert!(%Post{title: "3"}) 665 666 %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id}) 667 %Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id}) 668 %Comment{id: cid3} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id}) 669 %Comment{id: cid4} = TestRepo.insert!(%Comment{text: "4", post_id: p2.id}) 670 671 # Regular query 672 query = from(p in Post, preload: [:comments], select: p) 673 674 assert [p1, p2, p3] = TestRepo.all(query) |> sort_by_id 675 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id 676 assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id 677 assert [] = p3.comments 678 679 # Query with interpolated preload query 680 query = from(p in Post, preload: [comments: ^from(c in Comment, where: false)], select: p) 681 682 assert [p1, p2, p3] = TestRepo.all(query) 683 assert [] = p1.comments 684 assert [] = p2.comments 685 assert [] = p3.comments 686 687 # Now let's use an interpolated preload too 688 comments = [:comments] 689 query = from(p in Post, preload: ^comments, select: {0, [p], 1, 2}) 690 691 posts = TestRepo.all(query) 692 [p1, p2, p3] = Enum.map(posts, fn {0, [p], 1, 2} -> p end) |> sort_by_id 693 694 assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id 695 assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id 696 assert [] = p3.comments 697 end 698 699 700 test "preload belongs_to in embedded_schema" do 701 %User{id: uid1} = TestRepo.insert!(%User{name: "1"}) 702 item = %Item{user_id: uid1} 703 704 # Starts as not loaded 705 assert %Ecto.Association.NotLoaded{} = item.user 706 707 # Now we preload it 708 item = TestRepo.preload(item, :user) 709 assert %User{id: ^uid1} = item.user 710 end 711 712 describe "preload associations from nested embeds" do 713 setup do 714 %User{id: uid1} = TestRepo.insert!(%User{name: "1"}) 715 %User{id: uid2} = TestRepo.insert!(%User{name: "2"}) 716 %User{id: uid3} = TestRepo.insert!(%User{name: "3"}) 717 item1 = %Item{id: 1, user_id: uid1} 718 item2 = %Item{id: 2, user_id: uid2} 719 item3 = %Item{id: 3, user_id: uid3} 720 order1 = %Order{items: [item1, item3, item2], item: item1} 721 order2 = %Order{items: [], item: nil} 722 order3 = %Order{items: nil, item: nil} 723 order4 = %Order{items: [item1, item2], item: item2} 724 725 [orders: [order1, order2, order3, order4]] 726 end 727 728 test "cannot preload embed without its associations", context do 729 assert_raise ArgumentError, ~r/cannot preload embedded field/, fn -> 730 TestRepo.preload(context.orders, :item) 731 end 732 end 733 734 test "embeds_one", context do 735 [nil | preloaded_orders] = [nil | context.orders] |> TestRepo.preload(item: :user) 736 737 expected_item_user = 738 Enum.map(context.orders, fn 739 %{item: nil} -> {nil, nil} 740 %{item: item} -> {item.id, item.user_id} 741 end) 742 743 actual_item_user = 744 Enum.map(preloaded_orders, fn 745 %{item: nil} -> {nil, nil} 746 %{item: item} -> {item.id, item.user.id} 747 end) 748 749 assert expected_item_user == actual_item_user 750 end 751 752 test "embeds_many", context do 753 [nil | preloaded_orders] = [nil | context.orders] |> TestRepo.preload(items: :user) 754 755 expected_items_user = 756 Enum.map(context.orders, fn 757 %{items: nil} -> {nil, nil} 758 %{items: items} -> Enum.map(items, & {&1.id, &1.user_id}) 759 end) 760 761 actual_items_user = 762 Enum.map(preloaded_orders, fn 763 %{items: nil} -> {nil, nil} 764 %{items: items} -> Enum.map(items, & {&1.id, &1.user.id}) 765 end) 766 767 assert expected_items_user == actual_items_user 768 end 769 end 770 771 defp sort_by_id(values) do 772 Enum.sort_by(values, &(&1.id)) 773 end 774 end