Created
December 23, 2023 18:33
-
-
Save YannickFricke/a1e224e2888ca3344a5fb55e1350a5d2 to your computer and use it in GitHub Desktop.
Ecto CMS block data casting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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