Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
75 changes: 62 additions & 13 deletions lib/ash/action_input.ex
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ defmodule Ash.ActionInput do
- `set_private_argument/3` for setting private arguments
- `for_action/4` for providing initial arguments
"""
@spec set_argument(input :: t(), name :: atom, value :: term()) :: t()
@spec set_argument(input :: t(), name :: atom | String.t(), value :: term()) :: t()
def set_argument(input, argument, value) do
if input.action do
argument =
Expand Down Expand Up @@ -553,6 +553,26 @@ defmodule Ash.ActionInput do
end
end

@doc """
Deletes one or more arguments from the subject.

## Parameters

* `subject` - The subject to delete arguments from
* `arguments` - Single argument name or list of argument names to delete
"""
@spec delete_argument(
input :: t(),
argument_or_arguments :: atom | String.t() | list(atom | String.t())
) :: t()
def delete_argument(input, argument_or_arguments) do
argument_or_arguments
|> List.wrap()
|> Enum.reduce(input, fn argument, input ->
%{input | arguments: Map.delete(input.arguments, argument)}
end)
end

@doc """
Sets a private argument value on the action input.

Expand Down Expand Up @@ -998,10 +1018,15 @@ defmodule Ash.ActionInput do
"""
@spec after_action(
input :: t(),
fun :: after_action_fun()
fun :: after_action_fun(),
opts :: Keyword.t()
) :: t()
def after_action(input, func) do
%{input | after_action: input.after_action ++ [func]}
def after_action(input, func, opts \\ []) do
if opts[:prepend?] do
%{input | after_action: [func | input.after_action]}
else
%{input | after_action: input.after_action ++ [func]}
end
end

@doc """
Expand All @@ -1025,9 +1050,17 @@ defmodule Ash.ActionInput do
- `around_transaction/2` for hooks that wrap the entire transaction
- `before_action/3` for hooks that run before the action (inside transaction)
"""
@spec before_transaction(t, before_transaction_fun) :: t
def before_transaction(input, func) do
%{input | before_transaction: input.before_transaction ++ [func]}
@spec before_transaction(
input :: t(),
fun :: before_transaction_fun(),
opts :: Keyword.t()
) :: t()
def before_transaction(input, func, opts \\ []) do
if opts[:prepend?] do
%{input | before_transaction: [func | input.before_transaction]}
else
%{input | before_transaction: input.before_transaction ++ [func]}
end
end

@doc """
Expand All @@ -1051,9 +1084,17 @@ defmodule Ash.ActionInput do
- `around_transaction/2` for hooks that wrap the entire transaction
- `after_action/2` for hooks that run after the action (inside transaction)
"""
@spec after_transaction(t, after_transaction_fun) :: t
def after_transaction(input, func) do
%{input | after_transaction: input.after_transaction ++ [func]}
@spec after_transaction(
input :: t(),
fun :: after_transaction_fun(),
opts :: Keyword.t()
) :: t()
def after_transaction(input, func, opts \\ []) do
if opts[:prepend?] do
%{input | after_transaction: [func | input.after_transaction]}
else
%{input | after_transaction: input.after_transaction ++ [func]}
end
end

@doc """
Expand Down Expand Up @@ -1081,9 +1122,17 @@ defmodule Ash.ActionInput do
- `after_transaction/2` for hooks that run after the transaction
- `before_action/3` and `after_action/2` for hooks that run inside the transaction
"""
@spec around_transaction(t, around_transaction_fun) :: t
def around_transaction(input, func) do
%{input | around_transaction: input.around_transaction ++ [func]}
@spec around_transaction(
input :: t(),
fun :: around_transaction_fun(),
opts :: Keyword.t()
) :: t()
def around_transaction(input, func, opts \\ []) do
if opts[:prepend?] do
%{input | around_transaction: [func | input.around_transaction]}
else
%{input | around_transaction: input.around_transaction ++ [func]}
end
end

@doc false
Expand Down
105 changes: 90 additions & 15 deletions lib/ash/changeset/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4828,6 +4828,28 @@ defmodule Ash.Changeset do
end
end

@doc """
Fetches the changing value or the original value of an attribute.

## Example

