Packages

ActiveStorage-like file storage for Phoenix. All things file uploads for your Phoenix app.

Current section

Files

Jump to
phoenix_contrib_storage lib storage attachment.ex
Raw

lib/storage/attachment.ex

defmodule Storage.Attachment do
@moduledoc """
Provides macros for adding file attachments to Ecto schemas.
This module allows you to easily add file attachment functionality
to your Ecto schemas, similar to Rails ActiveStorage.
## Usage
defmodule MyApp.User do
use Ecto.Schema
use Storage.Attachment
schema "users" do
field :name, :string
has_one_attached :avatar
has_many_attached :documents
end
end
This will add virtual fields and helper functions for managing attachments.
"""
defmacro __using__(_opts) do
quote do
import Storage.Attachment, only: [has_one_attached: 1, has_many_attached: 1]
@before_compile Storage.Attachment
end
end
defmacro __before_compile__(_env) do
quote do
@doc """
Attaches a file to this record.
"""
def attach(record, attachment_name, blob_or_blobs) do
Storage.Attachment.attach(record, attachment_name, blob_or_blobs)
end
@doc """
Detaches files from this record.
"""
def detach(record, attachment_name) do
Storage.Attachment.detach(record, attachment_name)
end
@doc """
Purges attachments and their blobs from this record.
"""
def purge_attached(record, attachment_name) do
Storage.Attachment.purge_attached(record, attachment_name)
end
end
end
@doc """
Defines a has_one_attached relationship.
"""
defmacro has_one_attached(name) do
quote do
field unquote(:"#{name}_attachment"), :any, virtual: true
field unquote(:"#{name}_blob"), :any, virtual: true
def unquote(:"#{name}_attached?")(%__MODULE__{} = record) do
Storage.Attachment.attached?(record, unquote(name))
end
def unquote(:"attach_#{name}")(%__MODULE__{} = record, blob) do
Storage.Attachment.attach_one(record, unquote(name), blob)
end
def unquote(:"detach_#{name}")(%__MODULE__{} = record) do
Storage.Attachment.detach_one(record, unquote(name))
end
def unquote(:"#{name}")(%__MODULE__{} = record) do
Storage.Attachment.get_one(record, unquote(name))
end
end
end
@doc """
Defines a has_many_attached relationship.
"""
defmacro has_many_attached(name) do
quote do
field unquote(:"#{name}_attachments"), {:array, :any}, virtual: true, default: []
field unquote(:"#{name}_blobs"), {:array, :any}, virtual: true, default: []
def unquote(:"#{name}_attached?")(%__MODULE__{} = record) do
Storage.Attachment.attached?(record, unquote(name))
end
def unquote(:"attach_#{name}")(%__MODULE__{} = record, blobs) when is_list(blobs) do
Storage.Attachment.attach_many(record, unquote(name), blobs)
end
def unquote(:"attach_#{name}")(%__MODULE__{} = record, blob) do
Storage.Attachment.attach_many(record, unquote(name), [blob])
end
def unquote(:"detach_#{name}")(%__MODULE__{} = record) do
Storage.Attachment.detach_many(record, unquote(name))
end
def unquote(:"#{name}")(%__MODULE__{} = record) do
Storage.Attachment.get_many(record, unquote(name))
end
end
end
# Implementation functions
@doc """
Checks if a record has any attachments for the given name.
"""
def attached?(record, name) do
case Storage.AttachmentSchema.find_for_record(record, name) do
nil -> false
_attachment -> true
end
end
@doc """
Attaches a single blob to a record.
"""
def attach_one(record, name, blob) do
# First detach any existing attachment
detach_one(record, name)
# Create new attachment
Storage.AttachmentSchema.create!(record, name, blob)
record
end
@doc """
Attaches multiple blobs to a record.
"""
def attach_many(record, name, blobs) when is_list(blobs) do
Enum.each(blobs, fn blob ->
Storage.AttachmentSchema.create!(record, name, blob)
end)
record
end
@doc """
Detaches a single attachment without deleting the blob.
"""
def detach_one(record, name) do
case Storage.AttachmentSchema.find_for_record(record, name) do
nil -> :ok
attachment -> Storage.AttachmentSchema.detach!(attachment)
end
end
@doc """
Detaches all attachments for a given name without deleting the blobs.
"""
def detach_many(record, name) do
attachments = Storage.AttachmentSchema.all_for_record(record, name)
Enum.each(attachments, fn attachment ->
Storage.AttachmentSchema.detach!(attachment)
end)
end
@doc """
Gets a single attachment for a record.
"""
def get_one(record, name) do
case Storage.AttachmentSchema.find_for_record(record, name) do
nil -> nil
attachment -> attachment.blob
end
end
@doc """
Gets all attachments for a record.
"""
def get_many(record, name) do
attachments = Storage.AttachmentSchema.all_for_record(record, name)
Enum.map(attachments, & &1.blob)
end
@doc """
Purges all attachments and their blobs for a given name.
"""
def purge_attached(record, name) do
attachments = Storage.AttachmentSchema.all_for_record(record, name)
Enum.each(attachments, fn attachment ->
Storage.AttachmentSchema.purge!(attachment)
end)
end
end