Thanks guys, works like a charm.
Am I doing it right?
In my data I have static stuff (here: hobbies and jobs which can be referenced in a list or as a single atom, this works). Also I have data (here person) that has an integer ID. These objects may
- reference each other (here friends)
- reference other objects (say we’d have a pets field that references pets by
[Pet.id_t()]
I see how I could first load all pets and then check in a person-changeset-validation if the referenced pets exist. But I can’t do that with persons referencing other persons, because they may not be loaded yet. So I’d need a second run, right?
defmodule Person do
use TypedEctoSchema
@type id_t() :: non_neg_integer()
@primary_key false
typed_schema "person" do
field(:id, :integer)
field(:name, :string, null: false)
field(:age, :integer) :: non_neg_integer()
field(:job, EctoAtom) :: Job.id_t()
field(:hobbies, {:array, EctoAtom}) :: [Hobby.id_t()]
field(:friends, {:array, :integer}) :: [Person.id_t()]
end
end
defmodule EctoAtom do
use Ecto.Type
def type, do: :atom
def cast(data), do: {:ok, String.to_atom(data)}
def load(data), do: {:ok, String.to_atom(data)}
def dump(atom), do: {:ok, Atom.to_string(atom)}
end
defmodule Job do
@type id_t() :: :job_mechanic | :job_doc | :job_programmer
end
defmodule Hobby do
@type id_t() :: :hobby_painting | :hobby_freeclimbing | :hobby_stampcollecting
end
iex> data = %{id: 1, name: "Bob", age: "18", job: "job_programmer", friends: [2, 4711], hobbies: ["hobby_freeclimbing", "hobby_painting"]}
...
iex> p =
...> Ecto.Changeset.cast(%Person{}, data, Map.keys(data))
...> |> Ecto.Changeset.apply_changes()
%Person{
__meta__: #Ecto.Schema.Metadata<:built, "person">,
age: 18,
friends: [2, 4711],
hobbies: [:hobby_freeclimbing, :hobby_painting],
id: 1,
job: :job_programmer,
name: "Bob"
}
EDIT: I created a behaviour for the atom-types and I like it. I think I’ll use this.