iex> changeset = Ash.Changeset.for_update(post, :update, %{title: "New Title"})
iex> Ash.Changeset.fetch_attribute(changeset, :title)
{:ok, "New Title"}
iex> Ash.Changeset.fetch_attribute(changeset, :content)
:error
"""
@spec fetch_attribute(t, atom) :: {:ok, term} | :error
def fetch_attribute(changeset, attribute) do
case fetch_change(changeset, attribute) do
{:ok, value} ->
{:ok, value}

:error ->
fetch_data(changeset, attribute)
end
end

@doc "Gets the value of an argument provided to the changeset, falling back to `Ash.Changeset.get_attribute/2` if nothing was provided."
@spec get_argument_or_attribute(t, atom) :: term
def get_argument_or_attribute(changeset, attribute) do
Expand All @@ -4837,6 +4859,25 @@ defmodule Ash.Changeset do
end
end

@doc """
Fetches the value of an argument provided to the changeset, falling back to `Ash.Changeset.fetch_attribute/2` if nothing was provided.

## Example

iex> changeset = Ash.Changeset.for_update(post, :update, %{title: "New Title"})
iex> Ash.Changeset.fetch_argument_or_attribute(changeset, :title)
{:ok, "New Title"}
iex> Ash.Changeset.fetch_argument_or_attribute(changeset, :content)
:error
"""
@spec fetch_argument_or_attribute(t, atom) :: {:ok, term} | :error
def fetch_argument_or_attribute(changeset, argument_or_attribute) do
case fetch_argument(changeset, argument_or_attribute) do
{:ok, value} -> {:ok, value}
:error -> fetch_attribute(changeset, argument_or_attribute)
end
end

@doc "Gets the new value for an attribute, or `:error` if it is not being changed."
@spec fetch_change(t, atom) :: {:ok, any} | :error
def fetch_change(changeset, attribute) do
Expand All @@ -4858,6 +4899,22 @@ defmodule Ash.Changeset do
Map.get(changeset.data, attribute)
end

@doc """
Gets the original value for an attribute, or `:error` if it is not available.

## Example

iex> changeset = Ash.Changeset.for_update(post, :update, %{title: "New Title"})
iex> Ash.Changeset.fetch_data(changeset, :title)
{:ok, "Original Title"}
iex> Ash.Changeset.fetch_data(changeset, :content)
:error
"""
@spec fetch_data(t, atom) :: {:ok, term} | :error
def fetch_data(changeset, attribute) do
Map.fetch(changeset.data, attribute)
end

@doc """
Puts a key/value in the changeset context that can be used later.

Expand Down Expand Up @@ -6546,9 +6603,9 @@ defmodule Ash.Changeset do
- `around_transaction/2` for hooks that wrap the entire transaction
"""
@spec before_transaction(
t(),
before_transaction_fun(),
Keyword.t()
changeset :: t(),
fun :: before_transaction_fun(),
opts :: Keyword.t()
) :: t()
def before_transaction(changeset, func, opts \\ []) do
changeset = maybe_dirty_hook(changeset, :before_transaction)
Expand Down Expand Up @@ -6622,9 +6679,9 @@ defmodule Ash.Changeset do
- `around_action/2` for hooks that wrap the data layer action
"""
@spec after_action(
t(),
after_action_fun(),
Keyword.t()
changeset :: t(),
fun :: after_action_fun(),
opts :: Keyword.t()
) :: t()
def after_action(changeset, func, opts \\ []) do
changeset = maybe_dirty_hook(changeset, :after_action)
Expand Down Expand Up @@ -6724,9 +6781,9 @@ defmodule Ash.Changeset do
- `around_transaction/2` for hooks that wrap the entire transaction
"""
@spec after_transaction(
t(),
after_transaction_fun(),
Keyword.t()
changeset :: t(),
fun :: after_transaction_fun(),
opts :: Keyword.t()
) :: t()
def after_transaction(changeset, func, opts \\ []) do
changeset = maybe_dirty_hook(changeset, :after_transaction)
Expand Down Expand Up @@ -6784,10 +6841,19 @@ defmodule Ash.Changeset do
- Multi-step actions guide for complex workflow patterns
"""

@spec around_action(t(), around_action_fun()) :: t()
def around_action(changeset, func) do
@spec around_action(
changeset :: t(),
fun :: around_action_fun(),
opts :: Keyword.t()
) :: t()
def around_action(changeset, func, opts \\ []) do
changeset = maybe_dirty_hook(changeset, :around_action)
%{changeset | around_action: changeset.around_action ++ [func]}

if opts[:prepend?] do
%{changeset | around_action: [func | changeset.around_action]}
else
%{changeset | around_action: changeset.around_action ++ [func]}
end
end

@doc """
Expand Down Expand Up @@ -6829,10 +6895,19 @@ defmodule Ash.Changeset do
- Multi-step actions guide for complex workflow patterns
"""

@spec around_transaction(t(), around_transaction_fun()) :: t()
def around_transaction(changeset, func) do
@spec around_transaction(
changeset :: t(),
fun :: around_transaction_fun(),
opts :: Keyword.t()
) :: t()
def around_transaction(changeset, func, opts \\ []) do
changeset = maybe_dirty_hook(changeset, :around_transaction)
%{changeset | around_transaction: changeset.around_transaction ++ [func]}

