Skip to content

Instantly share code, notes, and snippets.

@YannickFricke
Created December 23, 2023 18:33
Show Gist options
  • Save YannickFricke/a1e224e2888ca3344a5fb55e1350a5d2 to your computer and use it in GitHub Desktop.
Save YannickFricke/a1e224e2888ca3344a5fb55e1350a5d2 to your computer and use it in GitHub Desktop.
Ecto CMS block data casting
defmodule Cms.ContentBlock do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
alias Cms.Blocks.TextBlock
@supported_block_types [
TextBlock.type()
]
@type t() :: %__MODULE__{
type: String.t(),
block_data: map()
}
embedded_schema do
field(:type, :string)
field(:block_data, :map)
end
@spec changeset(
content_block_or_changeset :: Ecto.Changeset.t(t()),
params :: map()
) :: Ecto.Changeset.t(t())
def changeset(content_block_or_changeset, params) do
content_block_or_changeset
|> cast(params, [:type, :block_data])
|> validate_required([:type, :block_data])
|> validate_inclusion(:type, @supported_block_types)
|> validate_block_data()
end
@spec base_types() :: map()
def base_types, do: %{id: :string, classes: {:array, :string}}
@spec extend_base_types(new_types :: map()) :: map()
def extend_base_types(new_types), do: Map.merge(base_types(), new_types)
@spec validate_base_types(
changeset :: Ecto.Changeset.t(),
params :: map()
) :: Ecto.Changeset.t()
def validate_base_types(changeset, params) do
cast(changeset, params, Map.keys(base_types()))
end
@spec validate_block_data(changeset :: Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp validate_block_data(%Ecto.Changeset{valid?: false} = changeset), do: changeset
defp validate_block_data(%Ecto.Changeset{data: changeset_data} = changeset) do
existing_data = Map.get(changeset_data, :block_data) || %{}
block_data = get_field(changeset, :block_data, %{})
block_data_changeset =
case get_field(changeset, :type) do
"text" ->
TextBlock.changeset(existing_data, block_data)
end
if block_data_changeset.valid? do
updated_changes_map =
block_data_changeset.changes
|> Enum.map(fn {key, value} ->
{Atom.to_string(key), value}
end)
|> Map.new()
new_block_data =
Map.merge(
block_data_changeset.data,
updated_changes_map
)
put_change(changeset, :block_data, new_block_data)
else
add_error(changeset, :block_data, "invalid block data")
end
end
end
defmodule Cms.Page do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
alias Cms.ContentBlock
@type t() :: %__MODULE__{
title: String.t(),
path: String.t(),
content: list(ContentBlock.t())
}
schema "cms_page" do
field(:title, :string)
field(:path, :string)
embeds_many(:content, ContentBlock, on_replace: :delete)
end
@spec changeset(
page_or_changeset :: Ecto.Changeset.t(t()),
params :: map()
) :: Ecto.Changeset.t(t())
def changeset(page_or_changeset, params) do
page_or_changeset
|> cast(params, [:title, :path])
|> cast_embed(:content, required: true)
|> validate_required([:title, :path])
end
end
defmodule Cms.Blocks.TextBlock do
@moduledoc false
import Ecto.Changeset
alias Cms.ContentBlock
@type t() :: %__MODULE__{
id: String.t() | nil,
classes: list(String.t()),
content: String.t()
}
defstruct id: nil, classes: [], content: ""
def type, do: "text"
@spec changeset(existing_data :: map(), params :: map()) :: Ecto.Changeset.t(t())
def changeset(existing_data, params) do
types =
%{
content: :string
}
# ContentBlock.extend_base_types()
{existing_data, types}
|> cast(params, [:content])
|> ContentBlock.validate_base_types(params)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment