#[Ecto] Cast and validate a map field

2 messages · Page 1 of 1 (latest)

vivid palm
#

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
Gist

GitHub Gist: instantly share code, notes, and snippets.

green ether
#

There's a library that makes this a lot easier if you don't want to try and do it yourself: https://github.com/mathieuprog/polymorphic_embed
The difference between the library and your code is that the "type" field is inside the embed, not the outer schema. If that's ok with you then I think this library does exactly what you want.