Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ash destroy action #666

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/lib/demo/helpdesk/ticket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Demo.Helpdesk.Ticket do
end

actions do
defaults [:read]
defaults [:read, :destroy]
end

attributes do
Expand Down
2 changes: 1 addition & 1 deletion demo/lib/demo_web/live/ticket_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule DemoWeb.TicketLive do
end

@impl Backpex.LiveResource
def can?(_assigns, action, _item) when action in [:index, :show], do: true
def can?(_assigns, action, _item) when action in [:index, :show, :delete], do: true
def can?(_assigns, _action, _item), do: false

@impl Backpex.LiveResource
Expand Down
13 changes: 11 additions & 2 deletions lib/backpex/adapters/ash.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,17 @@ if Code.ensure_loaded?(Ash) do
Deletes multiple items.
"""
@impl Backpex.Adapter
def delete_all(_items, _config) do
raise "not implemented yet"
def delete_all(items, live_resource) do
config = live_resource.config(:adapter_config)
primary_key = live_resource.config(:primary_key)

ids = Enum.map(items, &Map.fetch!(&1, primary_key))

config[:resource]
|> Ash.Query.filter(^Ash.Expr.ref(primary_key) in ^ids)
|> Ash.bulk_destroy!(:destroy, %{})

:ok
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we want to catch errors? Currently, the item action, e.g., Delete, uses try rescue and pattern matches on the error. I don't like this, as the item action itself should not figure out why the deletion failed. In the case of Ash, the bulk delete might contain errors (in the result struct), even if it did not raise an error. I suggest moving the error handling to the adapter. @pehbehbeh

end

@doc """
Expand Down
2 changes: 2 additions & 0 deletions lib/backpex/adapters/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ defmodule Backpex.Adapters.Ecto do
config[:schema]
|> where([i], field(i, ^primary_key) in ^Enum.map(items, &Map.get(&1, primary_key)))
|> config[:repo].delete_all()

:ok
end

@doc """
Expand Down
5 changes: 0 additions & 5 deletions lib/backpex/html/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,6 @@ defmodule Backpex.HTML.Resource do
"""
end

@doc """
Checks the given module if it has a `confirm/1` function exported or a list with fields.
"""
def has_modal?(module), do: function_exported?(module, :confirm, 1) or module.fields() != []

defp metric_toggle(assigns) do
visible = Backpex.Metric.metrics_visible?(assigns.metric_visibility, assigns.live_resource)

Expand Down
2 changes: 1 addition & 1 deletion lib/backpex/html/resource/resource_index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</.modal>
<% end %>

<%= if @action_to_confirm && has_modal?(@action_to_confirm.module) do %>
<%= if @action_to_confirm do %>
<.modal title={@action_to_confirm.module.label(assigns, nil)}>
<div class="px-5 py-3">
<%= @action_to_confirm.module.confirm(assigns) %>
Expand Down
2 changes: 1 addition & 1 deletion lib/backpex/item_actions/delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Backpex.ItemActions.Delete do
def handle(socket, items, _data) do
socket =
try do
{:ok, _items} = Resource.delete_all(items, socket.assigns.live_resource)
{:ok, items} = Resource.delete_all(items, socket.assigns.live_resource)

for item <- items do
socket.assigns.live_resource.on_item_deleted(socket, item)
Expand Down
22 changes: 21 additions & 1 deletion lib/backpex/item_actions/item_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ defmodule Backpex.ItemAction do
@callback fields() :: list()

