Packages
phoenix_contrib_storage
0.1.0
ActiveStorage-like file storage for Phoenix. All things file uploads for your Phoenix app.
Current section
Files
Jump to
Current section
Files
lib/storage/attachment_schema.ex
defmodule Storage.AttachmentSchema do
@moduledoc """
Schema for linking records to blobs.
An Attachment represents the relationship between a record (like User)
and a Blob (the actual file). This allows for polymorphic associations
where any record can have attachments.
"""
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "storage_attachments" do
field :name, :string
field :record_type, :string
field :record_id, :binary_id
belongs_to :blob, Storage.Blob, foreign_key: :blob_id, type: :binary_id
timestamps()
end
@doc false
def changeset(attachment, attrs) do
attachment
|> cast(attrs, [:name, :record_type, :record_id, :blob_id])
|> validate_required([:name, :record_type, :record_id, :blob_id])
|> validate_length(:name, max: 255)
|> validate_length(:record_type, max: 255)
|> foreign_key_constraint(:blob_id)
|> unique_constraint([:record_type, :record_id, :name, :blob_id])
end
@doc """
Creates an attachment for the given record and blob.
"""
def create!(record, name, blob) do
attrs = %{
name: to_string(name),
record_type: record.__struct__ |> Module.split() |> List.last(),
record_id: record.id,
blob_id: blob.id
}
%__MODULE__{}
|> changeset(attrs)
|> Storage.Repo.insert!()
end
@doc """
Finds attachments for a given record and name.
"""
def for_record(record, name) do
record_type = record.__struct__ |> Module.split() |> List.last()
from a in __MODULE__,
where: a.record_type == ^record_type and a.record_id == ^record.id and a.name == ^to_string(name),
preload: [:blob]
end
@doc """
Finds a single attachment for a given record and name.
"""
def find_for_record(record, name) do
for_record(record, name) |> Storage.Repo.one()
end
@doc """
Finds all attachments for a given record and name.
"""
def all_for_record(record, name) do
for_record(record, name) |> Storage.Repo.all()
end
@doc """
Purges an attachment and its associated blob if no other attachments reference it.
"""
def purge!(%__MODULE__{} = attachment) do
blob = Storage.Repo.preload(attachment, :blob).blob
Storage.Repo.delete!(attachment)
# Check if this blob has other attachments
other_attachments =
from a in __MODULE__,
where: a.blob_id == ^blob.id,
select: count(a.id)
case Storage.Repo.one(other_attachments) do
0 ->
# No other attachments, safe to delete the blob
Storage.Services.delete_file(blob.service_name, blob.key)
Storage.Repo.delete!(blob)
_ ->
# Other attachments exist, keep the blob
:ok
end
end
@doc """
Detaches an attachment without deleting the underlying blob.
"""
def detach!(%__MODULE__{} = attachment) do
Storage.Repo.delete!(attachment)
end
end