mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 13:03:02 +12:00
feat: add accept
option to create/update actions (#105)
This commit is contained in:
parent
3ff4415c77
commit
a58367414d
9 changed files with 160 additions and 11 deletions
|
@ -1,6 +1,7 @@
|
|||
# THIS FILE IS AUTOGENERATED USING `mix ash.formatter`
|
||||
# DONT MODIFY IT BY HAND
|
||||
locals_without_parens = [
|
||||
accept: 1,
|
||||
allow_nil?: 1,
|
||||
argument: 2,
|
||||
argument: 3,
|
||||
|
|
|
@ -5,6 +5,8 @@ defmodule Ash.Actions.Create do
|
|||
alias Ash.Engine.Request
|
||||
require Logger
|
||||
|
||||
alias Ash.Error.Changes.{InvalidAttribute, InvalidRelationship}
|
||||
|
||||
@spec run(Ash.api(), Ash.changeset(), Ash.action(), Keyword.t()) ::
|
||||
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||
def run(api, changeset, action, opts) do
|
||||
|
@ -16,7 +18,7 @@ defmodule Ash.Actions.Create do
|
|||
|> Keyword.take([:verbose?, :actor, :authorize?])
|
||||
|> Keyword.put(:transaction?, true)
|
||||
|
||||
with %{valid?: true} = changeset <- changeset(changeset, api),
|
||||
with %{valid?: true} = changeset <- changeset(changeset, api, action),
|
||||
:ok <- check_upsert_support(changeset.resource, upsert?),
|
||||
%{
|
||||
data: %{commit: %^resource{} = created},
|
||||
|
@ -43,13 +45,48 @@ defmodule Ash.Actions.Create do
|
|||
end
|
||||
end
|
||||
|
||||
defp changeset(changeset, api) do
|
||||
defp changeset(changeset, api, action) do
|
||||
%{changeset | api: api}
|
||||
|> Relationships.handle_relationship_changes()
|
||||
|> validate_attributes_accepted(action)
|
||||
|> validate_relationships_accepted(action)
|
||||
|> set_defaults()
|
||||
|> add_validations()
|
||||
end
|
||||
|
||||
defp validate_attributes_accepted(changeset, %{accept: nil}), do: changeset
|
||||
|
||||
defp validate_attributes_accepted(changeset, %{accept: accepted_attributes}) do
|
||||
changeset.attributes
|
||||
|> Enum.reject(fn {key, _value} ->
|
||||
key in accepted_attributes
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn {key, _}, changeset ->
|
||||
Ash.Changeset.add_error(
|
||||
changeset,
|
||||
InvalidAttribute.exception(field: key, message: "Cannot be changed")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_relationships_accepted(changeset, %{accept: nil}), do: changeset
|
||||
|
||||
defp validate_relationships_accepted(changeset, %{accept: accepted_relationships}) do
|
||||
changeset.relationships
|
||||
|> Enum.reject(fn {key, _value} ->
|
||||
key in accepted_relationships
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn {key, _}, changeset ->
|
||||
Ash.Changeset.add_error(
|
||||
changeset,
|
||||
InvalidRelationship.exception(
|
||||
relationship: key,
|
||||
message: "Cannot be changed"
|
||||
)
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_validations(changeset) do
|
||||
Ash.Changeset.before_action(changeset, fn changeset ->
|
||||
changeset.resource
|
||||
|
|
|
@ -5,6 +5,8 @@ defmodule Ash.Actions.Update do
|
|||
alias Ash.Engine.Request
|
||||
require Logger
|
||||
|
||||
alias Ash.Error.Changes.{InvalidAttribute, InvalidRelationship}
|
||||
|
||||
@spec run(Ash.api(), Ash.record(), Ash.action(), Keyword.t()) ::
|
||||
{:ok, Ash.record()} | {:error, Ash.Changeset.t()} | {:error, Ash.error()}
|
||||
def run(api, changeset, action, opts) do
|
||||
|
@ -15,7 +17,7 @@ defmodule Ash.Actions.Update do
|
|||
|
||||
resource = changeset.resource
|
||||
|
||||
with %{valid?: true} = changeset <- changeset(changeset, api),
|
||||
with %{valid?: true} = changeset <- changeset(changeset, api, action),
|
||||
%{data: %{commit: %^resource{} = updated}, errors: []} <-
|
||||
do_run_requests(
|
||||
changeset,
|
||||
|
@ -37,13 +39,48 @@ defmodule Ash.Actions.Update do
|
|||
end
|
||||
end
|
||||
|
||||
defp changeset(changeset, api) do
|
||||
defp changeset(changeset, api, action) do
|
||||
%{changeset | api: api}
|
||||
|> Relationships.handle_relationship_changes()
|
||||
|> validate_attributes_accepted(action)
|
||||
|> validate_relationships_accepted(action)
|
||||
|> set_defaults()
|
||||
|> add_validations()
|
||||
end
|
||||
|
||||
defp validate_attributes_accepted(changeset, %{accept: nil}), do: changeset
|
||||
|
||||
defp validate_attributes_accepted(changeset, %{accept: accepted_attributes}) do
|
||||
changeset.attributes
|
||||
|> Enum.reject(fn {key, _value} ->
|
||||
key in accepted_attributes
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn {key, _}, changeset ->
|
||||
Ash.Changeset.add_error(
|
||||
changeset,
|
||||
InvalidAttribute.exception(field: key, message: "Cannot be changed")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_relationships_accepted(changeset, %{accept: nil}), do: changeset
|
||||
|
||||
defp validate_relationships_accepted(changeset, %{accept: accepted_relationships}) do
|
||||
changeset.relationships
|
||||
|> Enum.reject(fn {key, _value} ->
|
||||
key in accepted_relationships
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn {key, _}, changeset ->
|
||||
Ash.Changeset.add_error(
|
||||
changeset,
|
||||
InvalidRelationship.exception(
|
||||
relationship: key,
|
||||
message: "Cannot be changed"
|
||||
)
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_validations(changeset) do
|
||||
changeset.resource()
|
||||
|> Ash.Resource.validations(:update)
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Ash.Error.Changes.InvalidAttribute do
|
|||
@moduledoc "Used when an invalid value is provided for an attribute change"
|
||||
use Ash.Error
|
||||
|
||||
def_ash_error([:field, :type, :message, :validation], class: :invalid)
|
||||
def_ash_error([:field, :message, :validation], class: :invalid)
|
||||
|
||||
defimpl Ash.ErrorKind do
|
||||
def id(_), do: Ecto.UUID.generate()
|
||||
|
|
|
@ -22,6 +22,14 @@ defmodule Ash.OptionsHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
def list_of_atoms(value) do
|
||||
if is_list(value) and Enum.all?(value, &is_atom/1) do
|
||||
{:ok, value}
|
||||
else
|
||||
{:error, "Expected a list of atoms"}
|
||||
end
|
||||
end
|
||||
|
||||
def default(value) when is_function(value, 0), do: {:ok, value}
|
||||
|
||||
def default({module, function, args})
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
defmodule Ash.Resource.Actions.Create do
|
||||
@moduledoc "Represents a create action on a resource."
|
||||
defstruct [:name, :primary?, type: :create]
|
||||
defstruct [:name, :primary?, :accept, type: :create]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :create,
|
||||
name: atom,
|
||||
accept: [atom],
|
||||
primary?: boolean
|
||||
}
|
||||
|
||||
|
@ -14,6 +15,11 @@ defmodule Ash.Resource.Actions.Create do
|
|||
required: true,
|
||||
doc: "The name of the action"
|
||||
],
|
||||
accept: [
|
||||
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
||||
doc:
|
||||
"The list of attributes and relationships to accept. Defaults to all attributes on the resource"
|
||||
],
|
||||
primary?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
defmodule Ash.Resource.Actions.Update do
|
||||
@moduledoc "Represents a update action on a resource."
|
||||
|
||||
defstruct [:name, :primary?, type: :update]
|
||||
defstruct [:name, :primary?, :accept, type: :update]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :update,
|
||||
name: atom,
|
||||
accept: [atom],
|
||||
primary?: boolean
|
||||
}
|
||||
|
||||
|
@ -14,6 +15,11 @@ defmodule Ash.Resource.Actions.Update do
|
|||
type: :atom,
|
||||
doc: "The name of the action"
|
||||
],
|
||||
accept: [
|
||||
type: {:custom, Ash.OptionsHelpers, :list_of_atoms, []},
|
||||
doc:
|
||||
"The list of attributes and relationships to accept. Defaults to all attributes on the resource"
|
||||
],
|
||||
primary?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
|
|
|
@ -58,9 +58,14 @@ defmodule Ash.Test.Actions.CreateTest do
|
|||
end
|
||||
|
||||
actions do
|
||||
read(:default)
|
||||
create(:default)
|
||||
update(:default)
|
||||
read :default
|
||||
create :default, primary?: true
|
||||
|
||||
create :only_allow_name do
|
||||
accept([:name])
|
||||
end
|
||||
|
||||
update :default
|
||||
end
|
||||
|
||||
attributes do
|
||||
|
@ -201,6 +206,24 @@ defmodule Ash.Test.Actions.CreateTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "accept" do
|
||||
test "allows using attributes in the list" do
|
||||
Author
|
||||
|> new()
|
||||
|> change_attribute(:name, "fred")
|
||||
|> Api.create!(action: :only_allow_name)
|
||||
end
|
||||
|
||||
test "it prevents using attributes not in the list" do
|
||||
assert_raise Ash.Error.Invalid, ~r/Invalid value provided for bio: Cannot be changed/, fn ->
|
||||
Author
|
||||
|> new()
|
||||
|> change_attribute(:bio, "foo")
|
||||
|> Api.create!(action: :only_allow_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating many to many relationships" do
|
||||
test "allows creating with a many_to_many relationship" do
|
||||
post2 =
|
||||
|
|
|
@ -58,12 +58,17 @@ defmodule Ash.Test.Actions.UpdateTest do
|
|||
actions do
|
||||
read :default
|
||||
create :default
|
||||
update :default
|
||||
update :default, primary?: true
|
||||
|
||||
update :only_allow_name do
|
||||
accept([:name])
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
attribute :id, :uuid, primary_key?: true, default: &Ecto.UUID.generate/0
|
||||
attribute :name, :string
|
||||
attribute :bio, :string
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
@ -156,6 +161,32 @@ defmodule Ash.Test.Actions.UpdateTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "allow" do
|
||||
test "allows attributes in the list" do
|
||||
author =
|
||||
Author
|
||||
|> new(%{name: "fred"})
|
||||
|> Api.create!()
|
||||
|
||||
author
|
||||
|> new(%{name: "joe"})
|
||||
|> Api.update!(action: :only_allow_name)
|
||||
end
|
||||
|
||||
test "does not allow attributes in the list" do
|
||||
author =
|
||||
Author
|
||||
|> new(%{name: "fred"})
|
||||
|> Api.create!()
|
||||
|
||||
assert_raise Ash.Error.Invalid, ~r/Invalid value provided for bio: Cannot be changed/, fn ->
|
||||
author
|
||||
|> new(%{bio: "bio"})
|
||||
|> Api.update!(action: :only_allow_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating many to many relationships" do
|
||||
test "allows updating with a many_to_many relationship" do
|
||||
post =
|
||||
|
|
Loading…
Reference in a new issue