@doc """
TODO: rename to `init_schema`
Flo0807 marked this conversation as resolved.
Show resolved Hide resolved

Initial change. The result will be passed to `c:changeset/3` in order to generate a changeset.

This function is optional and can be used to use changesets with schemas in item actions. If this function
Expand Down Expand Up @@ -111,10 +113,28 @@ defmodule Backpex.ItemAction do

@impl Backpex.ItemAction
def init_change(_assigns) do
types = Backpex.Field.changeset_types(fields())
types = fields() |> Backpex.Field.changeset_types()

{%{}, types}
end
end
end

@doc """
Checks whether item action has confirmation modal.
"""
def has_confirm_modal?(item_action) do
module = Map.fetch!(item_action, :module)

function_exported?(module, :confirm, 1)
end

@doc """
Checks whether item action has form.
"""
def has_form?(item_action) do
module = Map.fetch!(item_action, :module)

module.fields() != []
end
Comment on lines +123 to +139
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about moving this functions in the item action use block so we can call item_action.has_form?, but I guess we should stick with a simple function inside Backpex.ItemAction. What do you think? @pehbehbeh

end
29 changes: 20 additions & 9 deletions lib/backpex/live_components/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ defmodule Backpex.FormComponent do
end

def handle_event("validate", %{"change" => change, "_target" => target}, %{assigns: %{action_type: :item}} = socket) do
%{assigns: %{item_action_types: item_action_types, live_resource: live_resource, fields: fields} = assigns} = socket
%{assigns: %{init_schema: init_schema, fields: fields} = assigns} = socket

changeset_function = &assigns.action_to_confirm.module.changeset/3

target = Enum.at(target, 1)

Expand All @@ -81,9 +83,12 @@ defmodule Backpex.FormComponent do
|> drop_readonly_changes(fields, assigns)
|> put_upload_change(socket, :validate)

metadata = Resource.build_changeset_metadata(socket.assigns, target)

changeset =
item_action_types
|> Resource.change(change, fields, assigns, live_resource, target: target)
init_schema
|> changeset_function.(change, metadata)
|> Map.put(:action, :validate)
Comment on lines +88 to +90
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not sure where to place the code that calls the changeset function and if we should move it to another module 🤔 I also did not include special calls like before_changeset. We even state that we do not support advanced fields like HasMany in item actions in our docs (of course, we should change this in the future).


form = Phoenix.Component.to_form(changeset, as: :change)

Expand Down Expand Up @@ -332,19 +337,25 @@ defmodule Backpex.FormComponent do
%{
assigns:
%{
live_resource: live_resource,
selected_items: selected_items,
action_to_confirm: action_to_confirm,
return_to: return_to,
item_action_types: item_action_types,
fields: fields
} = assigns
} = socket

result =
item_action_types
|> Backpex.Resource.change(params, fields, assigns, live_resource)
|> Ecto.Changeset.apply_action(:insert)
if Backpex.ItemAction.has_form?(action_to_confirm) do
changeset_function = &action_to_confirm.module.changeset/3

metadata = Resource.build_changeset_metadata(assigns)

assigns.init_schema
|> changeset_function.(params, metadata)
|> Map.put(:action, :insert)
|> Ecto.Changeset.apply_action(:insert)
else
{:ok, %{}}
end

case result do
{:ok, data} ->
Expand Down
30 changes: 17 additions & 13 deletions lib/backpex/live_resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -708,29 +708,30 @@ defmodule Backpex.LiveResource do
action = socket.assigns.item_actions[key]
items = socket.assigns.selected_items

if has_modal?(action.module) do
if Backpex.ItemAction.has_confirm_modal?(action) do
open_action_confirm_modal(socket, action, key)
else
handle_item_action(socket, action, key, items)
end
end

defp open_action_confirm_modal(socket, action, key) do
init_change = action.module.init_change(socket.assigns)
changeset_function = &action.module.changeset/3
socket =
if Backpex.ItemAction.has_form?(action) do
changeset_function = &action.module.changeset/3
init_schema = action.module.init_change(socket.assigns)

metadata = Resource.build_changeset_metadata(socket.assigns)
metadata = Resource.build_changeset_metadata(socket.assigns)

changeset =
init_change
|> Ecto.Changeset.change()
|> changeset_function.(%{}, metadata)
changeset = changeset_function.(init_schema, %{}, metadata)

socket =
socket
|> assign(:item_action_types, init_change)
|> assign(:changeset_function, changeset_function)
|> assign(:changeset, changeset)
socket
|> assign(:init_schema, init_schema)
|> assign(:changeset, changeset)
else
socket
|> assign(:changeset, %{})
end
|> assign(:action_to_confirm, Map.put(action, :key, key))

{:noreply, socket}
Expand Down Expand Up @@ -891,6 +892,9 @@ defmodule Backpex.LiveResource do

select_all = length(updated_selected_items) == length(socket.assigns.items)

IO.inspect(updated_selected_items, label: :updated_selected_items)


socket =
socket
|> assign(:selected_items, updated_selected_items)
Expand Down
3 changes: 2 additions & 1 deletion lib/backpex/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ defmodule Backpex.Resource do
pubsub = live_resource.config(:pubsub)

case adapter.delete_all(items, live_resource) do
{_count_, nil} ->
:ok ->
Enum.each(items, fn item -> broadcast({:ok, item}, "deleted", pubsub) end)

{:ok, items}

_error ->
Expand Down
Loading