Waffle.Ecto.Schema (waffle_ecto v0.1.0)

Copy Markdown

Provides helpers for casting attachments in Ecto changesets.

Summary

Types

attachment_option()

@type attachment_option() :: {:allow_urls, boolean()} | {:allow_paths, boolean()}

changeset_params()

@type changeset_params() :: map() | nil | :invalid

permitted_fields()

@type permitted_fields() :: [atom()]

Functions

cast_attachments(changeset_or_data, params, allowed, options \\ [])

(macro)
@spec cast_attachments(
  Ecto.Changeset.t() | struct(),
  changeset_params(),
  permitted_fields(),
  [
    attachment_option()
  ]
) :: Ecto.Changeset.t() | struct()

Converts a payload into a schema change.

This function triggers Waffle's file processing and storage pipeline during type casting.

Type casting

When processing the payload, the function expects the payload to be an attachment.

An attachment can be represented in the following forms:

  • %Plug.Upload{}
  • %Waffle.File{}
  • %{filename: "name.jpg", binary: <<42>>}
  • %{filename: "name.jpg", path: "/path/to/file.jpg"} - requires allow_paths: true
  • %{filename: "name.jpg", path: "https://example.com/file.jpg"} - requires allow_paths: true or allow_urls: true
  • "/path/to/file.jpg" - requires allow_paths: true
  • "https://example.com/file.jpg" - requires allow_paths: true or allow_urls: true

Options

  • :allow_urls - defaults to false, fetches a remote file if the string matches ~r/^https?:\/\//
  • :allow_paths - defaults to false, accepts a local path as a source file, should never be used with untrusted input

Examples

cast_attachments(
  changeset,
  params,
  [:attachment],
  allow_urls: true,
  allow_paths: true
)

Cleaning up files after schema changes

When removing or updating files, you need to handle the cleanup after the schema update. In general, it's recommended to put file handling into a dedicated changeset function, as file handling happens during type casting.

old_avatar = user.avatar
old_scope = user

changeset =
  user
  |> Ecto.Changeset.change()
  |> cast_attachments(%{"avatar" => nil}, [:avatar])

case Repo.update(changeset) do
  {:ok, updated_user} ->
    if updated_user.avatar == nil && old_avatar do
      AvatarUploader.delete({old_avatar, old_scope})
    end

    {:ok, updated_user}

  {:error, changeset} ->
    {:error, changeset}
end

check_and_apply_scope(params, scope, options)

convert_params_to_binary(params)

do_apply_changes(changeset)