792 lines
29 KiB
Elixir
792 lines
29 KiB
Elixir
|
defmodule Ecto.Integration.PreloadTest do
|
||
|
use Ecto.Integration.Case, async: Application.compile_env(:ecto, :async_integration_tests, true)
|
||
|
|
||
|
alias Ecto.Integration.TestRepo
|
||
|
import Ecto.Query
|
||
|
|
||
|
alias Ecto.Integration.Post
|
||
|
alias Ecto.Integration.Comment
|
||
|
alias Ecto.Integration.Item
|
||
|
alias Ecto.Integration.Permalink
|
||
|
alias Ecto.Integration.User
|
||
|
alias Ecto.Integration.Custom
|
||
|
alias Ecto.Integration.Order
|
||
|
|
||
|
test "preload with parameter from select_merge" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "p1"})
|
||
|
TestRepo.insert!(%Comment{text: "c1", post: p1})
|
||
|
|
||
|
comments =
|
||
|
from(c in Comment, select: struct(c, [:text]))
|
||
|
|> select_merge([c], %{post_id: c.post_id})
|
||
|
|> preload(:post)
|
||
|
|> TestRepo.all()
|
||
|
|
||
|
assert [%{text: "c1", post: %{title: "p1"}}] = comments
|
||
|
end
|
||
|
|
||
|
test "preload has_many" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
# We use the same text to expose bugs in preload sorting
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
|
||
|
assert %Ecto.Association.NotLoaded{} = p1.comments
|
||
|
|
||
|
[p3, p1, p2] = TestRepo.preload([p3, p1, p2], :comments)
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id()
|
||
|
assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id()
|
||
|
assert [] = p3.comments
|
||
|
end
|
||
|
|
||
|
test "preload has_many multiple times" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
|
||
|
[p1, p1] = TestRepo.preload([p1, p1], :comments)
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id()
|
||
|
|
||
|
[p1, p1] = TestRepo.preload([p1, p1], :comments)
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id()
|
||
|
end
|
||
|
|
||
|
test "preload has_one" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
%Permalink{id: pid1} = TestRepo.insert!(%Permalink{url: "1", post_id: p1.id})
|
||
|
%Permalink{} = TestRepo.insert!(%Permalink{url: "2", post_id: nil})
|
||
|
%Permalink{id: pid3} = TestRepo.insert!(%Permalink{url: "3", post_id: p3.id})
|
||
|
|
||
|
assert %Ecto.Association.NotLoaded{} = p1.permalink
|
||
|
assert %Ecto.Association.NotLoaded{} = p2.permalink
|
||
|
|
||
|
[p3, p1, p2] = TestRepo.preload([p3, p1, p2], :permalink)
|
||
|
assert %Permalink{id: ^pid1} = p1.permalink
|
||
|
refute p2.permalink
|
||
|
assert %Permalink{id: ^pid3} = p3.permalink
|
||
|
end
|
||
|
|
||
|
test "preload belongs_to" do
|
||
|
%Post{id: pid1} = TestRepo.insert!(%Post{title: "1"})
|
||
|
TestRepo.insert!(%Post{title: "2"})
|
||
|
%Post{id: pid3} = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
pl1 = TestRepo.insert!(%Permalink{url: "1", post_id: pid1})
|
||
|
pl2 = TestRepo.insert!(%Permalink{url: "2", post_id: nil})
|
||
|
pl3 = TestRepo.insert!(%Permalink{url: "3", post_id: pid3})
|
||
|
assert %Ecto.Association.NotLoaded{} = pl1.post
|
||
|
|
||
|
[pl3, pl1, pl2] = TestRepo.preload([pl3, pl1, pl2], :post)
|
||
|
assert %Post{id: ^pid1} = pl1.post
|
||
|
refute pl2.post
|
||
|
assert %Post{id: ^pid3} = pl3.post
|
||
|
end
|
||
|
|
||
|
test "preload multiple belongs_to" do
|
||
|
%User{id: uid} = TestRepo.insert!(%User{name: "foo"})
|
||
|
%Post{id: pid} = TestRepo.insert!(%Post{title: "1"})
|
||
|
%Comment{id: cid} = TestRepo.insert!(%Comment{post_id: pid, author_id: uid})
|
||
|
|
||
|
comment = TestRepo.get!(Comment, cid)
|
||
|
comment = TestRepo.preload(comment, [:author, :post])
|
||
|
assert comment.author.id == uid
|
||
|
assert comment.post.id == pid
|
||
|
end
|
||
|
|
||
|
test "preload belongs_to with shared parent" do
|
||
|
%Post{id: pid1} = TestRepo.insert!(%Post{title: "1"})
|
||
|
%Post{id: pid2} = TestRepo.insert!(%Post{title: "2"})
|
||
|
|
||
|
c1 = TestRepo.insert!(%Comment{text: "1", post_id: pid1})
|
||
|
c2 = TestRepo.insert!(%Comment{text: "2", post_id: pid1})
|
||
|
c3 = TestRepo.insert!(%Comment{text: "3", post_id: pid2})
|
||
|
|
||
|
[c3, c1, c2] = TestRepo.preload([c3, c1, c2], :post)
|
||
|
assert %Post{id: ^pid1} = c1.post
|
||
|
assert %Post{id: ^pid1} = c2.post
|
||
|
assert %Post{id: ^pid2} = c3.post
|
||
|
end
|
||
|
|
||
|
test "preload many_to_many" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
# We use the same name to expose bugs in preload sorting
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "1"})
|
||
|
%User{id: uid3} = TestRepo.insert!(%User{name: "2"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "2"})
|
||
|
%User{id: uid4} = TestRepo.insert!(%User{name: "3"})
|
||
|
|
||
|
TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1],
|
||
|
[post_id: p1.id, user_id: uid2],
|
||
|
[post_id: p2.id, user_id: uid3],
|
||
|
[post_id: p2.id, user_id: uid4],
|
||
|
[post_id: p3.id, user_id: uid1],
|
||
|
[post_id: p3.id, user_id: uid4]]
|
||
|
|
||
|
assert %Ecto.Association.NotLoaded{} = p1.users
|
||
|
|
||
|
[p1, p2, p3] = TestRepo.preload([p1, p2, p3], :users)
|
||
|
assert [%User{id: ^uid1}, %User{id: ^uid2}] = p1.users |> sort_by_id
|
||
|
assert [%User{id: ^uid3}, %User{id: ^uid4}] = p2.users |> sort_by_id
|
||
|
assert [%User{id: ^uid1}, %User{id: ^uid4}] = p3.users |> sort_by_id
|
||
|
end
|
||
|
|
||
|
test "preload has_many through" do
|
||
|
%Post{id: pid1} = p1 = TestRepo.insert!(%Post{})
|
||
|
%Post{id: pid2} = p2 = TestRepo.insert!(%Post{})
|
||
|
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "foo"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "bar"})
|
||
|
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid2, author_id: uid2})
|
||
|
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], :comments_authors)
|
||
|
|
||
|
# Through was preloaded
|
||
|
[u1, u2] = p1.comments_authors |> sort_by_id
|
||
|
assert u1.id == uid1
|
||
|
assert u2.id == uid2
|
||
|
|
||
|
[u2] = p2.comments_authors
|
||
|
assert u2.id == uid2
|
||
|
|
||
|
# But we also preloaded everything along the way
|
||
|
assert [c1, c2, c3] = p1.comments |> sort_by_id
|
||
|
assert c1.author.id == uid1
|
||
|
assert c2.author.id == uid1
|
||
|
assert c3.author.id == uid2
|
||
|
|
||
|
assert [c4] = p2.comments
|
||
|
assert c4.author.id == uid2
|
||
|
end
|
||
|
|
||
|
test "preload has_one through" do
|
||
|
%Post{id: pid1} = TestRepo.insert!(%Post{})
|
||
|
%Post{id: pid2} = TestRepo.insert!(%Post{})
|
||
|
|
||
|
%Permalink{id: lid1} = TestRepo.insert!(%Permalink{post_id: pid1, url: "1"})
|
||
|
%Permalink{id: lid2} = TestRepo.insert!(%Permalink{post_id: pid2, url: "2"})
|
||
|
|
||
|
%Comment{} = c1 = TestRepo.insert!(%Comment{post_id: pid1})
|
||
|
%Comment{} = c2 = TestRepo.insert!(%Comment{post_id: pid1})
|
||
|
%Comment{} = c3 = TestRepo.insert!(%Comment{post_id: pid2})
|
||
|
|
||
|
[c1, c2, c3] = TestRepo.preload([c1, c2, c3], :post_permalink)
|
||
|
|
||
|
# Through was preloaded
|
||
|
assert c1.post.id == pid1
|
||
|
assert c1.post.permalink.id == lid1
|
||
|
assert c1.post_permalink.id == lid1
|
||
|
|
||
|
assert c2.post.id == pid1
|
||
|
assert c2.post.permalink.id == lid1
|
||
|
assert c2.post_permalink.id == lid1
|
||
|
|
||
|
assert c3.post.id == pid2
|
||
|
assert c3.post.permalink.id == lid2
|
||
|
assert c3.post_permalink.id == lid2
|
||
|
end
|
||
|
|
||
|
test "preload through with nil association" do
|
||
|
%Comment{} = c = TestRepo.insert!(%Comment{post_id: nil})
|
||
|
|
||
|
c = TestRepo.preload(c, [:post, :post_permalink])
|
||
|
assert c.post == nil
|
||
|
assert c.post_permalink == nil
|
||
|
|
||
|
c = TestRepo.preload(c, [:post, :post_permalink])
|
||
|
assert c.post == nil
|
||
|
assert c.post_permalink == nil
|
||
|
end
|
||
|
|
||
|
test "preload through with nil struct" do
|
||
|
%Comment{} = c = TestRepo.insert!(%Comment{})
|
||
|
[%Comment{}, nil] = TestRepo.preload([c, nil], [:post, :post_permalink])
|
||
|
end
|
||
|
|
||
|
test "preload has_many through-through" do
|
||
|
%Post{id: pid1} = TestRepo.insert!(%Post{})
|
||
|
%Post{id: pid2} = TestRepo.insert!(%Post{})
|
||
|
|
||
|
%Permalink{} = l1 = TestRepo.insert!(%Permalink{post_id: pid1, url: "1"})
|
||
|
%Permalink{} = l2 = TestRepo.insert!(%Permalink{post_id: pid2, url: "2"})
|
||
|
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "foo"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "bar"})
|
||
|
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid2, author_id: uid2})
|
||
|
|
||
|
# With assoc query
|
||
|
[l1, l2] = TestRepo.preload([l1, l2], :post_comments_authors)
|
||
|
|
||
|
# Through was preloaded
|
||
|
[u1, u2] = l1.post_comments_authors |> sort_by_id
|
||
|
assert u1.id == uid1
|
||
|
assert u2.id == uid2
|
||
|
|
||
|
[u2] = l2.post_comments_authors
|
||
|
assert u2.id == uid2
|
||
|
|
||
|
# But we also preloaded everything along the way
|
||
|
assert l1.post.id == pid1
|
||
|
assert l1.post.comments != []
|
||
|
|
||
|
assert l2.post.id == pid2
|
||
|
assert l2.post.comments != []
|
||
|
end
|
||
|
|
||
|
test "preload has_many through many_to_many" do
|
||
|
%Post{} = p1 = TestRepo.insert!(%Post{})
|
||
|
%Post{} = p2 = TestRepo.insert!(%Post{})
|
||
|
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "foo"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "bar"})
|
||
|
|
||
|
TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1],
|
||
|
[post_id: p1.id, user_id: uid2],
|
||
|
[post_id: p2.id, user_id: uid2]]
|
||
|
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{author_id: uid1})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{author_id: uid1})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{author_id: uid2})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{author_id: uid2})
|
||
|
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], :users_comments)
|
||
|
|
||
|
# Through was preloaded
|
||
|
[c1, c2, c3, c4] = p1.users_comments |> sort_by_id
|
||
|
assert c1.id == cid1
|
||
|
assert c2.id == cid2
|
||
|
assert c3.id == cid3
|
||
|
assert c4.id == cid4
|
||
|
|
||
|
[c3, c4] = p2.users_comments |> sort_by_id
|
||
|
assert c3.id == cid3
|
||
|
assert c4.id == cid4
|
||
|
|
||
|
# But we also preloaded everything along the way
|
||
|
assert [u1, u2] = p1.users |> sort_by_id
|
||
|
assert u1.id == uid1
|
||
|
assert u2.id == uid2
|
||
|
|
||
|
assert [u2] = p2.users
|
||
|
assert u2.id == uid2
|
||
|
end
|
||
|
|
||
|
## Empties
|
||
|
|
||
|
test "preload empty" do
|
||
|
assert TestRepo.preload([], :anything_goes) == []
|
||
|
end
|
||
|
|
||
|
test "preload has_many with no associated entries" do
|
||
|
p = TestRepo.insert!(%Post{title: "1"})
|
||
|
p = TestRepo.preload(p, :comments)
|
||
|
|
||
|
assert p.title == "1"
|
||
|
assert p.comments == []
|
||
|
end
|
||
|
|
||
|
test "preload has_one with no associated entries" do
|
||
|
p = TestRepo.insert!(%Post{title: "1"})
|
||
|
p = TestRepo.preload(p, :permalink)
|
||
|
|
||
|
assert p.title == "1"
|
||
|
assert p.permalink == nil
|
||
|
end
|
||
|
|
||
|
test "preload belongs_to with no associated entry" do
|
||
|
c = TestRepo.insert!(%Comment{text: "1"})
|
||
|
c = TestRepo.preload(c, :post)
|
||
|
|
||
|
assert c.text == "1"
|
||
|
assert c.post == nil
|
||
|
end
|
||
|
|
||
|
test "preload many_to_many with no associated entries" do
|
||
|
p = TestRepo.insert!(%Post{title: "1"})
|
||
|
p = TestRepo.preload(p, :users)
|
||
|
|
||
|
assert p.title == "1"
|
||
|
assert p.users == []
|
||
|
end
|
||
|
|
||
|
## With queries
|
||
|
|
||
|
test "preload with function" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
# We use the same text to expose bugs in preload sorting
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
|
||
|
assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2],
|
||
|
comments: fn _ -> TestRepo.all(Comment) end)
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = pe1.comments
|
||
|
assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = pe2.comments
|
||
|
assert [] = pe3.comments
|
||
|
end
|
||
|
|
||
|
test "preload many_to_many with function" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
# We use the same name to expose bugs in preload sorting
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "1"})
|
||
|
%User{id: uid3} = TestRepo.insert!(%User{name: "2"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "2"})
|
||
|
%User{id: uid4} = TestRepo.insert!(%User{name: "3"})
|
||
|
|
||
|
TestRepo.insert_all "posts_users", [[post_id: p1.id, user_id: uid1],
|
||
|
[post_id: p1.id, user_id: uid2],
|
||
|
[post_id: p2.id, user_id: uid3],
|
||
|
[post_id: p2.id, user_id: uid4],
|
||
|
[post_id: p3.id, user_id: uid1],
|
||
|
[post_id: p3.id, user_id: uid4]]
|
||
|
|
||
|
wrong_preloader = fn post_ids ->
|
||
|
TestRepo.all(
|
||
|
from u in User,
|
||
|
join: pu in "posts_users",
|
||
|
on: true,
|
||
|
where: pu.post_id in ^post_ids and pu.user_id == u.id,
|
||
|
order_by: u.id,
|
||
|
select: map(u, [:id])
|
||
|
)
|
||
|
end
|
||
|
|
||
|
assert_raise RuntimeError, ~r/invalid custom preload for `users` on `Ecto.Integration.Post`/, fn ->
|
||
|
TestRepo.preload([p1, p2, p3], users: wrong_preloader)
|
||
|
end
|
||
|
|
||
|
right_preloader = fn post_ids ->
|
||
|
TestRepo.all(
|
||
|
from u in User,
|
||
|
join: pu in "posts_users",
|
||
|
on: true,
|
||
|
where: pu.post_id in ^post_ids and pu.user_id == u.id,
|
||
|
order_by: u.id,
|
||
|
select: {pu.post_id, map(u, [:id])}
|
||
|
)
|
||
|
end
|
||
|
|
||
|
[p1, p2, p3] = TestRepo.preload([p1, p2, p3], users: right_preloader)
|
||
|
assert p1.users == [%{id: uid1}, %{id: uid2}]
|
||
|
assert p2.users == [%{id: uid3}, %{id: uid4}]
|
||
|
assert p3.users == [%{id: uid1}, %{id: uid4}]
|
||
|
end
|
||
|
|
||
|
test "preload with query" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
p3 = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
# We use the same text to expose bugs in preload sorting
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
|
||
|
assert %Ecto.Association.NotLoaded{} = p1.comments
|
||
|
|
||
|
# With empty query
|
||
|
assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2],
|
||
|
comments: from(c in Comment, where: false))
|
||
|
assert [] = pe1.comments
|
||
|
assert [] = pe2.comments
|
||
|
assert [] = pe3.comments
|
||
|
|
||
|
# With custom select
|
||
|
assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2],
|
||
|
comments: from(c in Comment, select: c.id, order_by: c.id))
|
||
|
assert [^cid1, ^cid2] = pe1.comments
|
||
|
assert [^cid3, ^cid4] = pe2.comments
|
||
|
assert [] = pe3.comments
|
||
|
|
||
|
# With custom ordered query
|
||
|
assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2],
|
||
|
comments: from(c in Comment, order_by: [desc: c.text]))
|
||
|
assert [%Comment{id: ^cid2}, %Comment{id: ^cid1}] = pe1.comments
|
||
|
assert [%Comment{id: ^cid4}, %Comment{id: ^cid3}] = pe2.comments
|
||
|
assert [] = pe3.comments
|
||
|
|
||
|
# With custom ordered query with preload
|
||
|
assert [pe3, pe1, pe2] = TestRepo.preload([p3, p1, p2],
|
||
|
comments: {from(c in Comment, order_by: [desc: c.text]), :post})
|
||
|
assert [%Comment{id: ^cid2} = c2, %Comment{id: ^cid1} = c1] = pe1.comments
|
||
|
assert [%Comment{id: ^cid4} = c4, %Comment{id: ^cid3} = c3] = pe2.comments
|
||
|
assert [] = pe3.comments
|
||
|
|
||
|
assert c1.post.title == "1"
|
||
|
assert c2.post.title == "1"
|
||
|
assert c3.post.title == "2"
|
||
|
assert c4.post.title == "2"
|
||
|
end
|
||
|
|
||
|
test "preload through with query" do
|
||
|
%Post{id: pid1} = p1 = TestRepo.insert!(%Post{})
|
||
|
|
||
|
u1 = TestRepo.insert!(%User{name: "foo"})
|
||
|
u2 = TestRepo.insert!(%User{name: "bar"})
|
||
|
u3 = TestRepo.insert!(%User{name: "baz"})
|
||
|
u4 = TestRepo.insert!(%User{name: "norf"})
|
||
|
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u1.id})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u1.id})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u2.id})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u3.id})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: u4.id})
|
||
|
|
||
|
np1 = TestRepo.preload(p1, comments_authors: from(u in User, where: u.name == "foo"))
|
||
|
assert np1.comments_authors == [u1]
|
||
|
|
||
|
assert_raise ArgumentError, ~r/Ecto expected a map\/struct with the key `id` but got: \d+/, fn ->
|
||
|
TestRepo.preload(p1, comments_authors: from(u in User, order_by: u.name, select: u.id))
|
||
|
end
|
||
|
|
||
|
# The subpreload order does not matter because the result is dictated by comments
|
||
|
np1 = TestRepo.preload(p1, comments_authors: from(u in User, order_by: u.name, select: %{id: u.id}))
|
||
|
assert np1.comments_authors ==
|
||
|
[%{id: u1.id}, %{id: u2.id}, %{id: u3.id}, %{id: u4.id}]
|
||
|
end
|
||
|
|
||
|
## With take
|
||
|
|
||
|
test "preload with take" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
_p = TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
|
||
|
assert %Ecto.Association.NotLoaded{} = p1.comments
|
||
|
|
||
|
posts = TestRepo.all(from Post, preload: [:comments], select: [:id, comments: [:id, :post_id]])
|
||
|
[p1, p2, p3] = sort_by_id(posts)
|
||
|
assert p1.title == nil
|
||
|
assert p2.title == nil
|
||
|
assert p3.title == nil
|
||
|
|
||
|
assert [%{id: ^cid1, text: nil}, %{id: ^cid2, text: nil}] = sort_by_id(p1.comments)
|
||
|
assert [%{id: ^cid3, text: nil}, %{id: ^cid4, text: nil}] = sort_by_id(p2.comments)
|
||
|
assert [] = sort_by_id(p3.comments)
|
||
|
end
|
||
|
|
||
|
test "preload through with take" do
|
||
|
%Post{id: pid1} = TestRepo.insert!(%Post{})
|
||
|
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "foo"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "bar"})
|
||
|
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid1})
|
||
|
%Comment{} = TestRepo.insert!(%Comment{post_id: pid1, author_id: uid2})
|
||
|
|
||
|
[p1] = TestRepo.all from Post, preload: [:comments_authors], select: [:id, comments_authors: :id]
|
||
|
[%{id: ^uid1, name: nil}, %{id: ^uid2, name: nil}] = p1.comments_authors |> sort_by_id
|
||
|
end
|
||
|
|
||
|
## Nested
|
||
|
|
||
|
test "preload many assocs" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
|
||
|
assert [p2, p1] = TestRepo.preload([p2, p1], [:comments, :users])
|
||
|
assert p1.comments == []
|
||
|
assert p2.comments == []
|
||
|
assert p1.users == []
|
||
|
assert p2.users == []
|
||
|
end
|
||
|
|
||
|
test "preload nested" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
|
||
|
TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
TestRepo.insert!(%Comment{text: "4", post_id: p2.id})
|
||
|
|
||
|
assert [p2, p1] = TestRepo.preload([p2, p1], [comments: :post])
|
||
|
assert [c1, c2] = p1.comments
|
||
|
assert [c3, c4] = p2.comments
|
||
|
assert p1.id == c1.post.id
|
||
|
assert p1.id == c2.post.id
|
||
|
assert p2.id == c3.post.id
|
||
|
assert p2.id == c4.post.id
|
||
|
end
|
||
|
|
||
|
test "preload nested via custom query" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
|
||
|
TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
TestRepo.insert!(%Comment{text: "4", post_id: p2.id})
|
||
|
|
||
|
query = from(c in Comment, preload: :post, order_by: [desc: c.text])
|
||
|
assert [p2, p1] = TestRepo.preload([p2, p1], comments: query)
|
||
|
assert [c2, c1] = p1.comments
|
||
|
assert [c4, c3] = p2.comments
|
||
|
assert p1.id == c1.post.id
|
||
|
assert p1.id == c2.post.id
|
||
|
assert p2.id == c3.post.id
|
||
|
assert p2.id == c4.post.id
|
||
|
end
|
||
|
|
||
|
test "custom preload_order" do
|
||
|
post = TestRepo.insert!(%Post{users: [%User{name: "bar"}, %User{name: "foo"}], title: "1"})
|
||
|
|
||
|
TestRepo.insert!(%Comment{text: "2", post_id: post.id})
|
||
|
TestRepo.insert!(%Comment{text: "1", post_id: post.id})
|
||
|
|
||
|
post = TestRepo.preload(post, [:ordered_comments, :ordered_users])
|
||
|
|
||
|
# asc
|
||
|
assert [%{text: "1"}, %{text: "2"}] = post.ordered_comments
|
||
|
|
||
|
# desc
|
||
|
assert [%{name: "foo"}, %{name: "bar"}] = post.ordered_users
|
||
|
end
|
||
|
|
||
|
test "custom preload_order with mfa" do
|
||
|
post1 = TestRepo.insert!(%Post{users: [%User{name: "bar"}, %User{name: "foo"}], title: "1"})
|
||
|
post2 = TestRepo.insert!(%Post{users: [%User{name: "baz"}, %User{name: "foz"}], title: "2"})
|
||
|
|
||
|
[post1, post2] = TestRepo.preload([post1, post2], [:ordered_users_by_join_table], log: :error)
|
||
|
|
||
|
assert [%{name: "foo"}, %{name: "bar"}] = post1.ordered_users_by_join_table
|
||
|
assert [%{name: "foz"}, %{name: "baz"}] = post2.ordered_users_by_join_table
|
||
|
end
|
||
|
|
||
|
## Others
|
||
|
|
||
|
@tag :invalid_prefix
|
||
|
test "preload custom prefix from schema" do
|
||
|
p = TestRepo.insert!(%Post{title: "1"})
|
||
|
p = Ecto.put_meta(p, prefix: "this_surely_does_not_exist")
|
||
|
# This preload should fail because it points to a prefix that does not exist
|
||
|
assert catch_error(TestRepo.preload(p, [:comments]))
|
||
|
end
|
||
|
|
||
|
@tag :invalid_prefix
|
||
|
test "preload custom prefix from options" do
|
||
|
p = TestRepo.insert!(%Post{title: "1"})
|
||
|
# This preload should fail because it points to a prefix that does not exist
|
||
|
assert catch_error(TestRepo.preload(p, [:comments], prefix: "this_surely_does_not_exist"))
|
||
|
end
|
||
|
|
||
|
test "preload with binary_id" do
|
||
|
c = TestRepo.insert!(%Custom{})
|
||
|
u = TestRepo.insert!(%User{custom_id: c.bid})
|
||
|
|
||
|
u = TestRepo.preload(u, :custom)
|
||
|
assert u.custom.bid == c.bid
|
||
|
end
|
||
|
|
||
|
test "preload raises with association set but without id" do
|
||
|
c1 = TestRepo.insert!(%Comment{text: "1"})
|
||
|
u1 = TestRepo.insert!(%User{name: "name"})
|
||
|
updated = %{c1 | author: u1, author_id: nil}
|
||
|
|
||
|
assert ExUnit.CaptureLog.capture_log(fn ->
|
||
|
assert TestRepo.preload(updated, [:author]).author == u1
|
||
|
end) =~ ~r/its association key `author_id` is nil/
|
||
|
|
||
|
assert TestRepo.preload(updated, [:author], force: true).author == nil
|
||
|
end
|
||
|
|
||
|
test "preload skips already loaded for cardinality one" do
|
||
|
%Post{id: pid} = TestRepo.insert!(%Post{title: "1"})
|
||
|
|
||
|
c1 = %Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: pid})
|
||
|
c2 = %Comment{id: _cid} = TestRepo.insert!(%Comment{text: "2", post_id: nil})
|
||
|
|
||
|
[c1, c2] = TestRepo.preload([c1, c2], :post)
|
||
|
assert %Post{id: ^pid} = c1.post
|
||
|
assert c2.post == nil
|
||
|
|
||
|
[c1, c2] = TestRepo.preload([c1, c2], post: :comments)
|
||
|
assert [%Comment{id: ^cid1}] = c1.post.comments
|
||
|
|
||
|
TestRepo.update_all Post, set: [title: "0"]
|
||
|
TestRepo.update_all Comment, set: [post_id: pid]
|
||
|
|
||
|
# Preloading once again shouldn't change the result
|
||
|
[c1, c2] = TestRepo.preload([c1, c2], :post)
|
||
|
assert %Post{id: ^pid, title: "1", comments: [_|_]} = c1.post
|
||
|
assert c2.post == nil
|
||
|
|
||
|
[c1, c2] = TestRepo.preload([c1, %{c2 | post_id: pid}], :post, force: true)
|
||
|
assert %Post{id: ^pid, title: "0", comments: %Ecto.Association.NotLoaded{}} = c1.post
|
||
|
assert %Post{id: ^pid, title: "0", comments: %Ecto.Association.NotLoaded{}} = c2.post
|
||
|
end
|
||
|
|
||
|
test "preload skips already loaded for cardinality many" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p2.id})
|
||
|
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], :comments)
|
||
|
assert [%Comment{id: ^cid1}] = p1.comments
|
||
|
assert [%Comment{id: ^cid2}] = p2.comments
|
||
|
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], comments: :post)
|
||
|
assert hd(p1.comments).post.id == p1.id
|
||
|
assert hd(p2.comments).post.id == p2.id
|
||
|
|
||
|
TestRepo.update_all Comment, set: [text: "0"]
|
||
|
|
||
|
# Preloading once again shouldn't change the result
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], :comments)
|
||
|
assert [%Comment{id: ^cid1, text: "1", post: %Post{}}] = p1.comments
|
||
|
assert [%Comment{id: ^cid2, text: "2", post: %Post{}}] = p2.comments
|
||
|
|
||
|
[p1, p2] = TestRepo.preload([p1, p2], :comments, force: true)
|
||
|
assert [%Comment{id: ^cid1, text: "0", post: %Ecto.Association.NotLoaded{}}] = p1.comments
|
||
|
assert [%Comment{id: ^cid2, text: "0", post: %Ecto.Association.NotLoaded{}}] = p2.comments
|
||
|
end
|
||
|
|
||
|
test "preload keyword query" do
|
||
|
p1 = TestRepo.insert!(%Post{title: "1"})
|
||
|
p2 = TestRepo.insert!(%Post{title: "2"})
|
||
|
TestRepo.insert!(%Post{title: "3"})
|
||
|
|
||
|
%Comment{id: cid1} = TestRepo.insert!(%Comment{text: "1", post_id: p1.id})
|
||
|
%Comment{id: cid2} = TestRepo.insert!(%Comment{text: "2", post_id: p1.id})
|
||
|
%Comment{id: cid3} = TestRepo.insert!(%Comment{text: "3", post_id: p2.id})
|
||
|
%Comment{id: cid4} = TestRepo.insert!(%Comment{text: "4", post_id: p2.id})
|
||
|
|
||
|
# Regular query
|
||
|
query = from(p in Post, preload: [:comments], select: p)
|
||
|
|
||
|
assert [p1, p2, p3] = TestRepo.all(query) |> sort_by_id
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id
|
||
|
assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id
|
||
|
assert [] = p3.comments
|
||
|
|
||
|
# Query with interpolated preload query
|
||
|
query = from(p in Post, preload: [comments: ^from(c in Comment, where: false)], select: p)
|
||
|
|
||
|
assert [p1, p2, p3] = TestRepo.all(query)
|
||
|
assert [] = p1.comments
|
||
|
assert [] = p2.comments
|
||
|
assert [] = p3.comments
|
||
|
|
||
|
# Now let's use an interpolated preload too
|
||
|
comments = [:comments]
|
||
|
query = from(p in Post, preload: ^comments, select: {0, [p], 1, 2})
|
||
|
|
||
|
posts = TestRepo.all(query)
|
||
|
[p1, p2, p3] = Enum.map(posts, fn {0, [p], 1, 2} -> p end) |> sort_by_id
|
||
|
|
||
|
assert [%Comment{id: ^cid1}, %Comment{id: ^cid2}] = p1.comments |> sort_by_id
|
||
|
assert [%Comment{id: ^cid3}, %Comment{id: ^cid4}] = p2.comments |> sort_by_id
|
||
|
assert [] = p3.comments
|
||
|
end
|
||
|
|
||
|
|
||
|
test "preload belongs_to in embedded_schema" do
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "1"})
|
||
|
item = %Item{user_id: uid1}
|
||
|
|
||
|
# Starts as not loaded
|
||
|
assert %Ecto.Association.NotLoaded{} = item.user
|
||
|
|
||
|
# Now we preload it
|
||
|
item = TestRepo.preload(item, :user)
|
||
|
assert %User{id: ^uid1} = item.user
|
||
|
end
|
||
|
|
||
|
describe "preload associations from nested embeds" do
|
||
|
setup do
|
||
|
%User{id: uid1} = TestRepo.insert!(%User{name: "1"})
|
||
|
%User{id: uid2} = TestRepo.insert!(%User{name: "2"})
|
||
|
%User{id: uid3} = TestRepo.insert!(%User{name: "3"})
|
||
|
item1 = %Item{id: 1, user_id: uid1}
|
||
|
item2 = %Item{id: 2, user_id: uid2}
|
||
|
item3 = %Item{id: 3, user_id: uid3}
|
||
|
order1 = %Order{items: [item1, item3, item2], item: item1}
|
||
|
order2 = %Order{items: [], item: nil}
|
||
|
order3 = %Order{items: nil, item: nil}
|
||
|
order4 = %Order{items: [item1, item2], item: item2}
|
||
|
|
||
|
[orders: [order1, order2, order3, order4]]
|
||
|
end
|
||
|
|
||
|
test "cannot preload embed without its associations", context do
|
||
|
assert_raise ArgumentError, ~r/cannot preload embedded field/, fn ->
|
||
|
TestRepo.preload(context.orders, :item)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
test "embeds_one", context do
|
||
|
[nil | preloaded_orders] = [nil | context.orders] |> TestRepo.preload(item: :user)
|
||
|
|
||
|
expected_item_user =
|
||
|
Enum.map(context.orders, fn
|
||
|
%{item: nil} -> {nil, nil}
|
||
|
%{item: item} -> {item.id, item.user_id}
|
||
|
end)
|
||
|
|
||
|
actual_item_user =
|
||
|
Enum.map(preloaded_orders, fn
|
||
|
%{item: nil} -> {nil, nil}
|
||
|
%{item: item} -> {item.id, item.user.id}
|
||
|
end)
|
||
|
|
||
|
assert expected_item_user == actual_item_user
|
||
|
end
|
||
|
|
||
|
test "embeds_many", context do
|
||
|
[nil | preloaded_orders] = [nil | context.orders] |> TestRepo.preload(items: :user)
|
||
|
|
||
|
expected_items_user =
|
||
|
Enum.map(context.orders, fn
|
||
|
%{items: nil} -> {nil, nil}
|
||
|
%{items: items} -> Enum.map(items, & {&1.id, &1.user_id})
|
||
|
end)
|
||
|
|
||
|
actual_items_user =
|
||
|
Enum.map(preloaded_orders, fn
|
||
|
%{items: nil} -> {nil, nil}
|
||
|
%{items: items} -> Enum.map(items, & {&1.id, &1.user.id})
|
||
|
end)
|
||
|
|
||
|
assert expected_items_user == actual_items_user
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp sort_by_id(values) do
|
||
|
Enum.sort_by(values, &(&1.id))
|
||
|
end
|
||
|
end
|