Problem: I have a map in a schema, that map should only contain certain keys (and preferably be validated too) based on a column value.
I have attached some working code for the cast part, my assumption is that I should use a custom validation after the casts (I don't think I can somehow merge two complete changesets here?).
However, this doesn't feel very Elixir to me. I have looked at embedded schemas but they seem to be too static for this use case as they need to be defined in "isolation" (can't be based on the type since it's unknown at the time of schema definition).
My reason for this structure is that everything in data is just for the client to consume, it has no value as stand alone columns and considering n number of types with y number of possible keys the rows would be very sparse with a lot of empty values.
Gist: https://gist.github.com/tozz/e436fc8701dbeec3e8479da9de14c123 (copied below)
defmodule SlideContent do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "slide_contents" do
field :section, :string
field :type, :string
field :data, :map, default: %{}
field :deleted, :boolean
timestamps()
end
defp data_field_types(type) do
case type do
"text" -> %{header: :string, hasSubtext: :boolean, subtext: :string}
"image" -> %{url: :string, crop: :string}
"audio" -> %{url: :string, format: :string}
end
end
def changeset_update(slide_content, attrs) do
types = data_field_types(slide_content.type)
changeset =
{%__MODULE__{}, types}
|> cast(slide_content.data, Map.keys(types))
slide_content
|> cast(attrs, [:section, :type, :data, :deleted])
|> change(%{data: changeset.changes})
|> validate_required([:section, :type, :data])
end
end