if opts[:prepend?] do
%{changeset | around_transaction: [func | changeset.around_transaction]}
else
%{changeset | around_transaction: changeset.around_transaction ++ [func]}
end
end

defp maybe_dirty_hook(changeset, type) do
Expand Down
70 changes: 51 additions & 19 deletions lib/ash/query/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1325,10 +1325,19 @@ defmodule Ash.Query do
- `around_transaction/2` for hooks that wrap the entire transaction
- `before_action/3` for hooks that run before the action (inside transaction)
"""
@spec before_transaction(t(), before_transaction_fun()) :: t()
def before_transaction(query, func) do
@spec before_transaction(
query :: t(),
fun :: before_transaction_fun(),
opts :: Keyword.t()
) :: t()
def before_transaction(query, func, opts \\ []) do
query = new(query)
%{query | before_transaction: query.before_transaction ++ [func]}

if opts[:prepend?] do
%{query | before_transaction: [func | query.before_transaction]}
else
%{query | before_transaction: query.before_transaction ++ [func]}
end
end

@doc """
Expand All @@ -1352,10 +1361,19 @@ defmodule Ash.Query do
- `around_transaction/2` for hooks that wrap the entire transaction
- `after_action/2` for hooks that run after the action (inside transaction)
"""
@spec after_transaction(t(), after_transaction_fun()) :: t()
def after_transaction(query, func) do
@spec after_transaction(
query :: t(),
fun :: after_transaction_fun(),
opts :: Keyword.t()
) :: t()
def after_transaction(query, func, opts \\ []) do
query = new(query)
%{query | after_transaction: query.after_transaction ++ [func]}

if opts[:prepend?] do
%{query | after_transaction: [func | query.after_transaction]}
else
%{query | after_transaction: query.after_transaction ++ [func]}
end
end

@doc """
Expand Down Expand Up @@ -1403,10 +1421,19 @@ defmodule Ash.Query do
- `Ash.read/2` for executing queries with hooks
"""

@spec around_transaction(t(), around_transaction_fun()) :: t()
def around_transaction(query, func) do
@spec around_transaction(
query :: t(),
fun :: around_transaction_fun(),
opts :: Keyword.t()
) :: t()
def around_transaction(query, func, opts \\ []) do
query = new(query)
%{query | around_transaction: query.around_transaction ++ [func]}

if opts[:prepend?] do
%{query | around_transaction: [func | query.around_transaction]}
else
%{query | around_transaction: query.around_transaction ++ [func]}
end
end

@doc """
Expand Down Expand Up @@ -1529,11 +1556,11 @@ defmodule Ash.Query do
- `Ash.read/2` for executing queries with hooks
"""
@spec after_action(
t(),
(t(), [Ash.Resource.record()] ->
{:ok, [Ash.Resource.record()]}
| {:ok, [Ash.Resource.record()], list(Ash.Notifier.Notification.t())}
| {:error, term})
query :: t(),
fun :: (t(), [Ash.Resource.record()] ->
{:ok, [Ash.Resource.record()]}
| {:ok, [Ash.Resource.record()], list(Ash.Notifier.Notification.t())}
| {:error, term})
) :: t()
# in 4.0, add an option to prepend hooks
def after_action(query, func) do
Expand Down Expand Up @@ -2769,9 +2796,12 @@ defmodule Ash.Query do
- `set_argument/3` for adding arguments to queries
- `for_read/4` for creating queries with arguments
"""
@spec get_argument(t, atom) :: term
def get_argument(query, argument) when is_atom(argument) do
Map.get(query.arguments, argument) || Map.get(query.arguments, to_string(argument))
@spec get_argument(t, atom | String.t()) :: term
def get_argument(query, argument) when is_atom(argument) or is_binary(argument) do
case fetch_argument(query, argument) do
{:ok, value} -> value
:error -> nil
end
end

@doc """
Expand Down Expand Up @@ -2804,8 +2834,10 @@ defmodule Ash.Query do
- `set_argument/3` for adding arguments to queries
- `for_read/4` for creating queries with arguments
"""
@spec fetch_argument(t, atom) :: {:ok, term} | :error
def fetch_argument(query, argument) when is_atom(argument) do
@spec fetch_argument(t, atom | String.t()) :: {:ok, term} | :error
def fetch_argument(query, argument) when is_atom(argument) or is_binary(argument) do
query = new(query)

case Map.fetch(query.arguments, argument) do
{:ok, value} ->
{:ok, value}
Expand Down
Loading
Loading