From 8927460f77f495a62eba4c8cc82fde75508d4a87 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 5 Apr 2024 15:05:01 -0400 Subject: [PATCH] fix: more improvements to resource matching in `can?` --- lib/ash/can.ex | 174 ++++++++++++++++++++++++++++++++++--------------- mix.exs | 9 ++- 2 files changed, 125 insertions(+), 58 deletions(-) diff --git a/lib/ash/can.ex b/lib/ash/can.ex index dce15fa5..a6506c1b 100644 --- a/lib/ash/can.ex +++ b/lib/ash/can.ex @@ -23,45 +23,31 @@ defmodule Ash.Can do opts = Keyword.put_new(opts, :filter_with, :filter) {resource, action_or_query_or_changeset, input} = - case action_or_query_or_changeset do - %Ash.Query{} = query -> - {query.resource, query, nil} - - %Ash.Changeset{} = changeset -> - {changeset.resource, changeset, nil} - - %Ash.ActionInput{} = input -> - {input.resource, input, nil} - - {resource, %struct{}} = action - when struct in [ - Ash.Resource.Actions.Create, - Ash.Resource.Actions.Read, - Ash.Resource.Actions.Update, - Ash.Resource.Actions.Destroy, - Ash.Resource.Actions.Action - ] -> - {resource, action, %{}} - - {resource, name} when is_atom(name) -> - {resource, Ash.Resource.Info.action(resource, name), %{}} - - {resource, %struct{}, input} = action - when struct in [ - Ash.Resource.Actions.Create, - Ash.Resource.Actions.Read, - Ash.Resource.Actions.Update, - Ash.Resource.Actions.Destroy, - Ash.Resource.Actions.Action - ] -> - {resource, action, input} - - {resource, name, input} when is_atom(name) -> - {resource, Ash.Resource.Info.action(resource, name), input} - end + resource_subject_input(action_or_query_or_changeset, domain, opts) subject = case action_or_query_or_changeset do + %Ash.ActionInput{} = action_input -> + if opts[:tenant] do + Ash.ActionInput.set_tenant(action_input, opts[:tenant]) + else + action_input + end + + %Ash.Query{} = query -> + if opts[:tenant] do + Ash.Query.set_tenant(query, opts[:tenant]) + else + query + end + + %Ash.Changeset{} = changeset -> + if opts[:tenant] do + Ash.Changeset.set_tenant(changeset, opts[:tenant]) + else + changeset + end + %{type: :update, name: name} -> if opts[:data] do Ash.Changeset.for_update(opts[:data], name, input, @@ -95,23 +81,6 @@ defmodule Ash.Can do %{type: :action, name: name} -> Ash.ActionInput.for_action(resource, name, input, actor: actor) - %Ash.ActionInput{} = action_input -> - action_input - - %Ash.Query{} = query -> - if opts[:tenant] do - Ash.Query.set_tenant(query, opts[:tenant]) - else - query - end - - %Ash.Changeset{} = changeset -> - if opts[:tenant] do - Ash.Changeset.set_tenant(changeset, opts[:tenant]) - else - changeset - end - _ -> raise ArgumentError, message: "Invalid action/query/changeset \"#{inspect(action_or_query_or_changeset)}\"" @@ -130,6 +99,105 @@ defmodule Ash.Can do end end + defp resource_subject_input(action_or_query_or_changeset, domain, opts) do + case action_or_query_or_changeset do + {resource, name} when is_atom(name) and is_atom(resource) -> + resource_subject_input( + {resource, Ash.Resource.Info.action(resource, name), %{}}, + domain, + opts + ) + + {resource, name, input} when is_atom(name) and is_atom(resource) -> + resource_subject_input( + {resource, Ash.Resource.Info.action(resource, name), input}, + domain, + opts + ) + + {%resource{} = record, name} when is_atom(name) and is_atom(resource) -> + resource_subject_input( + {record, Ash.Resource.Info.action(resource, name), %{}}, + domain, + opts + ) + + {%resource{} = record, name, input} when is_atom(name) and is_atom(resource) -> + resource_subject_input( + {record, Ash.Resource.Info.action(resource, name), input}, + domain, + opts + ) + + %Ash.Query{} = query -> + {query.resource, query, nil} + + %Ash.Changeset{} = changeset -> + {changeset.resource, changeset, nil} + + %Ash.ActionInput{} = input -> + {input.resource, input, nil} + + {resource, %struct{} = action} + when struct in [ + Ash.Resource.Actions.Create, + Ash.Resource.Actions.Read, + Ash.Resource.Actions.Update, + Ash.Resource.Actions.Destroy, + Ash.Resource.Actions.Action + ] -> + resource_subject_input({resource, action, %{}}, domain, opts) + + {%resource{} = record, %Ash.Resource.Actions.Read{} = action, input} -> + {resource, Ash.Query.for_read(resource, action.name, input) |> filter_by_pkey(record), + input} + + {%resource{}, %Ash.Resource.Actions.Action{} = action, input} -> + {resource, Ash.ActionInput.for_action(resource, action.name, input), input} + + {%resource{}, %Ash.Resource.Actions.Create{} = action, input} -> + {resource, Ash.Changeset.for_create(resource, action.name, input), input} + + {%resource{} = record, %struct{} = action, input} + when struct in [ + Ash.Resource.Actions.Update, + Ash.Resource.Actions.Destroy + ] -> + {resource, Ash.Changeset.for_action(record, action.name, input, domain: domain), input} + + {resource, %Ash.Resource.Actions.Read{} = action, input} -> + {resource, Ash.Query.for_read(resource, action.name, input, domain: domain), input} + + {resource, %Ash.Resource.Actions.Action{} = action, input} -> + {resource, Ash.ActionInput.for_action(resource, action.name, input, domain: domain), + input} + + {resource, %Ash.Resource.Actions.Create{} = action, input} -> + {resource, Ash.Changeset.for_create(resource, action.name, input, domain: domain), input} + + {resource, %struct{} = action, input} + when struct in [ + Ash.Resource.Actions.Update, + Ash.Resource.Actions.Destroy + ] -> + {resource, action, input} + + {resource, action} -> + raise ArgumentError, """ + If providing an update or destroy action, you must provide a record to update or destroy. + + Got: #{inspect({resource, action})} + """ + end + end + + defp filter_by_pkey(query, %resource{} = record) do + Ash.Query.do_filter( + query, + record |> Map.take(Ash.Resource.Info.primary_key(resource)) |> Map.to_list() + ) + end + defp alter_source({:ok, true, query}, domain, actor, %Ash.Changeset{} = subject, opts) do case alter_source({:ok, true}, domain, actor, subject, Keyword.put(opts, :base_query, query)) do {:ok, true, new_subject} -> {:ok, true, new_subject, query} diff --git a/mix.exs b/mix.exs index 6b36a029..d950687e 100644 --- a/mix.exs +++ b/mix.exs @@ -84,7 +84,7 @@ defmodule Ash.MixProject do "documentation/topics/store-context-in-process.md", "documentation/topics/testing.md", "documentation/topics/timeouts.md", - "documentation/topics/validations.md", + "documentation/topics/validations.md" ], groups_for_extras: [ "Start Here": [ @@ -99,8 +99,7 @@ defmodule Ash.MixProject do "documentation/topics/contributing-to-ash.md", "CHANGELOG.md" ], - "How To": [ - ], + "How To": [], "DSL Reference": [ "documentation/dsls/DSL:-Ash.Resource.md", "documentation/dsls/DSL:-Ash.Domain.md", @@ -109,7 +108,7 @@ defmodule Ash.MixProject do "documentation/dsls/DSL:-Ash.DataLayer.Ets.md", "documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md", "documentation/dsls/DSL:-Ash.Reactor.md", - "documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md", + "documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md" ], "Under Review": [ # Documentation below this line is pending review @@ -150,7 +149,7 @@ defmodule Ash.MixProject do "documentation/topics/store-context-in-process.md", "documentation/topics/testing.md", "documentation/topics/timeouts.md", - "documentation/topics/validations.md", + "documentation/topics/validations.md" ] ], skip_undefined_reference_warnings_on: [