mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
improvement: add manual?
option for create/update/destroy
This commit is contained in:
parent
3bd2686de3
commit
7d37f8ae3d
9 changed files with 148 additions and 22 deletions
|
@ -63,6 +63,7 @@ locals_without_parens = [
|
|||
kind: 1,
|
||||
list: 3,
|
||||
list: 4,
|
||||
manual?: 1,
|
||||
many_to_many: 2,
|
||||
many_to_many: 3,
|
||||
message: 1,
|
||||
|
|
|
@ -64,6 +64,8 @@ defmodule Ash.Actions.Create do
|
|||
end
|
||||
end
|
||||
|
||||
defp add_tenant({:ok, nil}, _), do: {:ok, nil}
|
||||
|
||||
defp add_tenant({:ok, data}, changeset) do
|
||||
if Ash.Resource.Info.multitenancy_strategy(changeset.resource) do
|
||||
{:ok, %{data | __metadata__: Map.put(data.__metadata__, :tenant, changeset.tenant)}}
|
||||
|
@ -158,6 +160,9 @@ defmodule Ash.Actions.Create do
|
|||
)
|
||||
|
||||
if changeset.valid? do
|
||||
if action.manual? do
|
||||
{:ok, nil}
|
||||
else
|
||||
if upsert? do
|
||||
resource
|
||||
|> Ash.DataLayer.upsert(changeset)
|
||||
|
@ -169,14 +174,63 @@ defmodule Ash.Actions.Create do
|
|||
|> add_tenant(changeset)
|
||||
|> manage_relationships(api, changeset, engine_opts)
|
||||
end
|
||||
end
|
||||
else
|
||||
{:error, changeset.errors}
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
{:ok, nil, _changeset, _instructions} ->
|
||||
if action.manual? do
|
||||
{:error,
|
||||
"""
|
||||
No record created in create action!
|
||||
For manual actions, you must implement an `after_action` inside of a `change` that returns a newly created record.
|
||||
|
||||
For example:
|
||||
|
||||
# in the resource
|
||||
|
||||
action :special_create do
|
||||
manual? true
|
||||
change MyApp.DoCreate
|
||||
end
|
||||
|
||||
# The change
|
||||
defmodule MyApp.DoCreate do
|
||||
use Ash.Resource.Change
|
||||
|
||||
def change(changeset, _, _) do
|
||||
Ash.Changeset.after_action(changeset, fn changeset, _result ->
|
||||
# result will be `nil`, because this is a manual action
|
||||
|
||||
result = do_something_that_creates_the_record(changeset)
|
||||
|
||||
{:ok, result}
|
||||
end)
|
||||
end
|
||||
end
|
||||
"""}
|
||||
else
|
||||
{:error, "No record created in create action!"}
|
||||
end
|
||||
|
||||
{:ok, created, _changeset, instructions} ->
|
||||
if action.manual? do
|
||||
{:ok, created}
|
||||
|> add_tenant(changeset)
|
||||
|> manage_relationships(api, changeset, engine_opts)
|
||||
|> case do
|
||||
{:ok, result} ->
|
||||
{:ok, result, instructions}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
else
|
||||
{:ok, created, instructions}
|
||||
end
|
||||
|
||||
other ->
|
||||
other
|
||||
|
@ -194,6 +248,10 @@ defmodule Ash.Actions.Create do
|
|||
)
|
||||
end
|
||||
|
||||
defp manage_relationships({:ok, nil}, _, _, _) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
defp manage_relationships({:ok, created}, api, changeset, engine_opts) do
|
||||
with {:ok, loaded} <-
|
||||
Ash.Actions.ManagedRelationships.load(api, created, changeset, engine_opts),
|
||||
|
|
|
@ -88,6 +88,9 @@ defmodule Ash.Actions.Destroy do
|
|||
changeset
|
||||
|> Ash.Changeset.put_context(:private, %{actor: engine_opts[:actor]})
|
||||
|> Ash.Changeset.with_hooks(fn changeset ->
|
||||
if action.manual? do
|
||||
{:ok, record}
|
||||
else
|
||||
case Ash.DataLayer.destroy(resource, changeset) do
|
||||
:ok ->
|
||||
{:ok, record}
|
||||
|
@ -95,6 +98,7 @@ defmodule Ash.Actions.Destroy do
|
|||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
{:ok, result, changeset, instructions} ->
|
||||
|
|
|
@ -156,18 +156,37 @@ defmodule Ash.Actions.Update do
|
|||
changeset = set_tenant(changeset)
|
||||
|
||||
if changeset.valid? do
|
||||
if action.manual? do
|
||||
{:ok, nil}
|
||||
else
|
||||
resource
|
||||
|> Ash.DataLayer.update(changeset)
|
||||
|> add_tenant(changeset)
|
||||
|> manage_relationships(api, changeset, engine_opts)
|
||||
end
|
||||
else
|
||||
{:error, changeset.errors}
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
{:ok, updated, _changeset, instructions} ->
|
||||
{:ok, updated, changeset, instructions} ->
|
||||
if action.manual? do
|
||||
updated = updated || changeset.data
|
||||
|
||||
{:ok, updated}
|
||||
|> add_tenant(changeset)
|
||||
|> manage_relationships(api, changeset, engine_opts)
|
||||
|> case do
|
||||
{:ok, data} ->
|
||||
{:ok, data, instructions}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
else
|
||||
{:ok, updated, instructions}
|
||||
end
|
||||
|
||||
other ->
|
||||
other
|
||||
|
|
|
@ -858,7 +858,7 @@ defmodule Ash.Changeset do
|
|||
@spec with_hooks(
|
||||
t(),
|
||||
(t() ->
|
||||
{:ok, Ash.Resource.record(), %{notifications: list(Ash.Notifier.Notification.t())}}
|
||||
{:ok, term, %{notifications: list(Ash.Notifier.Notification.t())}}
|
||||
| {:error, term})
|
||||
) ::
|
||||
{:ok, term, t(), %{notifications: list(Ash.Notifier.Notification.t())}} | {:error, term}
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Ash.Resource.Actions.Create do
|
|||
:primary?,
|
||||
:description,
|
||||
accept: nil,
|
||||
manual?: false,
|
||||
require_attributes: [],
|
||||
arguments: [],
|
||||
changes: [],
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Ash.Resource.Actions.Destroy do
|
|||
:primary?,
|
||||
:soft?,
|
||||
:description,
|
||||
manual?: false,
|
||||
arguments: [],
|
||||
accept: nil,
|
||||
changes: [],
|
||||
|
|
|
@ -39,6 +39,47 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
|||
|
||||
No need to include attributes that are `allow_nil?: false`.
|
||||
"""
|
||||
],
|
||||
manual?: [
|
||||
type: :boolean,
|
||||
doc: """
|
||||
Instructs Ash to *skip* the actual update/create/destroy step.
|
||||
|
||||
All validation still takes place, but the `result` in any `after_action` callbacks
|
||||
attached to that action will simply be the record that was read from the database initially.
|
||||
For creates, the `result` will be `nil`, and you will be expected to handle the changeset in
|
||||
an after_action callback and return an instance of the record. This is a good way to prevent
|
||||
Ash from issuing an unnecessary update to the record, e.g updating the `updated_at` of the record
|
||||
when an action actually only involves modifying relating records.
|
||||
|
||||
You could then handle the changeset automatically.
|
||||
|
||||
For example:
|
||||
|
||||
# in the action
|
||||
|
||||
```elixir
|
||||
action :special_create do
|
||||
manual? true
|
||||
change MyApp.DoCreate
|
||||
end
|
||||
|
||||
# The change
|
||||
defmodule MyApp.DoCreate do
|
||||
use Ash.Resource.Change
|
||||
|
||||
def change(changeset, _, _) do
|
||||
Ash.Changeset.after_action(changeset, fn changeset, _result ->
|
||||
# result will be `nil`, because this is a manual action
|
||||
|
||||
result = do_something_that_creates_the_record(changeset)
|
||||
|
||||
{:ok, result}
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Ash.Resource.Actions.Update do
|
|||
:primary?,
|
||||
:description,
|
||||
accept: nil,
|
||||
manual?: false,
|
||||
require_attributes: [],
|
||||
arguments: [],
|
||||
changes: [],
|
||||
|
|
Loading…
Reference in a new issue