mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
fix: allow api.load/2 to load calculations
improvement: add `allow_nil_input` to create actions for api layers improvement: add `load/1` builtin change feat: change `get?: true` interface functions to raise on `nil`
This commit is contained in:
parent
00608e00d7
commit
e353ea49c3
8 changed files with 98 additions and 8 deletions
|
@ -4,6 +4,7 @@ locals_without_parens = [
|
||||||
accept: 1,
|
accept: 1,
|
||||||
action: 1,
|
action: 1,
|
||||||
allow_nil?: 1,
|
allow_nil?: 1,
|
||||||
|
allow_nil_input: 1,
|
||||||
always_select?: 1,
|
always_select?: 1,
|
||||||
args: 1,
|
args: 1,
|
||||||
argument: 2,
|
argument: 2,
|
||||||
|
|
|
@ -279,7 +279,9 @@ defmodule Ash.Actions.Read do
|
||||||
initial_query
|
initial_query
|
||||||
) do
|
) do
|
||||||
if params[:initial_data] do
|
if params[:initial_data] do
|
||||||
List.wrap(params[:initial_data])
|
Request.resolve([], fn _ ->
|
||||||
|
add_calculation_values(initial_query, params[:initial_data], initial_query.calculations)
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
relationship_filter_paths =
|
relationship_filter_paths =
|
||||||
Enum.map(filter_requests, fn request ->
|
Enum.map(filter_requests, fn request ->
|
||||||
|
|
|
@ -58,7 +58,15 @@ defmodule Ash.Api.Interface do
|
||||||
)
|
)
|
||||||
|
|
||||||
if unquote(interface.get?) do
|
if unquote(interface.get?) do
|
||||||
unquote(api).read_one(query, Keyword.drop(opts, [:query, :tenant]))
|
query
|
||||||
|
|> unquote(api).read_one(Keyword.drop(opts, [:query, :tenant]))
|
||||||
|
|> case do
|
||||||
|
{:ok, nil} ->
|
||||||
|
{:error, Ash.Error.Query.NotFound.exception(resource: query.resource)}
|
||||||
|
|
||||||
|
{:ok, result} ->
|
||||||
|
{:ok, result}
|
||||||
|
end
|
||||||
else
|
else
|
||||||
unquote(api).read(query, Keyword.drop(opts, [:query, :tenant]))
|
unquote(api).read(query, Keyword.drop(opts, [:query, :tenant]))
|
||||||
end
|
end
|
||||||
|
@ -92,7 +100,15 @@ defmodule Ash.Api.Interface do
|
||||||
)
|
)
|
||||||
|
|
||||||
if unquote(interface.get?) do
|
if unquote(interface.get?) do
|
||||||
unquote(api).read_one!(query, Keyword.drop(opts, [:query, :tenant]))
|
query
|
||||||
|
|> unquote(api).read_one!(Keyword.drop(opts, [:query, :tenant]))
|
||||||
|
|> case do
|
||||||
|
{:ok, nil} ->
|
||||||
|
{:error, Ash.Error.Query.NotFound.exception(resource: query.resource)}
|
||||||
|
|
||||||
|
{:ok, result} ->
|
||||||
|
{:ok, result}
|
||||||
|
end
|
||||||
else
|
else
|
||||||
unquote(api).read!(query, Keyword.drop(opts, [:query, :tenant]))
|
unquote(api).read!(query, Keyword.drop(opts, [:query, :tenant]))
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
accept: nil,
|
accept: nil,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
|
allow_nil: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
type: :create
|
type: :create
|
||||||
]
|
]
|
||||||
|
@ -15,6 +16,7 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
type: :create,
|
type: :create,
|
||||||
name: atom,
|
name: atom,
|
||||||
accept: [atom],
|
accept: [atom],
|
||||||
|
allow_nil: [atom],
|
||||||
arguments: [Ash.Resource.Actions.Argument.t()],
|
arguments: [Ash.Resource.Actions.Argument.t()],
|
||||||
primary?: boolean,
|
primary?: boolean,
|
||||||
description: String.t()
|
description: String.t()
|
||||||
|
@ -25,7 +27,21 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
@global_opts shared_options()
|
@global_opts shared_options()
|
||||||
@create_update_opts create_update_opts()
|
@create_update_opts create_update_opts()
|
||||||
|
|
||||||
@opt_schema []
|
@opt_schema [
|
||||||
|
allow_nil_input: [
|
||||||
|
type: {:list, :atom},
|
||||||
|
doc: """
|
||||||
|
A list of attributes that would normally be required, but should not be for this action.
|
||||||
|
|
||||||
|
This exists because extensions like ash_graphql and ash_json_api will add non-null validations to their input for any attribute
|
||||||
|
that is accepted by an action that has `allow_nil?: false`. This tells those extensions that some `change` on the resource might
|
||||||
|
set that attribute, and so they should not require it at the API layer.
|
||||||
|
|
||||||
|
Ash core doesn't actually use the values in this list, because it does its `nil` validation *after* running all resource
|
||||||
|
changes. If the value is still `nil` by the time Ash would submit to the data layer, then an error is returned.
|
||||||
|
"""
|
||||||
|
]
|
||||||
|
]
|
||||||
|> Ash.OptionsHelpers.merge_schemas(
|
|> Ash.OptionsHelpers.merge_schemas(
|
||||||
@global_opts,
|
@global_opts,
|
||||||
"Action Options"
|
"Action Options"
|
||||||
|
|
|
@ -21,15 +21,14 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
@create_update_opts [
|
@create_update_opts [
|
||||||
accept: [
|
accept: [
|
||||||
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
||||||
doc:
|
doc: "The list of attributes to accept. Defaults to all attributes on the resource"
|
||||||
"The list of attributes and relationships to accept. Defaults to all attributes on the resource"
|
|
||||||
],
|
],
|
||||||
reject: [
|
reject: [
|
||||||
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
||||||
doc: """
|
doc: """
|
||||||
A list of attributes and relationships not to accept. This is useful if you want to say 'accept all but x'
|
A list of attributes not to accept. This is useful if you want to say 'accept all but x'
|
||||||
|
|
||||||
If this is specified along with `accept`, then everything in the `accept` list minuse any matches in the
|
If this is specified along with `accept`, then everything in the `accept` list minus any matches in the
|
||||||
`reject` list will be accepted.
|
`reject` list will be accepted.
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
|
|
|
@ -52,4 +52,11 @@ defmodule Ash.Resource.Change.Builtins do
|
||||||
def set_context(context) do
|
def set_context(context) do
|
||||||
{Ash.Resource.Change.SetContext, context: context}
|
{Ash.Resource.Change.SetContext, context: context}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Passes the provided value into `changeset.api.load()`, after the action has completed.
|
||||||
|
"""
|
||||||
|
def load(value) do
|
||||||
|
{Ash.Resource.Change.Load, target: value}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
11
lib/ash/resource/change/load.ex
Normal file
11
lib/ash/resource/change/load.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Change.Load do
|
||||||
|
@moduledoc false
|
||||||
|
use Ash.Resource.Change
|
||||||
|
alias Ash.Changeset
|
||||||
|
|
||||||
|
def change(changeset, opts, _) do
|
||||||
|
Changeset.after_action(changeset, fn changeset, result ->
|
||||||
|
changeset.api.load(result, opts[:target])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
38
test/resource/changes/load_test.exs
Normal file
38
test/resource/changes/load_test.exs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
defmodule Ash.Test.Resource.Changes.LoadTest do
|
||||||
|
@moduledoc false
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
defmodule Post do
|
||||||
|
use Ash.Resource,
|
||||||
|
data_layer: Ash.DataLayer.Ets
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
uuid_primary_key :id
|
||||||
|
attribute :text, :string
|
||||||
|
attribute :second_text, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
actions do
|
||||||
|
create :create do
|
||||||
|
change load(:full_text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
calculations do
|
||||||
|
calculate :full_text, :string, concat([:text, :second_text])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Api do
|
||||||
|
use Ash.Api
|
||||||
|
|
||||||
|
resources do
|
||||||
|
resource Post
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "you can use it to load on create" do
|
||||||
|
assert Api.create!(Ash.Changeset.for_create(Post, :create, text: "foo", second_text: "bar")).full_text ==
|
||||||
|
"foobar"
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue