I have a Player
model and a Match
model. Matches belong_to
a player and a player has_many
matches.
A player is created manually, but matches are pulled asynchronously from an external API. To build the relationship, my first impulse was to do something like:
player = get_player_from_somewhere()
for match <- match_data do
attrs = %{
length: match["length"],
score: match["score"],
player: player
}
Matches.create_match(attrs)
end
And inside Match.ex
, I had
def changeset(match, attrs) do
match
|> cast(attr, [...])
|> cast_assoc(:player)
end
Now this fails because it actually expects player
to be a map of player attributes, not an already constructed object (as per the docs).
I could alter my code to be:
player = get_player_from_somewhere()
for match <- match_data do
attrs = %{
length: match["length"]
score: match["score"]
player: %{
id: player.id
}
}
Matches.create_match(attrs)
end
But that seems like a bit of a hack, might as well just set player_id
.
I come from a Rails background, where I felt like I would be calling something more like Matches.create_match_for_player(attr, player)
if I were dealing with nested resource.
I settled on altering my Matches.ex
context with
def create_match_for_player(attrs \\ %{}, player) do
%Match{}
|> Match.changeset(attrs, player)
|> Repo.insert()
end
and Match.ex to
def changeset(match, attrs, player) do
match
|> cast(attr, [...])
|> put_assoc(player)
end
Is this the correct way of laying out this code? I could also go player |> build_assoc(:match, attrs)
. I’m unsure which is better.
Alternatively I read this method of using foreign_key_constraint and basically ignoring the “fancy” parts of relationships.
Part of me think’s this is throwing out a lot of cool stuff, but Ecto also doesn’t do things like preloading automatically, so maybe working with just id’s until you want to operate on an object is more natural.
I do like the idea of simplifying (?) the changeset code by removing any association casts and relying on the DB to alert errors. It seems almost more explicit.