Home > Back-end >  Ecto Association with two objects of same class
Ecto Association with two objects of same class

Time:10-18

I'm trying to define a Game module that should have an associated Team linked to team_a_id column and another associated Team linked to team_b_id column (both should match id column on teams table).

So far it looks like this:

defmodule MyApp.Store.Game do
  use Ecto.Schema
  import Ecto.Changeset

  schema "games" do
    field :date, :utc_datetime
    field :title, :string

    has_many :game_gets, MyApp.Store.GameBet

    belongs_to :team_a, MyApp.Store.Team
    belongs_to :team_b, MyApp.Store.Team

    timestamps()
  end

  @doc false
  def changeset(game, attrs) do
    game
    |> cast(attrs, [:title, :date, :team_a_id, :team_b_id])
    |> validate_required([:title, :date])
  end
end

This is the migration file:

defmodule MyApp.Repo.Migrations.CreateGames do
  use Ecto.Migration

  def change do
    create table(:games) do
      add :title, :string
      add :date, :utc_datetime
      add :team_a_id, references(:teams, on_delete: :nothing)
      add :team_b_id, references(:teams, on_delete: :nothing)

      timestamps()
    end

    create index(:games, [:team_a_id])
    create index(:games, [:team_b_id])
    create unique_index(:games, [:title])
  end
end

Team module is defined like this:

defmodule MyApp.Store.Team do
  use Ecto.Schema
  import Ecto.Changeset

  schema "teams" do
    field :image_src, :string
    field :name, :string

    timestamps()
  end

  @doc false
  def changeset(team, attrs) do
    team
    |> cast(attrs, [:name, :image_src])
    |> validate_required([:name])
  end
end

Migration file is pretty standard:

defmodule MyApp.Repo.Migrations.CreateTeams do
  use Ecto.Migration

  def change do
    create table(:teams) do
      add :name, :string
      add :image_src, :string

      timestamps()
    end
  end
end

But up to this point, whenever I try to create an new Game:

{:ok, game} =
      attrs
      |> Enum.into(%{
        date: ~U[2022-07-31 15:03:00Z],
        title: "red_devils_vs_bold_guys_21092022",
        team_a_id: team_a.id,
        team_b_id: team_b.id
      })
      |> MyApp.Store.create_game()

I get the following error:

     ** (Postgrex.Error) ERROR 42703 (undefined_column) column "team_a_id" of relation "games" does not exist
     
         query: INSERT INTO "games" ("date","team_a_id","team_b_id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id"

*Just in case anyone wonders, "team_a_id" column does exist:

tourney_dev=# SELECT * FROM games;
 id | title | date | team_a_id | team_b_id | inserted_at | updated_at 
---- ------- ------ ----------- ----------- ------------- ------------
(0 rows)

CodePudding user response:

To formalize my comment into an example, I would rework the definition into one that uses has_one instead of belongs_to. Consider something like the following:

defmodule MyApp.Store.Team do
  use Ecto.Schema
  import Ecto.Changeset

  schema "teams" do
    field :image_src, :string
    field :name, :string

    timestamps()
  end

  # ...
end

defmodule MyApp.Store.Game do
  use Ecto.Schema
  import Ecto.Changeset

  schema "games" do
    field :date, :utc_datetime
    field :title, :string


    timestamps()
  end

  schema "games" do
    field :date, :utc_datetime
    field :title, :string
    field :team_a_id :integer
    field :team_b_id :integer
    timestamps()

    has_one :team_a, MyApp.Store.Team, foreign_key: :id, references: :team_a_id
    has_one :team_b, MyApp.Store.Team, foreign_key: :id, references: :team_b_id
  end
  # ...
end

The above definitions work, e.g. in iex I can do the following (verified/tested):

# use changesets to insert:

iex> MyApp.Store.Team.changeset(%Foo.Team{}, %{name: "Alpha"}) |> MyApp.Repo.insert()
iex> MyApp.Store.Team.changeset(%Foo.Team{}, %{name: "Beta"}) |> MyApp..Repo.insert()

# Or pass a valid struct to Repo.insert:
iex> %MyApp.Store.Game{title: "Showdown", team_a_id: 1, team_b_id: 2} |> MyApp.Repo.insert()

I think you need to show us what your MyApp.Store.create_game/1 function is doing.

Remember, with Ecto (or any database abstraction tool), defining the relations is only useful when it's actually useful. If you're not querying the database using the abstraction, then it can save time to just write regular old SQL queries.

  • Related