I have a many_to_many relationship ( I guess there really isn’t such a thing) but a junction table nevertheless.
erDiagram
USER_GAMES {
int user_id
int game_id
}
USER {
int id
string name
}
GAME {
int id
string name
}
schema "users" do
field :email, :string
many_to_many :games, Game, join_through: "users_games"
timestamps(type: :utc_datetime)
end
schema "games" do
field :name, :string
field :state, :map
field :started_at, :utc_datetime
many_to_many :users, User, join_through: "users_games"
timestamps(type: :utc_datetime)
end
No schema for users_games but in the migration
create table("users_games", primary_key: false) do
add :user_id,
references(:users, on_delete: :delete_all),
null: false
add :game_id,
references(:games, on_delete: :delete_all),
null: false
timestamps()
end
create unique_index(:users_games, [:user_id, :game_id])
end
end
The action happens here
new_game = %Game{
name: attrs.name,
started_at: attrs.started_at
}
user =
Accounts.get_user_with_games(user_id)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:games, [new_game])
|> Repo.update!()
where get_user_with_games is
get_user!(user_id)
|> Repo.preload(:games)
I realise right now I need to include any existing related games but I can get to that later.
I get this
[debug] QUERY OK db=1.0ms idle=895.3ms
begin []
↳ ScrabbleWeb.GameLive.Form.save_game/3, at: lib/scrabble_web/live/game_live/form.ex:87
[debug] QUERY OK source="games" db=1.6ms
INSERT INTO "games" ("name","started_at","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["ABC", ~U[2026-02-12 19:12:43Z], ~U[2026-02-12 19:12:43Z], ~U[2026-02-12 19:12:43Z]]
↳ ScrabbleWeb.GameLive.Form.save_game/3, at: lib/scrabble_web/live/game_live/form.ex:87
[debug] QUERY ERROR source="users_games" db=0.5ms
INSERT INTO "users_games" ("user_id","game_id") VALUES ($1,$2) [1, 6]
↳ ScrabbleWeb.GameLive.Form.save_game/3, at: lib/scrabble_web/live/game_live/form.ex:87
[debug] QUERY OK db=0.2ms
rollback []
↳ ScrabbleWeb.GameLive.Form.save_game/3, at: lib/scrabble_web/live/game_live/form.ex:87
[error] GenServer #PID<0.1178.0> terminating
** (Postgrex.Error) ERROR 23502 (not_null_violation) null value in column "inserted_at" of relation "users_games" violates not-null constraint
table: users_games
column: inserted_at
Which looks so close…it creates a transaction creates the game but when it is inserting into users_games it omits the timestamps.
What am I missing?
It looks like building the query
INSERT INTO "users_games" ("user_id","game_id") VALUES ($1,$2) [1, 6]
should know there are timestamp fields that could be populated for this.
Should I sack the many to many and create an intermediate entity and and have two one_to_many relationships and do it manually?






















