lib/elph/contents/content.ex

defmodule Elph.Contents.Content do
  @moduledoc """
  Content is the abstract representation of the datastructure the user is
  sending and receiving from the frontends. It is also the "base class" for
  every other content type. Content subtype changesets are called from here.
  """

  use Ecto.Schema
  import Ecto.Changeset

  def repo, do: Application.get_env(:elph, :repo)
  def types, do: Application.get_env(:elph, :types, Elph.Contents.DefaultTypes)

  @primary_key {:id, :id, autogenerate: true}
  embedded_schema do
    field :name, :string
    field :shared, :boolean, default: false
    field :type, :string

    embeds_many :children, __MODULE__, on_replace: :delete
    field :subtype_changeset_map, :map
  end

  def changeset(content, %{__struct__: _} = attrs) do
    changeset(content, Map.from_struct(attrs))
  end

  def changeset(content, attrs) do
    content
    |> cast_and_validate_basics(attrs)
    |> cast_subtype(attrs)
    |> cast_embed(:children)
  end

  def cast_and_validate_basics(content, attrs) do
    content
    |> cast(attrs, [:id, :name, :shared, :type])
    |> validate_required([:type])
    |> validate_has_name_if_shared()
  end

  def validate_has_name_if_shared(changeset) do
    if get_field(changeset, :shared) == true and get_field(changeset, :name) == nil do
      add_error(changeset, :name, "required if shared")
    else
      changeset
    end
  end

  def cast_subtype(changeset, attrs) do
    type = get_field(changeset, :type)

    module = types().get_module(type)

    changeset =
      case module do
        nil ->
          add_error(changeset, :type, "is invalid")

        _ ->
          struct =
            case Map.get(attrs, "id") || Map.get(attrs, :id) do
              nil ->
                module.__struct__

              id ->
                repo().get!(module, id)
            end

          case subtype_changeset = module.changeset(struct, attrs) do
            %{valid?: true} ->
              subtype_changeset_map = Map.from_struct(subtype_changeset)
              put_change(changeset, :subtype_changeset_map, subtype_changeset_map)

            %{valid?: false} ->
              %{changeset | errors: changeset.errors ++ subtype_changeset.errors, valid?: false}
          end
      end

    changeset
  end
end