LoginSignup
11
6

More than 3 years have passed since last update.

Phoenixで論理削除①:phx.gen.XXXXでの自動生成CRUDのdeleteをupdateに変更

Last updated at Posted at 2021-03-06

Elixir Digitalization Implementors/fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixのphx.gen.html/phx.gen.jsonで生成されるDBアクセッサにて「論理削除」、つまり、deleteでは無く、updateにより論理削除を立てる方法が、どこにも解説されていなくて、でも実務では頻出するコードなので、コラム化してみました

ちなみにLiveViewでも、本コラムと全く同じ方法で対応が可能です(LiveViewのSSRとの互換性対応が素晴らしいです)

内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします :wink:

:ocean::ocean::ocean: Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ :ocean::ocean::ocean:

fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
image.png

そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー:laughing:
https://qiita.com/advent-calendar/2020/elixir
image.png

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

ステップ1:DBアクセッサのdeleteを論理削除updateに改修

phx.gen.html/phx.gen.jsonで生成されるDBアクセッサは、下記ファイルとして生成されます

lib/【PJ名】/【コンテキスト名】.ex

コンテキスト名とは、phx.gen.XXXXの第1引数で指定される、スキーマ定義モジュールを内蔵するフォルダ名、およびRepo呼出モジュール名のことです

たとえば、basicというPhoenix PJで、phx.gen.html(もしくはphx.gen.json)によりpostsというテーブルを下記コマンドで作るとします(ここでは、DB作成/マイグレート/ルーティング追加は割愛)

> mix phx.gen.html Posts Post posts title:string description:text deleted_at:datetime

この場合、DBアクセッサは、下記の通り、生成されます

lib/basic/posts.ex
defmodule Basic.Posts do
  @moduledoc """
  The Posts context.
  """

  def update_post(%Post{} = post, attrs) do
    post
    |> Post.changeset(attrs)
    |> Repo.update()
  end

  def delete_post(%Post{} = post) do
    Repo.delete(post)
  end

これを、Repo.deleteから、論理削除日時(deleted_at)のRepo.updateに変更します

なお、秒より下の桁(millisecond、microsecond)が入っていると、エラーになるので、NaiveDateTime.truncateで削っておきます

lib/basic/posts.ex

  def delete_post(%Post{} = post) do
#    Repo.delete(post)  # comment-out here
    %Ecto.Changeset
    { 
      data: post, 
      changes: %{ deleted_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate( :second ) }, 
      valid?: true
    }
    |> Repo.update()
  end

update_postで行っている、Post.changeset(attrs) の結果と同じデータ構造を自己生成して渡す点がポイントです

なお、論理削除の解除は、下記のように作ります(呼び出し側は、ここでは割愛)

lib/basic/posts.ex

  def restore_delete_post(%Post{} = post) do
    %Ecto.Changeset
    { 
      data: post, 
      changes: %{ deleted_at: nil }, 
      valid?: true
    }
    |> Repo.update()
  end

ステップ2:UIの改修(phx.gen.html時のみ)

deleted_atは、通常のデータ追加/編集の際には不要なので、UI上から削除します

lib/basic_web/templates/post/form.html.ex
<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %><% # comment-out start %>
  <%= # label f, :deleted_at %>
  <%= # datetime_select f, :deleted_at %>
  <%= # error_tag f, :deleted_at %>
  <% # comment-out end %>

ステップ3:changesetの改修

changesetによる入力時バリデーションチェックにて、validate_requiredの必須チェックがdeleted_atにもかかっていますが、不要なので、これを解除します

lib/basic/posts/post.ex
defmodule Basic.Posts.Post do
  use Ecto.Schema
  import Ecto.Changeset

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :description, :deleted_at])
    |> validate_required([:title, :description])  # comment-out here  # , :deleted_at])
  end

ステップ4:動作確認

http://localhost:4000/posts にアクセスして、データを投入し、Deleteリンクをクリックします
image.png

deleted_atに現在時刻(UTC)が入り、論理削除済みとなりました(なお、現状の削除UIでは、一度論理削除すると、論理削除状態を解除して、元に戻すことができません)
image.png

なお、論理削除済みのデータをEditしても、deleted_atは更新されなくなっているため、論理削除状態は維持され、Edit側の改修は不要です(ただし、この後、解説する注意点はあるかもなので、引き続きご覧ください)

論理削除を設計・運用する際の注意点

論理削除の運用は、論理削除済みデータに纏わる下記ポイントで地味にハマる方も多いカテゴリなので、実務の案件で実装する際には、ご注意ください

  • 論理削除済みデータと同じ内容のデータ投入時、重複チェックを解除するのをお忘れなく
  • 論理削除済みなのに、機能が生き残っているケースを生まないように
    • 論理削除対象だけで無く、リレーション元の非表示化/無効化/従属削除も考慮すること
  • 一覧表示/個別表示は、アクセス権限でモードが変わる
    • 対ユーザ向けには見えないようにし、対管理者向けには見えるモードも用意する
    • Ectoレベルで消すか?表示上のみ消すか?

最後に

今回は、Phoenixのphx.gen.~で生成されるCRUDで「論理削除」を実装してみました

Elixir開発の実務でも良く出てくるテクニックなので、覚えておきましょう

また論理削除だけに限らず、Ecto.Repoにおけるupdate自体のカスタマイズや、独自タイミングでのupdate実施にも応用できるテクニックなので、Phoenix/Ectoの腕前を上げる参考にしてください

次回は、削除UIのトグル化を行います

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6