#elixirlang #ecto 관계를 여러 번 타는 건 ecto가 알아서 하고 우리는 through 옵션으로 한 번에 접근하자
schema "artists" do
field(:name)
field(:birth_date, :date)
field(:death_date, :date)
timestamps()
has_many(:albums, Album, on_replace: :nilify)
end
schema "albums" do
field(:title, :string)
timestamps()
belongs_to(:artist, Artist)
has_many(:tracks, Track)
end
schema "tracks" do
field(:title, :string)
field(:duration, :integer)
field(:duration_string, :string, virtual: true)
field(:index, :integer)
field(:number_of_plays, :integer)
timestamps()
belongs_to(:album, Album)
end
DB 예제 단골이시다. 아티스트와 앨범은 일대다(one-to-many) 관계이고 앨범과 트랙의 관계도 일대다 관계다. 아티스트의 트랙 전부를 쿼리하려면 어떻게 하면 될까?
iex> import Ecto.Query
iex> (from(a in MusicDB.Artist, where: a.name == "Bobby Hutcherson")
...> |> MusicDB.Repo.all()
...> |> MusicDB.Repo.preload([:albums])
...> |> Enum.flat_map(& &1.albums)
...> |> MusicDB.Repo.preload([:tracks])
...> |> Enum.flat_map(& &1.tracks))
[
%MusicDB.Track{
__meta__: #Ecto.Schema.Metadata<:loaded, "tracks">,
album: #Ecto.Association.NotLoaded<association :album is not loaded>,
album_id: 5,
duration: 761,
duration_string: nil,
id: 30,
index: 1,
inserted_at: ~N[2020-07-20 11:04:28],
number_of_plays: 0,
title: "Anton's Ball",
updated_at: ~N[2020-07-20 11:04:28]
},
# ...
%MusicDB.Track{
__meta__: #Ecto.Schema.Metadata<:loaded, "tracks">,
album: #Ecto.Association.NotLoaded<association :album is not loaded>,
album_id: 5,
duration: 844,
duration_string: nil,
id: 33,
index: 4,
inserted_at: ~N[2020-07-20 11:04:29],
number_of_plays: 0,
title: "Song Of Songs",
updated_at: ~N[2020-07-20 11:04:29]
}
]
아티스트와 관계(relationship)된 앨범을 읽어오고 다시 앨범과 관계된 트랙을 읽어오면 된다. Ecto.Repo.preload/2 함수는 관계 데이터를 읽어오는 함수다. 아티스트를 쿼리해서 읽어온다고 하더라도 preload 함수를 호출해 읽어오기 전까진 관계된 앨범 데이터를 읽어오지 않는다.
schema "artists" do
field(:name)
field(:birth_date, :date)
field(:death_date, :date)
timestamps()
has_many(:albums, Album, on_replace: :nilify)
has_many(:tracks, through: [:albums, :tracks]) #-- 1
end
1번 코드처럼 has_many/3, has_one/3 함수 through 옵션을 사용하면 관계를 타고 들어가야 하는 데이터를 쉽게 쿼리할 수 있다. 아티스트의 tracks 관계를 가져오면 :albums, :tracks 관계 데이터를 차례로 가져온다.
iex> import Ecto.Query
iex> (from(a in MusicDB.Artist, where: a.name == "Bobby Hutcherson")
...> |> MusicDB.Repo.all()
...>|> MusicDB.Repo.preload([:tracks])
...>|> Enum.flat_map(& &1.tracks))
MusicDB.Repo.preload([:tracks])
한 번으로 가져올 수 있다. programming ecto 책에서 배웠다.