mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +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,
|
kind: 1,
|
||||||
list: 3,
|
list: 3,
|
||||||
list: 4,
|
list: 4,
|
||||||
|
manual?: 1,
|
||||||
many_to_many: 2,
|
many_to_many: 2,
|
||||||
many_to_many: 3,
|
many_to_many: 3,
|
||||||
message: 1,
|
message: 1,
|
||||||
|
|
|
@ -64,6 +64,8 @@ defmodule Ash.Actions.Create do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp add_tenant({:ok, nil}, _), do: {:ok, nil}
|
||||||
|
|
||||||
defp add_tenant({:ok, data}, changeset) do
|
defp add_tenant({:ok, data}, changeset) do
|
||||||
if Ash.Resource.Info.multitenancy_strategy(changeset.resource) do
|
if Ash.Resource.Info.multitenancy_strategy(changeset.resource) do
|
||||||
{:ok, %{data | __metadata__: Map.put(data.__metadata__, :tenant, changeset.tenant)}}
|
{:ok, %{data | __metadata__: Map.put(data.__metadata__, :tenant, changeset.tenant)}}
|
||||||
|
@ -158,6 +160,9 @@ defmodule Ash.Actions.Create do
|
||||||
)
|
)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
|
if action.manual? do
|
||||||
|
{:ok, nil}
|
||||||
|
else
|
||||||
if upsert? do
|
if upsert? do
|
||||||
resource
|
resource
|
||||||
|> Ash.DataLayer.upsert(changeset)
|
|> Ash.DataLayer.upsert(changeset)
|
||||||
|
@ -169,14 +174,63 @@ defmodule Ash.Actions.Create do
|
||||||
|> add_tenant(changeset)
|
|> add_tenant(changeset)
|
||||||
|> manage_relationships(api, changeset, engine_opts)
|
|> manage_relationships(api, changeset, engine_opts)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
{:error, changeset.errors}
|
{:error, changeset.errors}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case result do
|
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} ->
|
{: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}
|
{:ok, created, instructions}
|
||||||
|
end
|
||||||
|
|
||||||
other ->
|
other ->
|
||||||
other
|
other
|
||||||
|
@ -194,6 +248,10 @@ defmodule Ash.Actions.Create do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp manage_relationships({:ok, nil}, _, _, _) do
|
||||||
|
{:ok, nil}
|
||||||
|
end
|
||||||
|
|
||||||
defp manage_relationships({:ok, created}, api, changeset, engine_opts) do
|
defp manage_relationships({:ok, created}, api, changeset, engine_opts) do
|
||||||
with {:ok, loaded} <-
|
with {:ok, loaded} <-
|
||||||
Ash.Actions.ManagedRelationships.load(api, created, changeset, engine_opts),
|
Ash.Actions.ManagedRelationships.load(api, created, changeset, engine_opts),
|
||||||
|
|
|
@ -88,6 +88,9 @@ defmodule Ash.Actions.Destroy do
|
||||||
changeset
|
changeset
|
||||||
|> Ash.Changeset.put_context(:private, %{actor: engine_opts[:actor]})
|
|> Ash.Changeset.put_context(:private, %{actor: engine_opts[:actor]})
|
||||||
|> Ash.Changeset.with_hooks(fn changeset ->
|
|> Ash.Changeset.with_hooks(fn changeset ->
|
||||||
|
if action.manual? do
|
||||||
|
{:ok, record}
|
||||||
|
else
|
||||||
case Ash.DataLayer.destroy(resource, changeset) do
|
case Ash.DataLayer.destroy(resource, changeset) do
|
||||||
:ok ->
|
:ok ->
|
||||||
{:ok, record}
|
{:ok, record}
|
||||||
|
@ -95,6 +98,7 @@ defmodule Ash.Actions.Destroy do
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
{:error, error}
|
{:error, error}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, result, changeset, instructions} ->
|
{:ok, result, changeset, instructions} ->
|
||||||
|
|
|
@ -156,18 +156,37 @@ defmodule Ash.Actions.Update do
|
||||||
changeset = set_tenant(changeset)
|
changeset = set_tenant(changeset)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
|
if action.manual? do
|
||||||
|
{:ok, nil}
|
||||||
|
else
|
||||||
resource
|
resource
|
||||||
|> Ash.DataLayer.update(changeset)
|
|> Ash.DataLayer.update(changeset)
|
||||||
|> add_tenant(changeset)
|
|> add_tenant(changeset)
|
||||||
|> manage_relationships(api, changeset, engine_opts)
|
|> manage_relationships(api, changeset, engine_opts)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
{:error, changeset.errors}
|
{:error, changeset.errors}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case result do
|
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}
|
{:ok, updated, instructions}
|
||||||
|
end
|
||||||
|
|
||||||
other ->
|
other ->
|
||||||
other
|
other
|
||||||
|
|
|
@ -858,7 +858,7 @@ defmodule Ash.Changeset do
|
||||||
@spec with_hooks(
|
@spec with_hooks(
|
||||||
t(),
|
t(),
|
||||||
(t() ->
|
(t() ->
|
||||||
{:ok, Ash.Resource.record(), %{notifications: list(Ash.Notifier.Notification.t())}}
|
{:ok, term, %{notifications: list(Ash.Notifier.Notification.t())}}
|
||||||
| {:error, term})
|
| {:error, term})
|
||||||
) ::
|
) ::
|
||||||
{:ok, term, t(), %{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?,
|
:primary?,
|
||||||
:description,
|
:description,
|
||||||
accept: nil,
|
accept: nil,
|
||||||
|
manual?: false,
|
||||||
require_attributes: [],
|
require_attributes: [],
|
||||||
arguments: [],
|
arguments: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Ash.Resource.Actions.Destroy do
|
||||||
:primary?,
|
:primary?,
|
||||||
:soft?,
|
:soft?,
|
||||||
:description,
|
:description,
|
||||||
|
manual?: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
accept: nil,
|
accept: nil,
|
||||||
changes: [],
|
changes: [],
|
||||||
|
|
|
@ -39,6 +39,47 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
|
|
||||||
No need to include attributes that are `allow_nil?: false`.
|
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?,
|
:primary?,
|
||||||
:description,
|
:description,
|
||||||
accept: nil,
|
accept: nil,
|
||||||
|
manual?: false,
|
||||||
require_attributes: [],
|
require_attributes: [],
|
||||||
arguments: [],
|
arguments: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
|
|
Loading…
Reference in a new issue