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…