Best practice to use put_assoc/3 with multiple has_many associations

I have (football) Teams and Games :

schema "games" do
    field :away_team_score, :integer
    field :competition, :string
    field :game_datetime, :utc_datetime
    field :home_team_score, :integer
    field :round, :string
    belongs_to :home_team, LvdnApi.Fixtures.Team
    belongs_to :away_team, LvdnApi.Fixtures.Team
    timestamps()
  end

  schema "teams" do
    field :name, :string
    has_many :games, LvdnApi.Fixtures.Game
    timestamps()
  end

I need to use put_assoc to persist the association in my DB when I create a game. I get the attributes for a new game from a form in which I get the ids of home_team and away_team.

home_team and away_team can’t be null nor empty; I need to make sure of this when both creating and updating any record.

But I don’t really get how to do this: where should I use put_assoc/3 ? in the changeset ? in my change_game method ? When do I retrieve the Team with their id ?

also the fact that I have two different fields of type Team makes me even more confused. How can I make sure each associated team will be persisted in the right field ?

If your team already exist when creating your team, then you don’t use put_assoc at all. Populate :home_team_id and :away_team_id and it should work.

2 Likes

Yep. The database will complain if there’s no record to be found by the given id. You might want to use assoc_constraint though if you want nice errors in your changeset for those cases, where this happens.

OK thank you :slight_smile:

But what is put_assoc for then (e.g. in opposition to cast_assoc) ? In the book Programming Phoenix , Chris McChord uses put_assoc even though the user associated the video he is creating already exists.

So put_assoc and cast_assoc do result in basically the same thing. They add associations to a changeset, which are also saved besides the root schema. This is especially useful from the “has_one/has_many” side of things, where you not only save the schema, but must also save/update the linked association, because it has the foreign key field. The other use case is if you have actual changes to the schema and its associations at the same time.

The difference between those two functions is that one is casting incoming data (equivalent to cast/4), while put_assoc is more like put_change, where incoming data is not cast into a runtime format, but simply accepted as is.

1 Like

OK I see, but if I use assoc_constraint, how do I make sure the team ID I got exists in the DB ?

You don’t. The db does. If you setup the column as a foreign key the db will complain if you try to insert an id, which does not exist on the other table. assoc_constraint just gives you nice changeset errors instead of raised errors if the db does indeed complain.

2 Likes

oh that is great ! Thank you very much @LostKobrakai :slight_smile:

Actually I have one last question: how do I use assoc_contraint as I have two fields that belongs_to the same model ?

def changeset(game, attrs) do
    game
    |> cast(attrs, [:home_team_score, :away_team_score, :competition, :round, :game_datetime, :home_team, :away_team])
    |> validate_required([:competition, :round, :game_datetime, :home_team, :away_team])
    |> assoc_constraint(:team)
  end

this way will it check for both home_team and away_team ?

Set it up for both fields. It’s just a detail that both fields happen to link to the same foreign table.

1 Like