always gen ecto bool
This commit is contained in:
parent
5886f90029
commit
d4f19b7f4a
2 changed files with 53 additions and 7 deletions
15
.obsidian/workspace.json
vendored
15
.obsidian/workspace.json
vendored
|
@ -4,16 +4,16 @@
|
||||||
"type": "split",
|
"type": "split",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "a470dcfe9214cd52",
|
"id": "d16dcbc379f22118",
|
||||||
"type": "tabs",
|
"type": "tabs",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "73b3910cd9e889d3",
|
"id": "7212df225ebf759a",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "content/tils/asdf_direnv.md",
|
"file": "content/tils/always_generated_ecto_boolean.md",
|
||||||
"mode": "source",
|
"mode": "source",
|
||||||
"source": false
|
"source": false
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
"state": {
|
"state": {
|
||||||
"type": "backlink",
|
"type": "backlink",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "content/tils/asdf_direnv.md",
|
"file": "content/tils/always_generated_ecto_boolean.md",
|
||||||
"collapseAll": false,
|
"collapseAll": false,
|
||||||
"extraContext": false,
|
"extraContext": false,
|
||||||
"sortOrder": "alphabetical",
|
"sortOrder": "alphabetical",
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
"state": {
|
"state": {
|
||||||
"type": "file-properties",
|
"type": "file-properties",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "content/tils/asdf_direnv.md"
|
"file": "content/tils/always_generated_ecto_boolean.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,10 +132,12 @@
|
||||||
"markdown-importer:Open format converter": false
|
"markdown-importer:Open format converter": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "7f987208866df706",
|
"active": "7212df225ebf759a",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"content/tils/struct_pattern_matching.md",
|
"content/tils/struct_pattern_matching.md",
|
||||||
|
"content/tils/pdf_obsidian_at_page.md",
|
||||||
"content/tils/asdf_direnv.md",
|
"content/tils/asdf_direnv.md",
|
||||||
|
"content/tils/always_generated_ecto_boolean.md",
|
||||||
"_site/tils/asdf_direnv/index.html",
|
"_site/tils/asdf_direnv/index.html",
|
||||||
"_site/tils/asdf_direnv",
|
"_site/tils/asdf_direnv",
|
||||||
"_site/tils/Untitled/index.html",
|
"_site/tils/Untitled/index.html",
|
||||||
|
@ -168,7 +170,6 @@
|
||||||
"node_modules/markdown-it/node_modules/entities/readme.md",
|
"node_modules/markdown-it/node_modules/entities/readme.md",
|
||||||
"node_modules/liquidjs/node_modules/commander/Readme.md",
|
"node_modules/liquidjs/node_modules/commander/Readme.md",
|
||||||
"node_modules/minipass/node_modules/yallist/README.md",
|
"node_modules/minipass/node_modules/yallist/README.md",
|
||||||
"node_modules/minipass/README.md",
|
|
||||||
"_site/images/ultimate-dungeon-terrain-3.jpg",
|
"_site/images/ultimate-dungeon-terrain-3.jpg",
|
||||||
"_site/images/ultimate-dungeon-terrain-2.jpg",
|
"_site/images/ultimate-dungeon-terrain-2.jpg",
|
||||||
"_site/images/ultimate-dungeon-terrain-1.jpg",
|
"_site/images/ultimate-dungeon-terrain-1.jpg",
|
||||||
|
|
45
content/tils/always_generated_ecto_boolean.md
Normal file
45
content/tils/always_generated_ecto_boolean.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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()
|
||||||
|
```
|
Loading…
Add table
Reference in a new issue