#elixirlang 외래 키는 스키마에 정의하고 ecto 연관 함수로 외래 키를 직접 쓰지 말자

1 minute read

relationship

연관(associations) 함수를 사용하면 외래 키(foreign key)를 직접 안 넘겨도 된다. ecto 연관 함수를 쓰면 스키마(schema)에 외래 키를 정의하기에 외래 키를 직접 넘기지 않아도 된다. Programming Phoenix 1.4 책에서 본 온 소스 코드를 간단하게 수정했다.

defmodule User do
  use Ecto.Schema

  schema "users" do
    field(:name, :string)

    has_many(:videos, Video)
  end
end

defmodule Video do
  use Ecto.Schema

  schema "videos" do
    field(:title, :string)

    belongs_to(:user, User)
  end
end

테스트를 위해 스키마를 먼저 정의한다. User와 Video에 일대다 관계(one-to-many relationship)를 설정한다. belongs_to/3 매크로로 외래 키를 정의한다. 옵션을 넘기지 않으면 _id 접미사를 붙여서 키를 정의한다. 즉 user_id 키를 정의한다.

iex> {:ok, user} = Repo.insert(%User{name: "ohyecloudy"})
{:ok,
 %User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   id: 1,
   name: "ohyecloudy",
   videos: #Ecto.Association.NotLoaded<association :videos is not loaded>
 }}

iex> {:ok, video} = Repo.insert(%Video{title: "video 1", user_id: user.id})
{:ok,
 %Video{
   __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
   id: 1,
   title: "video 1",
   user: #Ecto.Association.NotLoaded<association :user is not loaded>,
   user_id: 1
 }}

user_id: user.id 키와 값을 넘겨 관계를 만든다. 이렇게 직접 외래 키를 넣는 방법 대신에 스키마에서 가져오게 할 수도 있다.

iex> {:ok, video} = Ecto.build_assoc(user, :videos, %{title: "video 2"}) |> Repo.insert()
{:ok,
 %Video{
   __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
   id: 2,
   title: "video 2",
   user: #Ecto.Association.NotLoaded<association :user is not loaded>,
   user_id: 1
 }}

Ecto.build_assoc/3 함수를 사용하면 외래 키 이름을 스키마에서 가져와서 넣는다. 외래 키 이름이 뭔지 신경 쓸 필요 없이 스키마에 정의한 대로 관계를 만든다.

iex> import Ecto.Query
iex> Repo.all(from v in Video, where: v.user_id == ^user.id)
[
  %Video{
    __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
    id: 1,
    title: "video 1",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  },
  %Video{
    __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
    id: 2,
    title: "video 2",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  }
]

where 절에 user_id 컬럼 값을 검사해서 video를 찾을 수 있다.

iex> Ecto.assoc(user, :videos) |> Repo.all()
[
  %Video{
    __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
    id: 1,
    title: "video 1",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  },
  %Video{
    __meta__: #Ecto.Schema.Metadata<:loaded, "videos">,
    id: 2,
    title: "video 2",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  }
]

Ecto.assoc/2 함수를 사용하면 더 간단하게 쿼리(query) 문을 만들 수 있다.

ecto 연관 함수를 사용하면 스키마에 정의한 외래 키를 사용해 더 편하게 조회 및 삽입을 할 수 있다.