I’m trying to build a validation via Ecto.Changeset for dynamic data where the schema of the data is user-defined.
My example schema currently is:
[
%{
"label" => "Status of Ticket",
"id" => "status_of_ticket",
"type" => "select",
"options" => ["todo", "doing", "done", "cancelled"]
},
%{
"label" => "Component",
"id" => "component",
"type" => "text"
}
]
This can be created and changed by users. From this schema, I’m building an Ecto.Changeset with the validation rules in place:
defp to_changeset(schema, values, params \\ %{}) do
names = Enum.map(schema, &Map.get(&1, "id"))
types =
Enum.reduce(schema, %{}, fn %{"type" => type, "id" => id}, acc ->
case type do
"select" -> Map.put(acc, id, Select.storage_type()) # :string
"text" -> Map.put(acc, id, Text.storage_type()) # :string
_ -> raise "Unknown prop type #{type}"
end
end)
changeset = Ecto.Changeset.cast({values, types}, params, names)
Enum.reduce(schema, changeset, fn %{"type" => type} = prop, acc ->
case type do
"select" -> Select.changeset(acc, prop)
"text" -> Text.changeset(acc, prop)
_ -> raise "Type #{type} not known!"
end
end)
end
Here, schema
is the schema from above, values
is a map that holds the current values stored (as a JSON object in the Database) and params
is whatever has been changed in the form.
This fails with:
** (ArgumentError)
cast/3
expects a list of atom keys, got key: `“status_of_ticket”``
I have converted all keys in names
via String.to_atom/1
(against my better judgement), I get:
** (ArgumentError) unknown field
:status_of_ticket
given to cast. Either the field does not exist or it is a :through association (which are read-only). The known fields are: “component”, “status_of_ticket”
If I change the keys in types
to be atoms as well, I get:
** (FunctionClauseError) no function clause matching in Ecto.Changeset.validate_change/3
At that point I figured I had fought this system enough. Is there a way I can use Ecto for this specific use-case? Or am I better off building the (currently pretty simple) validation logic myself?