はじめに
この記事はElixirアドベントカレンダー2025シリーズ2の23日目の記事です。
今回はポストの削除、ユーザー削除を実装します
Post削除モーダル
現在開発はwxwidgetですが、iOS,Androidだとcondirmダイアログはでないのでモーダルでやるようにします
コンポーネントはこちら
対象idにハイフンが入っているとモーダルが開かなかったので注意が必要です
id -> post-[id]
post.id -> [id]
なのでpost.idの方を使えば大丈夫です
modalを開く場合は onclick={"modal_#{[ID]}.showModal()"}
閉じる場合は onclick={"modal_#{[ID]}.close()"}
を使います
<.table
id="posts"
rows={@streams.posts}
row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end}
>
<:col :let={{_id, post}} label="Text">{post.text}</:col>
<:action :let={{_id, post}}>
<div class="sr-only">
<.link navigate={~p"/posts/#{post}"}>Show</.link>
</div>
<.link navigate={~p"/posts/#{post}/edit"}>Edit</.link>
</:action>
<:action :let={{id, post}}>
- <.link
- phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")}
- data-confirm="Are you sure?"
- >
- Delete
- </.link>
+ <button onclick={"modal_#{post.id}.showModal()"}>Delete</button>
+ <dialog id={"modal_#{post.id}"} class="modal">
+ <div class="modal-box">
+ <h3 class="text-lg font-bold">Are you sure?</h3>
+ <div class="modal-action">
+ <form method="dialog">
+ <.button
+ onclick={"modal_#{post.id}.close()"}
+ phx-click={JS.push("delete", value: %{id: post.id})}
+ >
+ Delete
+ </.button>
+ <button class="btn">Cancel</button>
+ </form>
+ </div>
+ </div>
+ <form method="dialog" class="modal-backdrop">
+ <button>close</button>
+ </form>
+ </dialog>
</:action>
</.table>
ユーザー削除
メールアドレス登録してないので、一度ログアウトしたらログインはできないのでユーザー削除をおこないます
ポケットに入れてると誤タップ連射して、勝手に消えてしまう悲劇を防ぐためにテキストボックスに利用停止と入力したら削除が有効化するようにします
API
has_manyで on_delete: :delete_allをつけてユーザー削除されたら関連データを消すようにします
schema "users" do
field :email, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :confirmed_at, :utc_datetime
field :authenticated_at, :utc_datetime, virtual: true
+ has_many :posts, Blog.Posts.Post, on_delete: :delete_all
+ has_many :tokens, Blog.Accounts.UserToken, on_delete: :delete_all
timestamps(type: :utc_datetime)
end
削除する関数を追加します
def terminate_user(user) do
Repo.delete(user)
end
APIを追加します
def terminate(conn, _) do
user = conn.assigns.current_scope.user
case Accounts.terminate_user(user) do
{:ok, _user} ->
send_resp(conn, 200, "OK")
_ ->
send_resp(conn, 400, "Bad Request")
end
end
scope "/api/v1", BlogWeb.Api.V1 do
pipe_through [:api, :require_verify_header]
get "/users/status", UserController, :status
post "/refresh_token", UserController, :refresh_token
post "/logout", UserController, :logout
+ post "/terminate", UserController, :terminate
resources "/posts", PostController, except: [:new, :edit]
end
モバイル
APIリクエスト部分を作成します、成功したら端末にあるトークンも削除します
def terminate() do
{:ok, resp} = Req.post(Api.client(), url: "/terminate")
case resp.status do
200 ->
File.rm(BlogApp.config_dir() <> "/token")
:ok
_ ->
nil
end
end
liveview側を作ります
dialogのモーダルはliveviewの更新イベントで閉じられてしまうので、liveview側のopenフラグで制御します
また利用停止は、テキストボックスに利用停止と入力しないと押せないようになってます
def render(assigns) do
~H"""
<Layouts.app
name={:setting}
page_title="Settings"
flash={@flash}
current_scope={@current_scope}
>
<.form for={@email_form} id="email_form" phx-submit="update_email" phx-change="validate_email">
</.form>
<div class="divider" />
+ <.link
+ phx-click="open"
+ class="btn btn-error text-white normal-case text-xl"
+ >
+ 利用停止
+ </.link>
+
+ <dialog id="terminate" class="modal" open={@open}>
+ <div class="modal-box">
+ <h3 class="text-lg font-bold">BlogAppの利用停止</h3>
+ <p class="py-4">この操作は取り消せません</p>
+ <p>利用停止を行うと、すべてのデータはアクセスできなくなり、元に戻すことはできません。</p>
+ <br />
+
+ <form phx-change="change">
+ <label class="text-error" for="terminate">利用停止と入力してください</label>
+ <.input type="text" name="terminate" value={@terminate} />
+ </form>
+ <div class="modal-action">
+ <button
+ class="btn btn-error text-white"
+ phx-click="terminate"
+ disabled={!(@terminate == "利用停止")}
+ >
+ 利用停止
+ </button>
+ <form method="dialog">
+ <button phx-click="close" class="btn">キャンセル</button>
+ </form>
+ </div>
+ </div>
+ <form method="dialog" class="modal-backdrop">
+ <button phx-click="close">close</button>
+ </form>
+ </dialog>
</Layouts.app>
"""
end
@impl true
def mount(_params, _session, socket) do
user = socket.assigns.current_scope.user
email_changeset = Accounts.change_user_email(user, %{}, validate_unique: false)
socket =
socket
|> assign(:current_email, user.email)
|> assign(:email_form, to_form(email_changeset))
|> assign(:trigger_submit, false)
+ |> assign(:open, false)
+ |> assign(:terminate, "")
{:ok, socket}
end
@impl true
def handle_event("validate_email", params, socket) do
end
def handle_event("update_email", params, socket) do
end
+ def handle_event("open", _, socket), do: {:noreply, assign(socket, :open, true)}
+ def handle_event("close", _, socket) do
+ socket
+ |> assign(:open, false)
+ |> assign(:terminate, "")
+ |> then(&{:noreply, &1})
+ end
+ def handle_event("change", params, socket) do
+ socket
+ |> assign(:terminate, params["terminate"])
+ |> then(&{:noreply, &1})
+ end
+ def handle_event("terminate", _params, socket) do
+ case Accounts.terminate() do
+ :ok ->
+ socket
+ |> put_flash(:info, "BlogAppを利用停止しました")
+ |> redirect(to: ~p"/")
+ |> then(&{:noreply, &1})
+
+ _ ->
+ socket
+ |> put_flash(:error, "利用停止に失敗しました")
+ |> assign(:open, false)
+ |> assign(:terminate, "")
+ |> then(&{:noreply, &1})
+ end
end
動作確認
利用停止と入力されるまで、押せない状態
押すと消したユーザーのpostsとtokensが消されている
削除後トップページに戻される
最後に
本記事では、confirmモーダルをdaisyuiへの変更
キーワードを入力しないとユーザーが消せないようにしました
次はファイルアップロードを実装します

