Packages

File upload extension for Ecto

Current section

Files

Jump to
ectophile lib ectophile.ex
Raw

lib/ectophile.ex

defmodule Ectophile do
defmacro __using__(_) do
quote do
import unquote(__MODULE__), only: [attachment_fields: 1]
@before_compile Ectophile
Module.register_attribute(__MODULE__, :ectophile_data, accumulate: true)
end
end
defmacro attachment_fields(name, opts \\ []) do
quote do
name = unquote(name)
upload_field = :"#{name}_upload"
upload_path = unquote(opts)[:upload_path] || "#{name}/uploads"
ectophile_data = %{
filefield: name,
upload: upload_field,
upload_path: upload_path
}
Ecto.Schema.field(name, Ectophile.Type)
Ecto.Schema.field(upload_field, :any, virtual: true)
@ectophile_data ectophile_data
end
end
import Ecto.Changeset
def put_file(changeset, file_data) do
%{upload_path: upload_path} = file_data
if upload = get_change(changeset, file_data.upload) do
%{path: tmp_path, filename: filename} = upload
file_id = generate_file_id()
filepath = priv_path(upload_path, file_id, filename)
copy_files(tmp_path, upload_path, file_id, filename)
map = %{filename: filename, filepath: "/" <> filepath}
changeset
|> put_change(file_data.filefield, map)
else
changeset
end
end
def rm_file(%{model: model} = changeset, %{filefield: filefield}) do
new_filepath = get_change(changeset, filefield)
old_filepath = Map.get(model, filefield)[:filepath]
if new_filepath && old_filepath do
rm_files(old_filepath)
end
changeset
end
def rm_file_on_delete(%{model: model} = changeset, %{filefield: filefield}) do
old_filepath = Map.get(model, filefield)[:filepath]
if old_filepath do
rm_files(old_filepath)
end
changeset
end
if Mix.Project.config[:build_embedded] do
defp copy_files(tmp_path, upload_path, file_id, filename) do
File.cp!(tmp_path, priv_path(upload_path, file_id, filename))
File.cp!(tmp_path, build_priv_path(upload_path, file_id, filename))
end
defp rm_files(old_filepath) do
File.rm!("." <> old_filepath)
File.rm!(build_priv_path(old_filepath))
end
else
defp copy_files(tmp_path, upload_path, file_id, filename) do
File.cp!(tmp_path, priv_path(upload_path, file_id, filename))
end
defp rm_files(old_filepath) do
File.rm!("." <> old_filepath)
end
end
defp generate_file_id() do
:crypto.strong_rand_bytes(30) |> Base.encode16(case: :lower) |> binary_part(0,30)
end
def priv_path(upload_path) do
Path.join(["priv/static", upload_path])
end
defp priv_path(upload_path, file_id, filename) do
file_id = file_id <> Path.extname(filename)
Path.join(["priv/static", upload_path, file_id])
end
def build_priv_path(filepath) do
Application.app_dir(otp_app(), filepath)
end
def build_priv_path(upload_path, file_id, filename) do
file_id = file_id <> Path.extname(filename)
Application.app_dir(otp_app(), Path.join(["priv/static", upload_path, file_id]))
end
defp otp_app do
Application.get_env(:ectophile, :otp_app) || raise ":otp_app key required for :ectophile env"
end
defmacro __before_compile__(env) do
ectophile_data = Module.get_attribute(env.module, :ectophile_data)
callbacks =
for file_data <- ectophile_data do
file_data = Macro.escape(file_data)
quote do
before_insert Ectophile, :put_file, [unquote(file_data)]
before_update Ectophile, :put_file, [unquote(file_data)]
before_update Ectophile, :rm_file, [unquote(file_data)]
after_delete Ectophile, :rm_file_on_delete, [unquote(file_data)]
end
end
helpers =
quote do
def __ectophile_data__ do
unquote(Macro.escape(ectophile_data))
end
end
quote do
unquote(callbacks)
unquote(helpers)
end
end
end