♻️ Trash
Simple soft deletes for Ecto
Installation
Trash is available on Hex.
This package can be installed by adding trash to your list of dependencies in
mix.exs:
def deps do
[
{:trash, "~> 0.1.0"}
]
endUsage
Check the documentation for complete details.
Trash helps manage soft-deleting Ecto.Schemas 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.Schemas. To start marking schemas as
trashable, first add the required trashable fields:
mix ecto.gen.migration add_trashable_to_postsdefmodule 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
endThen declare the fields on your schema. You can do this manually or use the
convenience functions in Trash.Schema:
defmodule MyApp.Posts.Post do
use Ecto.Schema
use Trash.Schema
schema "posts" do
field(:title, :string)
trashable_fields()
end
endNext, import Trash by using it in your MyApp.Repo.
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
use Trash.Repo, repo: __MODULE__
endThis 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.
# 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.
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:
allexists?getget!get_byget_by!oneone!
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.
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.
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.
git clone https://github.com/newaperio/trash.git
cd trash
mix deps.get
mix testLicense
Trash is Copyright © 2020 NewAperio. It is free software, and may be redistributed under the terms specified in the 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 development as part of our portfolio of services. Get in touch to see how our team can help you.