45 lines
No EOL
1.6 KiB
Markdown
45 lines
No EOL
1.6 KiB
Markdown
---
|
|
title: Always Generated Boolean tied to a date, smart deleted states in sql
|
|
date: 2023-09-25
|
|
---
|
|
It's a common scenario to not only know that something happened, but also _when_ it happened. Such is the common case with something like the following example of marking a user as `deleted?: true`. But, rather than relying on keeping two attributes up to date (`deleted` and `deleted_at`) we can rely on the database to have an automatically generated boolean to handle that for us.
|
|
|
|
The following example implements a reversable database migration, field, and function used to mark a user as deleted (at the current timestamp) and undeleted (or reactivated). With all three implemented we'll be able to tell if a user is deleted `user.deleted? == true` and toggle a user's deletion `delete_user(current_user)` and `undelete_user(deleted_user)`.
|
|
|
|
```elixir
|
|
# mix ecto.gen.migration add_deleted_to_user
|
|
defmodule App.Repo.Migrations.AddDeletedToUser do
|
|
use Ecto.Migration
|
|
|
|
def change do
|
|
alter table(:users) do
|
|
add(:deleted_at, :naive_datetime)
|
|
end
|
|
|
|
execute(
|
|
"alter table users add column deleted boolean generated always as (deleted_at is null) stored",
|
|
"alter table users drop column deleted"
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
```elixir
|
|
# App.Accounts.User
|
|
field(:deleted?, :boolean, read_after_writes: true, source: :deleted)
|
|
field(:deleted_at, :naive_datetime)
|
|
```
|
|
|
|
```elixir
|
|
def delete_user(%User{} = user),
|
|
do:
|
|
user
|
|
|> User.changeset(%{deleted_at: NaiveDateTime.utc_now()})
|
|
|> Repo.update()
|
|
|
|
def undelete_user(%User{} = user),
|
|
do:
|
|
user
|
|
|> User.changeset(%{deleted_at: nil})
|
|
|> Repo.update()
|
|
``` |