1defmodule Ecto.AssociationTest do 2 use ExUnit.Case, async: true 3 doctest Ecto.Association 4 5 import Ecto 6 import Ecto.Query, only: [from: 2] 7 8 alias __MODULE__.Author 9 alias __MODULE__.Comment 10 alias __MODULE__.CommentWithPrefix 11 alias __MODULE__.Permalink 12 alias __MODULE__.Post 13 alias __MODULE__.PostWithPrefix 14 alias __MODULE__.Summary 15 alias __MODULE__.Email 16 alias __MODULE__.Profile 17 alias __MODULE__.AuthorPermalink 18 19 defmodule Post do 20 use Ecto.Schema 21 22 schema "posts" do 23 field :title, :string 24 25 has_many :comments, Comment 26 has_one :permalink, Permalink 27 has_many :permalinks, Permalink 28 belongs_to :author, Author, defaults: [title: "World!"] 29 belongs_to :summary, Summary 30 end 31 end 32 33 defmodule Comment do 34 use Ecto.Schema 35 36 schema "comments" do 37 field :text, :string 38 39 belongs_to :post, Post 40 has_one :permalink, Permalink 41 has_one :post_author, through: [:post, :author] # belongs -> belongs 42 has_one :post_permalink, through: [:post, :permalink] # belongs -> one 43 end 44 end 45 46 defmodule Permalink do 47 use Ecto.Schema 48 49 schema "permalinks" do 50 field :url, :string 51 many_to_many :authors, Author, join_through: AuthorPermalink, defaults: [title: "m2m!"] 52 has_many :author_emails, through: [:authors, :emails] 53 end 54 end 55 56 defmodule PostWithPrefix do 57 use Ecto.Schema 58 @schema_prefix "my_prefix" 59 60 schema "posts" do 61 belongs_to :author, Author 62 has_many :comments_with_prefix, CommentWithPrefix 63 end 64 end 65 66 defmodule CommentWithPrefix do 67 use Ecto.Schema 68 @schema_prefix "my_prefix" 69 70 schema "comments" do 71 belongs_to :posts_with_prefix, Post, foreign_key: :post_with_prefix_id 72 end 73 end 74 75 defmodule Author do 76 use Ecto.Schema 77 78 schema "authors" do 79 field :title, :string 80 has_many :posts, Post, on_replace: :delete 81 has_many :posts_comments, through: [:posts, :comments] # many -> many 82 has_many :posts_permalinks, through: [:posts, :permalink] # many -> one 83 has_many :emails, {"users_emails", Email} 84 has_one :profile, {"users_profiles", Profile}, 85 defaults: [name: "default"], on_replace: :delete 86 many_to_many :permalinks, {"custom_permalinks", Permalink}, 87 join_through: "authors_permalinks" 88 has_many :posts_with_prefix, PostWithPrefix 89 has_many :comments_with_prefix, through: [:posts_with_prefix, :comments_with_prefix] 90 end 91 end 92 93 defmodule AuthorPermalink do 94 use Ecto.Schema 95 96 schema "authors_permalinks" do 97 field :author_id 98 field :permalink_id 99 end 100 end 101 102 defmodule Summary do 103 use Ecto.Schema 104 105 schema "summaries" do 106 has_one :post, Post, defaults: [title: "default"], on_replace: :nilify 107 has_many :posts, Post, on_replace: :nilify 108 has_one :post_author, through: [:post, :author] # one -> belongs 109 has_many :post_comments, through: [:post, :comments] # one -> many 110 end 111 end 112 113 defmodule Email do 114 use Ecto.Schema 115 116 schema "emails" do 117 belongs_to :author, {"post_authors", Author} 118 end 119 end 120 121 defmodule Profile do 122 use Ecto.Schema 123 124 schema "profiles" do 125 field :name 126 belongs_to :author, Author 127 belongs_to :summary, Summary 128 end 129 end 130 131 test "has many" do 132 assoc = Post.__schema__(:association, :comments) 133 134 assert inspect(Ecto.Association.Has.joins_query(assoc)) == 135 inspect(from p in Post, join: c in Comment, on: c.post_id == p.id) 136 137 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [])) == 138 inspect(from c in Comment, where: c.post_id in ^[]) 139 140 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [1, 2, 3])) == 141 inspect(from c in Comment, where: c.post_id in ^[1, 2, 3]) 142 end 143 144 test "has many with specified source" do 145 assoc = Author.__schema__(:association, :emails) 146 147 assert inspect(Ecto.Association.Has.joins_query(assoc)) == 148 inspect(from a in Author, join: e in {"users_emails", Email}, on: e.author_id == a.id) 149 150 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [])) == 151 inspect(from e in {"users_emails", Email}, where: e.author_id in ^[]) 152 153 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [1, 2, 3])) == 154 inspect(from e in {"users_emails", Email}, where: e.author_id in ^[1, 2, 3]) 155 end 156 157 test "has many custom assoc query" do 158 assoc = Post.__schema__(:association, :comments) 159 query = from c in Comment, limit: 5 160 assert inspect(Ecto.Association.Has.assoc_query(assoc, query, [1, 2, 3])) == 161 inspect(from c in Comment, where: c.post_id in ^[1, 2, 3], limit: 5) 162 end 163 164 test "has one" do 165 assoc = Post.__schema__(:association, :permalink) 166 167 assert inspect(Ecto.Association.Has.joins_query(assoc)) == 168 inspect(from p in Post, join: c in Permalink, on: c.post_id == p.id) 169 170 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [])) == 171 inspect(from c in Permalink, where: c.post_id in ^[]) 172 173 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [1])) == 174 inspect(from c in Permalink, where: c.post_id == ^1) 175 176 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [1, 2, 3])) == 177 inspect(from c in Permalink, where: c.post_id in ^[1, 2, 3]) 178 end 179 180 test "has one with specified source" do 181 assoc = Author.__schema__(:association, :profile) 182 183 assert inspect(Ecto.Association.Has.joins_query(assoc)) == 184 inspect(from a in Author, join: p in {"users_profiles", Profile}, on: p.author_id == a.id) 185 186 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [])) == 187 inspect(from p in {"users_profiles", Profile}, where: p.author_id in ^[]) 188 189 assert inspect(Ecto.Association.Has.assoc_query(assoc, nil, [1, 2, 3])) == 190 inspect(from p in {"users_profiles", Profile}, where: p.author_id in ^[1, 2, 3]) 191 end 192 193 test "has one custom assoc query" do 194 assoc = Post.__schema__(:association, :permalink) 195 query = from c in Permalink, limit: 5 196 assert inspect(Ecto.Association.Has.assoc_query(assoc, query, [1, 2, 3])) == 197 inspect(from c in Permalink, where: c.post_id in ^[1, 2, 3], limit: 5) 198 end 199 200 test "belongs to" do 201 assoc = Post.__schema__(:association, :author) 202 203 assert inspect(Ecto.Association.BelongsTo.joins_query(assoc)) == 204 inspect(from p in Post, join: a in Author, on: a.id == p.author_id) 205 206 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [])) == 207 inspect(from a in Author, where: a.id in ^[]) 208 209 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [1])) == 210 inspect(from a in Author, where: a.id == ^1) 211 212 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [1, 2, 3])) == 213 inspect(from a in Author, where: a.id in ^[1, 2, 3]) 214 end 215 216 test "belongs to with specified source" do 217 assoc = Email.__schema__(:association, :author) 218 219 assert inspect(Ecto.Association.BelongsTo.joins_query(assoc)) == 220 inspect(from e in Email, join: a in {"post_authors", Author}, on: a.id == e.author_id) 221 222 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [])) == 223 inspect(from a in {"post_authors", Author}, where: a.id in ^[]) 224 225 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [1])) == 226 inspect(from a in {"post_authors", Author}, where: a.id == ^1) 227 228 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, nil, [1, 2, 3])) == 229 inspect(from a in {"post_authors", Author}, where: a.id in ^[1, 2, 3]) 230 end 231 232 test "belongs to custom assoc query" do 233 assoc = Post.__schema__(:association, :author) 234 query = from a in Author, limit: 5 235 assert inspect(Ecto.Association.BelongsTo.assoc_query(assoc, query, [1, 2, 3])) == 236 inspect(from a in Author, where: a.id in ^[1, 2, 3], limit: 5) 237 end 238 239 test "many to many" do 240 assoc = Permalink.__schema__(:association, :authors) 241 242 assert inspect(Ecto.Association.ManyToMany.joins_query(assoc)) == 243 inspect(from p in Permalink, 244 join: m in AuthorPermalink, on: m.permalink_id == p.id, 245 join: a in Author, on: m.author_id == a.id) 246 247 assert inspect(Ecto.Association.ManyToMany.assoc_query(assoc, nil, [])) == 248 inspect(from a in Author, 249 join: p in Permalink, on: p.id in ^[], 250 join: m in AuthorPermalink, on: m.permalink_id == p.id, 251 where: m.author_id == a.id) 252 253 assert inspect(Ecto.Association.ManyToMany.assoc_query(assoc, nil, [1, 2, 3])) == 254 inspect(from a in Author, 255 join: p in Permalink, on: p.id in ^[1, 2, 3], 256 join: m in AuthorPermalink, on: m.permalink_id == p.id, 257 where: m.author_id == a.id) 258 end 259 260 test "many to many with specified source" do 261 assoc = Author.__schema__(:association, :permalinks) 262 263 assert inspect(Ecto.Association.ManyToMany.joins_query(assoc)) == 264 inspect(from a in Author, 265 join: m in "authors_permalinks", on: m.author_id == a.id, 266 join: p in {"custom_permalinks", Permalink}, on: m.permalink_id == p.id) 267 268 assert inspect(Ecto.Association.ManyToMany.assoc_query(assoc, nil, [])) == 269 inspect(from p in {"custom_permalinks", Permalink}, 270 join: a in Author, on: a.id in ^[], 271 join: m in "authors_permalinks", on: m.author_id == a.id, 272 where: m.permalink_id == p.id) 273 274 assert inspect(Ecto.Association.ManyToMany.assoc_query(assoc, nil, [1, 2, 3])) == 275 inspect(from p in {"custom_permalinks", Permalink}, 276 join: a in Author, on: a.id in ^[1, 2, 3], 277 join: m in "authors_permalinks", on: m.author_id == a.id, 278 where: m.permalink_id == p.id) 279 end 280 281 test "many to many custom assoc query" do 282 assoc = Permalink.__schema__(:association, :authors) 283 query = from a in Author, limit: 5 284 assert inspect(Ecto.Association.ManyToMany.assoc_query(assoc, query, [1, 2, 3])) == 285 inspect(from a in Author, 286 join: p in Permalink, on: p.id in ^[1, 2, 3], 287 join: m in AuthorPermalink, on: m.permalink_id == p.id, 288 where: m.author_id == a.id, limit: 5) 289 end 290 291 test "has many through many to many" do 292 assoc = Author.__schema__(:association, :posts_comments) 293 294 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 295 inspect(from a in Author, join: p in assoc(a, :posts), join: c in assoc(p, :comments)) 296 297 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 298 inspect(from c in Comment, join: p in Post, on: p.author_id in ^[1, 2, 3], 299 where: c.post_id == p.id, distinct: true) 300 end 301 302 test "has many through many to one" do 303 assoc = Author.__schema__(:association, :posts_permalinks) 304 305 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 306 inspect(from a in Author, join: p in assoc(a, :posts), join: c in assoc(p, :permalink)) 307 308 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 309 inspect(from l in Permalink, join: p in Post, on: p.author_id in ^[1, 2, 3], 310 where: l.post_id == p.id, distinct: true) 311 end 312 313 test "has one through belongs to belongs" do 314 assoc = Comment.__schema__(:association, :post_author) 315 316 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 317 inspect(from c in Comment, join: p in assoc(c, :post), join: a in assoc(p, :author)) 318 319 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 320 inspect(from a in Author, join: p in Post, on: p.id in ^[1, 2, 3], 321 where: a.id == p.author_id, distinct: true) 322 end 323 324 test "has one through belongs to one" do 325 assoc = Comment.__schema__(:association, :post_permalink) 326 327 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 328 inspect(from c in Comment, join: p in assoc(c, :post), join: l in assoc(p, :permalink)) 329 330 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 331 inspect(from l in Permalink, join: p in Post, on: p.id in ^[1, 2, 3], 332 where: l.post_id == p.id, distinct: true) 333 end 334 335 test "has many through one to many" do 336 assoc = Summary.__schema__(:association, :post_comments) 337 338 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 339 inspect(from s in Summary, join: p in assoc(s, :post), join: c in assoc(p, :comments)) 340 341 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 342 inspect(from c in Comment, join: p in Post, on: p.summary_id in ^[1, 2, 3], 343 where: c.post_id == p.id, distinct: true) 344 end 345 346 test "has one through one to belongs" do 347 assoc = Summary.__schema__(:association, :post_author) 348 349 assert inspect(Ecto.Association.HasThrough.joins_query(assoc)) == 350 inspect(from s in Summary, join: p in assoc(s, :post), join: a in assoc(p, :author)) 351 352 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1,2,3])) == 353 inspect(from a in Author, join: p in Post, on: p.summary_id in ^[1, 2, 3], 354 where: a.id == p.author_id, distinct: true) 355 end 356 357 test "has many through custom assoc many to many query" do 358 assoc = Author.__schema__(:association, :posts_comments) 359 query = from c in Comment, where: c.text == "foo", limit: 5 360 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, query, [1,2,3])) == 361 inspect(from c in Comment, join: p in Post, 362 on: p.author_id in ^[1, 2, 3], 363 where: c.post_id == p.id, where: c.text == "foo", 364 distinct: true, limit: 5) 365 366 query = from c in {"custom", Comment}, where: c.text == "foo", limit: 5 367 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, query, [1,2,3])) == 368 inspect(from c in {"custom", Comment}, join: p in Post, 369 on: p.author_id in ^[1, 2, 3], 370 where: c.post_id == p.id, where: c.text == "foo", 371 distinct: true, limit: 5) 372 373 query = from c in Comment, join: p in assoc(c, :permalink), limit: 5 374 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, query, [1,2,3])) == 375 inspect(from c in Comment, join: p0 in Permalink, on: p0.comment_id == c.id, 376 join: p1 in Post, on: p1.author_id in ^[1, 2, 3], 377 where: c.post_id == p1.id, 378 distinct: true, limit: 5) 379 end 380 381 test "has many through many to many and has many" do 382 assoc = Permalink.__schema__(:association, :author_emails) 383 assert inspect(Ecto.Association.HasThrough.assoc_query(assoc, nil, [1, 2, 3])) == 384 inspect(from e in {"users_emails", Email}, 385 join: p in Permalink, on: p.id in ^[1, 2, 3], 386 join: ap in AuthorPermalink, on: ap.permalink_id == p.id, 387 join: a in Author, on: ap.author_id == a.id, 388 where: e.author_id == a.id, distinct: true) 389 end 390 391 ## Integration tests through Ecto 392 393 test "build/2" do 394 # has many 395 assert build_assoc(%Post{id: 1}, :comments) == 396 %Comment{post_id: 1} 397 398 # has one 399 assert build_assoc(%Summary{id: 1}, :post) == 400 %Post{summary_id: 1, title: "default"} 401 402 # belongs to 403 assert build_assoc(%Post{id: 1}, :author) == 404 %Author{title: "World!"} 405 406 # many to many 407 assert build_assoc(%Permalink{id: 1}, :authors) == 408 %Author{title: "m2m!"} 409 410 assert_raise ArgumentError, ~r"cannot build through association `:post_author`", fn -> 411 build_assoc(%Comment{}, :post_author) 412 end 413 end 414 415 test "build/2 with custom source" do 416 email = build_assoc(%Author{id: 1}, :emails) 417 assert email.__meta__.source == {nil, "users_emails"} 418 419 profile = build_assoc(%Author{id: 1}, :profile) 420 assert profile.__meta__.source == {nil, "users_profiles"} 421 422 profile = build_assoc(%Email{id: 1}, :author) 423 assert profile.__meta__.source == {nil, "post_authors"} 424 425 permalink = build_assoc(%Author{id: 1}, :permalinks) 426 assert permalink.__meta__.source == {nil, "custom_permalinks"} 427 end 428 429 test "build/3 with custom attributes" do 430 # has many 431 assert build_assoc(%Post{id: 1}, :comments, text: "Awesome!") == 432 %Comment{post_id: 1, text: "Awesome!"} 433 434 assert build_assoc(%Post{id: 1}, :comments, %{text: "Awesome!"}) == 435 %Comment{post_id: 1, text: "Awesome!"} 436 437 # has one 438 assert build_assoc(%Post{id: 1}, :comments, post_id: 2) == 439 %Comment{post_id: 1} 440 441 # belongs to 442 assert build_assoc(%Post{id: 1}, :author, title: "Hello!") == 443 %Author{title: "Hello!"} 444 445 # 2 belongs to 446 with author_post = build_assoc(%Author{id: 1}, :posts), 447 author_and_summary_post = build_assoc(%Summary{id: 2}, :posts, author_post) do 448 assert author_and_summary_post.author_id == 1 449 assert author_and_summary_post.summary_id == 2 450 end 451 452 # many to many 453 assert build_assoc(%Permalink{id: 1}, :authors, title: "Hello!") == 454 %Author{title: "Hello!"} 455 456 # Overriding defaults 457 assert build_assoc(%Summary{id: 1}, :post, title: "Hello").title == "Hello" 458 459 # Should not allow overriding of __meta__ 460 meta = %{__meta__: %{source: {nil, "posts"}}} 461 comment = build_assoc(%Post{id: 1}, :comments, meta) 462 assert comment.__meta__.source == {nil, "comments"} 463 end 464 465 test "sets association to loaded/not loaded" do 466 refute Ecto.assoc_loaded?(%Post{}.comments) 467 assert Ecto.assoc_loaded?(%Post{comments: []}.comments) 468 end 469 470 test "assoc/2" do 471 assert inspect(assoc(%Post{id: 1}, :comments)) == 472 inspect(from c in Comment, where: c.post_id == ^1) 473 474 assert inspect(assoc([%Post{id: 1}, %Post{id: 2}], :comments)) == 475 inspect(from c in Comment, where: c.post_id in ^[1, 2]) 476 end 477 478 test "assoc/2 with prefixes" do 479 author = %Author{id: 1} 480 assert Ecto.assoc(author, :posts_with_prefix).prefix == "my_prefix" 481 assert Ecto.assoc(author, :comments_with_prefix).prefix == "my_prefix" 482 end 483 484 test "assoc/2 filters nil ids" do 485 assert inspect(assoc([%Post{id: 1}, %Post{id: 2}, %Post{}], :comments)) == 486 inspect(from c in Comment, where: c.post_id in ^[1, 2]) 487 end 488 489 test "assoc/2 fails on empty list" do 490 assert_raise ArgumentError, ~r"cannot retrieve association :whatever for empty list", fn -> 491 assoc([], :whatever) 492 end 493 end 494 495 test "assoc/2 fails on missing association" do 496 assert_raise ArgumentError, ~r"does not have association :whatever", fn -> 497 assoc([%Post{}], :whatever) 498 end 499 end 500 501 test "assoc/2 fails on heterogeneous collections" do 502 assert_raise ArgumentError, ~r"expected a homogeneous list containing the same struct", fn -> 503 assoc([%Post{}, %Comment{}], :comments) 504 end 505 end 506 507 ## Preloader 508 509 alias Ecto.Repo.Preloader 510 511 test "preload: normalizer" do 512 assert Preloader.normalize(:foo, nil, []) == 513 [foo: {nil, nil, []}] 514 assert Preloader.normalize([foo: :bar], nil, []) == 515 [foo: {nil, nil, [bar: {nil, nil, []}]}] 516 assert Preloader.normalize([foo: [:bar, baz: :bat], this: :that], nil, []) == 517 [this: {nil, nil, [that: {nil, nil, []}]}, 518 foo: {nil, nil, [baz: {nil, nil, [bat: {nil, nil, []}]}, 519 bar: {nil, nil, []}]}] 520 end 521 522 test "preload: normalize with query" do 523 query = from(p in Post, limit: 1) 524 assert Preloader.normalize([foo: query], nil, []) == 525 [foo: {nil, query, []}] 526 assert Preloader.normalize([foo: {query, :bar}], nil, []) == 527 [foo: {nil, query, [bar: {nil, nil, []}]}] 528 assert Preloader.normalize([foo: {query, bar: :baz}], nil, []) == 529 [foo: {nil, query, [bar: {nil, nil, [baz: {nil, nil, []}]}]}] 530 end 531 532 test "preload: normalize with take" do 533 assert Preloader.normalize([:foo], [foo: :id], []) == 534 [foo: {[:id], nil, []}] 535 assert Preloader.normalize([foo: :bar], [foo: :id], []) == 536 [foo: {[:id], nil, [bar: {nil, nil, []}]}] 537 assert Preloader.normalize([foo: :bar], [foo: [:id, bar: :id]], []) == 538 [foo: {[:id, bar: :id], nil, [bar: {[:id], nil, []}]}] 539 assert Preloader.normalize([foo: [bar: :baz]], [foo: [:id, bar: :id]], []) == 540 [foo: {[:id, bar: :id], nil, [bar: {[:id], nil, [baz: {nil, nil, []}]}]}] 541 end 542 543 test "preload: raises on invalid preload" do 544 assert_raise ArgumentError, ~r"invalid preload `123` in `123`", fn -> 545 Preloader.normalize(123, nil, 123) 546 end 547 548 assert_raise ArgumentError, ~r"invalid preload `{:bar, :baz}` in", fn -> 549 Preloader.normalize([foo: {:bar, :baz}], nil, []) == [foo: [bar: []]] 550 end 551 end 552 553 defp expand(schema, preloads, take \\ nil) do 554 Preloader.expand(schema, Preloader.normalize(preloads, take, preloads), {%{}, %{}}) 555 end 556 557 test "preload: expand" do 558 assert {%{comments: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, nil, nil, []}, 559 permalink: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, nil, nil, []}}, 560 %{}} = 561 expand(Post, [:comments, :permalink]) 562 563 assert {%{post: {{:assoc, %Ecto.Association.BelongsTo{}, {0, :id}}, nil, nil, [author: {nil, nil, []}]}}, 564 %{}} = 565 expand(Comment, [post: :author]) 566 567 assert {%{post: {{:assoc, %Ecto.Association.BelongsTo{}, {0, :id}}, nil, nil, 568 [author: {nil, nil, []}, permalink: {nil, nil, []}]}}, 569 %{}} = 570 expand(Comment, [:post, post: :author, post: :permalink]) 571 572 assert {%{posts: {{:assoc, %Ecto.Association.Has{}, {0, :author_id}}, nil, nil, 573 [comments: {nil, nil, [post: {nil, nil, []}]}]}}, 574 %{posts_comments: {:through, %Ecto.Association.HasThrough{}, [:posts, :comments]}}} = 575 expand(Author, [posts_comments: :post]) 576 577 assert {%{posts: {{:assoc, %Ecto.Association.Has{}, {0, :author_id}}, nil, nil, 578 [comments: _, comments: _]}}, 579 %{posts_comments: {:through, %Ecto.Association.HasThrough{}, [:posts, :comments]}}} = 580 expand(Author, [:posts, posts_comments: :post, posts: [comments: :post]]) 581 end 582 583 test "preload: expand with queries" do 584 query = from(c in Comment, limit: 1) 585 assert {%{permalink: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, nil, nil, []}, 586 comments: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, nil, ^query, []}}, 587 %{}} = 588 expand(Post, [:permalink, comments: query]) 589 590 assert {%{posts: {{:assoc, %Ecto.Association.Has{}, {0, :author_id}}, nil, nil, 591 [comments: {nil, ^query, [post: {nil, nil, []}]}]}}, 592 %{posts_comments: {:through, %Ecto.Association.HasThrough{}, [:posts, :comments]}}} = 593 expand(Author, [posts_comments: {query, :post}]) 594 end 595 596 test "preload: expand with take" do 597 assert {%{permalink: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, [:id], nil, []}, 598 comments: {{:assoc, %Ecto.Association.Has{}, {0, :post_id}}, nil, nil, []}}, 599 %{}} = 600 expand(Post, [:permalink, :comments], [permalink: :id]) 601 602 assert {%{posts: {{:assoc, %Ecto.Association.Has{}, {0, :author_id}}, nil, nil, 603 [comments: {[:id], nil, [post: {nil, nil, []}]}]}}, 604 %{posts_comments: {:through, %Ecto.Association.HasThrough{}, [:posts, :comments]}}} = 605 expand(Author, [posts_comments: :post], [posts_comments: :id]) 606 end 607 608 test "preload: expand raises on duplicated entries" do 609 message = ~r"cannot preload `comments` as it has been supplied more than once with different queries" 610 assert_raise ArgumentError, message, fn -> 611 expand(Post, [comments: from(c in Comment, limit: 2), 612 comments: from(c in Comment, limit: 1)]) 613 end 614 end 615 616 describe "after_compile_validation/2" do 617 defp after_compile_validation(assoc, name, opts) do 618 defmodule Sample do 619 use Ecto.Schema 620 621 schema "sample" do 622 opts = [cardinality: :one] ++ opts 623 throw assoc.after_compile_validation(assoc.struct(__MODULE__, name, opts), 624 %{__ENV__ | context_modules: [Ecto.AssociationTest]}) 625 end 626 end 627 catch 628 result -> result 629 end 630 631 test "for has" do 632 assert after_compile_validation(Ecto.Association.Has, :post, 633 cardinality: :one, queryable: __MODULE__) == 634 :ok 635 636 assert after_compile_validation(Ecto.Association.Has, :post, 637 cardinality: :one, queryable: Unknown) == 638 {:error, "associated schema Unknown does not exist"} 639 640 assert after_compile_validation(Ecto.Association.Has, :post, 641 cardinality: :one, queryable: Ecto.Changeset) == 642 {:error, "associated module Ecto.Changeset is not an Ecto schema"} 643 644 assert after_compile_validation(Ecto.Association.Has, :post, 645 cardinality: :one, queryable: Post) == 646 {:error, "associated schema Ecto.AssociationTest.Post does not have field `sample_id`"} 647 648 assert after_compile_validation(Ecto.Association.Has, :post, 649 cardinality: :one, queryable: Post, foreign_key: :author_id) == 650 :ok 651 end 652 653 test "for belongs_to" do 654 assert after_compile_validation(Ecto.Association.BelongsTo, :sample, 655 foreign_key: :post_id, queryable: __MODULE__) == 656 :ok 657 658 assert after_compile_validation(Ecto.Association.BelongsTo, :sample, 659 foreign_key: :post_id, queryable: Unknown) == 660 {:error, "associated schema Unknown does not exist"} 661 662 assert after_compile_validation(Ecto.Association.BelongsTo, :sample, 663 foreign_key: :post_id, queryable: Ecto.Changeset) == 664 {:error, "associated module Ecto.Changeset is not an Ecto schema"} 665 666 assert after_compile_validation(Ecto.Association.BelongsTo, :sample, 667 foreign_key: :post_id, references: :non_id, queryable: Post) == 668 {:error, "associated schema Ecto.AssociationTest.Post does not have field `non_id`"} 669 670 assert after_compile_validation(Ecto.Association.BelongsTo, :sample, 671 foreign_key: :post_id, references: :id, queryable: Post) == 672 :ok 673 end 674 675 test "for many_to_many" do 676 assert after_compile_validation(Ecto.Association.ManyToMany, :sample, 677 join_through: "join", queryable: __MODULE__) == 678 :ok 679 680 assert after_compile_validation(Ecto.Association.ManyToMany, :sample, 681 join_through: "join", queryable: Unknown) == 682 {:error, "associated schema Unknown does not exist"} 683 684 assert after_compile_validation(Ecto.Association.ManyToMany, :sample, 685 join_through: "join", queryable: Ecto.Changeset) == 686 {:error, "associated module Ecto.Changeset is not an Ecto schema"} 687 688 assert after_compile_validation(Ecto.Association.ManyToMany, :sample, 689 join_through: "join", queryable: Post) == 690 :ok 691 end 692 end 693end 694