Search code examples
elixirassociationsectopolymorphic-associations

How do I define Elixir Ecto schema for a has_one / belongs_to_many association?


In the explanation of the Belongs To/Has One association at https://elixirschool.com/en/lessons/ecto/associations/#belongs-tohas-one the author writes:

Let’s say that a movie has one distributor, for example Netflix is the distributor of their original film “Bright”.

...

...the Distributor schema should use the belongs_to/3 macro to allow us to call distributor.movie and look up a distributor’s associated movie using this foreign key.

...

The has_one/3 macro functions just like the has_many/3 macro. It uses the associated schema’s foreign key to look up and expose the movie’s distributor. This will allow us to call movie.distributor.

Here are the schemas for their example:

defmodule Example.Distributor do
  use Ecto.Schema

  schema "distributors" do
    field :name, :string
    belongs_to :movie, Example.Movie
  end
end

defmodule Example.Movie do
  use Ecto.Schema

  schema "movies" do
    field :title, :string
    field :tagline, :string
    has_many :characters, Example.Character
    has_one :distributor, Example.Distributor # I'm new!
  end
end

José Valim writes in http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/:

The difference between has_one/3 and belongs_to/3 is that the foreign key is always defined in the schema that invokes belongs_to/3.

So by using belongs_to/3 in the Distributor schema, a foreign key is defined in that schema, limiting a single distributor in this example to a single movie. (This is confirmed by the example writer's "...to allow us to call distributor.movie and look up a distributor’s associated movie using this foreign key.")

How would I define the schema if I want a movie to have one distributor, but a single distributor to have one or more movies?


Solution

  • You reverse it.

    If a movie has only one distributor, you put the foreign key in its schema.

    And then when you need to find movies for one specific distributor, you just find all the movies which have distributor_id foreign key referencing the distributor's id.

    defmodule Example.Movie do
      use Ecto.Schema
    
      schema "movies" do
        field :title, :string
        field :tagline, :string
        has_many :characters, Example.Character
        belongs_to :distributor, Example.Distributor
      end
    end
    
    defmodule Example.Distributor do
      use Ecto.Schema
    
      schema "distributors" do
        field :name, :string
        has_many :movies, Example.Movie
      end
    end