From bae263ca12a52cbde1639ec7aacd577bf6d3e49e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Apr 2024 13:46:44 -0400 Subject: [PATCH] improvement: support omitting generic action return types --- documentation/dsls/DSL:-Ash.Resource.md | 2 +- documentation/topics/actions/generic-actions.md | 16 +++++++++++++++- lib/ash.ex | 2 +- lib/ash/actions/action.ex | 15 +++++++++++++-- lib/ash/resource/actions/action/action.ex | 6 +++++- lib/ash/resource/dsl.ex | 2 +- test/actions/generic_actions_test.exs | 4 ++++ 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/documentation/dsls/DSL:-Ash.Resource.md b/documentation/dsls/DSL:-Ash.Resource.md index 066b2fad..d0b1af93 100644 --- a/documentation/dsls/DSL:-Ash.Resource.md +++ b/documentation/dsls/DSL:-Ash.Resource.md @@ -912,7 +912,7 @@ end ## actions.action ```elixir -action name, returns +action name, returns \\ nil ``` diff --git a/documentation/topics/actions/generic-actions.md b/documentation/topics/actions/generic-actions.md index 6805ac19..0ea8867d 100644 --- a/documentation/topics/actions/generic-actions.md +++ b/documentation/topics/actions/generic-actions.md @@ -14,6 +14,20 @@ end A generic action declares its arguments, return type, and implementation, as illustrated above. +> ### No return? No problem! {: .tip} +> +> Generic actions can omit a return type, in which case running them returns `:ok` if successful. +> +> ```elixir +> action :schedule_job do +> argument :job_name, :string, allow_nil?: false +> run fn input -> +> # Schedule the job +> :ok +> end +> end +> ``` + ## Why use generic actions? The example above could be written as a normal function in elixir, i.e @@ -51,7 +65,7 @@ action :priority, :integer do end ``` -> #### Returning resource instances {: .tip} +> #### Returning resource instances {: .info} > > It sometimes happens that you want to make a generic action which returns an > instance or instances of the resource. It's natural to assume that you can diff --git a/lib/ash.ex b/lib/ash.ex index ed6683a0..14fb398c 100644 --- a/lib/ash.ex +++ b/lib/ash.ex @@ -1261,7 +1261,7 @@ defmodule Ash do """ @doc spark_opts: [{1, @run_action_opts}] @spec run_action(input :: Ash.ActionInput.t(), opts :: Keyword.t()) :: - {:ok, term} | {:error, Ash.Error.t()} + :ok | {:ok, term} | {:error, Ash.Error.t()} def run_action(input, opts \\ []) do Ash.Helpers.expect_options!(opts) domain = Ash.Helpers.domain!(input, opts) diff --git a/lib/ash/actions/action.ex b/lib/ash/actions/action.ex index f977f6f2..b34bc72f 100644 --- a/lib/ash/actions/action.ex +++ b/lib/ash/actions/action.ex @@ -123,8 +123,15 @@ defmodule Ash.Actions.Action do case authorize(domain, opts[:actor], input) do :ok -> case call_run_function(module, input, run_opts, context, false) do + :ok when is_nil(input.action.returns) -> + :ok + {:ok, result} -> - {:ok, result} + if input.action.returns do + {:ok, result} + else + :ok + end {:ok, result, notifications} -> remaining = Ash.Notifier.notify(notifications) @@ -133,7 +140,11 @@ defmodule Ash.Actions.Action do resource_notifications: remaining }) - {:ok, result} + if input.action.returns do + {:ok, result} + else + :ok + end {:error, error} -> {:error, error} diff --git a/lib/ash/resource/actions/action/action.ex b/lib/ash/resource/actions/action/action.ex index 30464d10..db6639fc 100644 --- a/lib/ash/resource/actions/action/action.ex +++ b/lib/ash/resource/actions/action/action.ex @@ -24,13 +24,17 @@ defmodule Ash.Resource.Actions.Action do touches_resources: [Ash.Resource.t()], constraints: Keyword.t(), run: {module, Keyword.t()}, - returns: Ash.Type.t(), + returns: Ash.Type.t() | nil, primary?: boolean, transaction?: boolean } import Ash.Resource.Actions.SharedOptions + def transform(%{returns: nil} = action) do + {:ok, action} + end + def transform(%{returns: original_type, constraints: constraints} = thing) do type = Ash.Type.get_type(original_type) diff --git a/lib/ash/resource/dsl.ex b/lib/ash/resource/dsl.ex index 3fca0749..fbd6ecb8 100644 --- a/lib/ash/resource/dsl.ex +++ b/lib/ash/resource/dsl.ex @@ -495,7 +495,7 @@ defmodule Ash.Resource.Dsl do @action_argument ] ], - args: [:name, :returns] + args: [:name, {:optional, :returns}] } @create %Spark.Dsl.Entity{ diff --git a/test/actions/generic_actions_test.exs b/test/actions/generic_actions_test.exs index eff5a2f0..779db857 100644 --- a/test/actions/generic_actions_test.exs +++ b/test/actions/generic_actions_test.exs @@ -36,6 +36,10 @@ defmodule Ash.Test.Actions.GenericActionsTest do {:ok, "Hello #{input.arguments.name}"} end) end + + action :do_nothing do + run fn _ -> :ok end + end end attributes do