README.md

# ♻️  Trash

![](https://github.com/newaperio/trash/workflows/CI/badge.svg)

Simple soft deletes for Ecto

## Installation

Trash is available on [Hex](https://hex.pm/packages/trash).

This package can be installed by adding `trash` to your list of dependencies in
`mix.exs`:

```elixir
def deps do
  [
    {:trash, "~> 0.1.0"}
  ]
end
```

## Usage

Check the [documentation](https://hexdocs.pm/trash) for complete details.

Trash helps manage soft-deleting `Ecto.Schema`s by providing convenience
functions to update and query for discarded and kept records.

### Terminology

Trash uses a few terms throughout to indicate the state of a record. Here are
some quick definitions:

- **Soft-deletion**: removing a record by updating an attribute instead of
  issuing a SQL `DELETE`
- **Discarded**: a record that has been soft-deleted
- **Kept**: a record that has not been soft-deleted
- **Restore**: reverse a soft-deletion to keep a record

### Getting Started

Trash is opt-in on individual `Ecto.Schema`s. To start marking schemas as
trashable, first add the required trashable fields:

```bash
mix ecto.gen.migration add_trashable_to_posts
```

```elixir
defmodule MyApp.Repo.Migrations.AddTrashableToPosts do
  use Ecto.Migration

  def change do
    alter(table(:posts)) do
      add(:discarded_at, :utc_datetime)
    end

    create(index(:posts, :discarded_at))
  end
end
```

Then declare the fields on your schema. You can do this manually or use the
convenience functions in `Trash.Schema`:

```elixir
defmodule MyApp.Posts.Post do
  use Ecto.Schema
  use Trash.Schema

  schema "posts" do
    field(:title, :string)
    trashable_fields()
  end
end
```

Next, import `Trash` by using it in your `MyApp.Repo`.

```elixir
defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres

  use Trash.Repo, repo: __MODULE__
end
```

This generates shorthand functions with the repo implicitly passed. However,
it's not required to call `use`. If preferred you can call the functions
directly on `Trash.Repo` by passing the `Ecto.Repo` manually. It's a bit more
convenient with `use`, though.

```elixir
# Shorthand with `use`
MyRepo.all_discarded(Post)

# Long form without
MyRepo.all_discarded(Post, [], MyRepo)
```

### Soft-deleting and Restoring

The functions `discard` and `restore` will soft-delete and restore records,
respectively.

```elixir
alias MyApp.Posts
alias MyApp.Repo

post = Posts.get_last_post!

{:ok, post} = Repo.discard(post) # => %Post{discarded_at: %DateTime{}}
post = Repo.restore(post) # => %Post{discarded_at: nil}
```

These call out to the repo's `update` function. This means a SQL `UPDATE` has
been issued and the returned schema has updated trashable fields.

These functions also have bang versions, which unwrap the return tuple and raise
on error. Note: when passing a struct instead of a changeset, the bang versions
of these will never raise an error.

### Querying

Trash provides `discarded` and `kept` variations of the following `Ecto.Repo`
functions:

- `all`
- `exists?`
- `get`
- `get!`
- `get_by`
- `get_by!`
- `one`
- `one!`

The variations are postfixed with `discarded` and `kept` (with the exception of
`exists?` which is replaced by `discarded?` and `kept?`) and modify the
passed-in queryable to add a `WHERE` condition to only return discarded or kept
records.

Trash also provides helper `where` functions that can be used in conjunction
with `Ecto.Query`.

```elixir
import Ecto.Query
alias MyApp.Posts.Post

from(p in Post) |> Trash.Query.where_discarded() |> Repo.all()
```

There is also a function that merges in the trashable fields into the select
statement to always ensure they are returned. It also hydrates `discarded?` with
a computed `SQL` value.

```elixir
import Ecto.Query
alias MyApp.Posts.Post
alias MyApp.Repo

Post
|> Trash.Query.where_discarded()
|> Repo.all()
|> Trash.Query.select_trashable()
```

## Contributing

Contributions are welcome! To make changes, clone the repo, make sure tests
pass, and then open a PR on GitHub.

```console
git clone https://github.com/newaperio/trash.git
cd trash
mix deps.get
mix test
```

## License

Trash is Copyright © 2020 NewAperio. It is free software, and may be
redistributed under the terms specified in the [LICENSE](/LICENSE) file.

## About NewAperio

Trash is built by NewAperio, LLC.

NewAperio is a web and mobile design and development studio. We offer [expert
Elixir and Phoenix][services] development as part of our portfolio of services.
[Get in touch][contact] to see how our team can help you.

[services]: https://newaperio.com/services#elixir?utm_source=github
[contact]: https://newaperio.com/contact?utm_source=github