marcin
Conditionally associate in a simple changeset() function
Hi! ![]()
I’d like to ask how you implement a super common functionality, for which I do not see any easy way in Ecto.Changeset
Lets say I have a Todo which belongs to a TodoList.
I get my auto-generated changeset() using phx.gen.schema which looks roughly like this:
def changeset(todo, attrs) do
todo
|> cast(attrs, [:title, :details, :done])
|> validate_required([:title])
end
Now I would like to also be able to pass the association into this function, so in my context API todos.ex I can do:
def move_todo_to_list(todo, todo_list) do
update_todo(todo, %{todo_list: todo_list})
end
obviouly in changeset() I cannot cast() the todo_list.
I need to call either put_assoc(), or change() - however, the association is not always given, I can also just have this usage from controller
def create_todo(params) do
%Todo{}
|> Todo.changeset(params)
|> Repo.insert()
end
where params would contain todo_list_id…
I do not want to do any conditional handling in my changeset, nor defp some multiclause helpers:
def changeset(todo, attrs) do
chset = todo
|> cast(attrs, [:title, :details, :done])
# OMG verbose!!!!
chset = if attrs[:todo_list] do
put_assoc(chset, attrs[:todo_list], todo_list)
else
chset
end
chset
|> validate_required([:title])
end
(note, some custom validations might use the association to figure out if an attribute is valid or not, so validate goes last).
I used to do kinda elegant:
def changeset(todo, attrs) do
assocs = Map.take(attrs, [:todo_list])
todo
|> cast(attrs, [:title, :details, :done])
|> change(assocs)
|> validate_required([:title])
end
But this will not work if attrs are sometimes keyed by strings (data from user), and sometimes by atoms (programmer controlled params from some module), and then when I add :todo_list key, i might end up with “cannot mix strings and atom keys” error from cast().
I bet this has to be solved somehow elegantly, this is such a common use case – but cannot come up with anything based on Ecto standard functions other then writing some custom helpers…
Most Liked
LostKobrakai
Tbh at a certain point it’s just not worth it trying to treat every possible scenario as “one and the same”. Ecto has useful APIs for all the usecases you mentioned. It’s however not going to be one API covering all of them at the same time.








