lib/honu/attachments/attachment.ex

defmodule Honu.Attachments.Attachment do
  import Ecto.Changeset
  import Ecto.Query, warn: false

  alias Honu.Attachments.AttachmentMap
  alias Honu.Attachments.Blob

  def attachments_changeset(changeset, attrs, attachments_names)
      when is_list(attachments_names) do
    if convert?(attrs) do
      Enum.reduce(attachments_names, changeset, fn {name, func}, cset ->
        attachment_changeset(cset, attrs, {name, func})
      end)
    else
      Enum.reduce(attachments_names, changeset, fn {name, func}, cset ->
        attachment_changeset(cset, attrs, {Atom.to_string(name), func})
      end)
    end
  end

  defp attachment_changeset(changeset, attrs, {attachment_name, changeset_func})
       when is_function(changeset_func, 2) do
    if upload = attrs[attachment_name] do
      attachments = AttachmentMap.build(upload, attachment_name)

      changeset
      |> cast(Map.put(attrs, attachment_name, attachments), [])
      |> cast_assoc(attachment_name |> to_string() |> String.to_existing_atom(),
        with: changeset_func
      )
      |> prepare_changes(fn changeset ->
        attachment_name =
          attachment_name
          |> to_string()
          |> String.to_existing_atom()

        blob_ids = get_blob_ids(changeset, attachment_name)
        query = from(b in Blob, where: b.id in ^blob_ids)
        changeset.repo.update_all(query, set: [deleted_at: NaiveDateTime.utc_now()])

        changeset
      end)
    else
      changeset
    end
  end

  defp get_blob_ids(changeset, attachment_name) do
    case get_change(changeset, attachment_name) do
      changes when is_list(changes) ->
        changes
        |> Enum.filter(&(&1.action == :replace))
        |> Enum.map(& &1.data.blob_id)

      nil ->
        []

      _change ->
        get_has_one_blob_id(changeset, attachment_name)
    end
  end

  defp get_has_one_blob_id(changeset, attachment_name) do
    case Map.get(changeset.data, attachment_name) do
      nil -> []
      %Ecto.Association.NotLoaded{} -> []
      attachment -> [attachment.blob_id]
    end
  end

  defp convert?(attrs) do
    attrs
    |> Map.keys()
    |> List.first()
    |> is_atom()
  end
end