If you define a belongs_to relationship with allow_nil? true
, the default create action becomes unusable; any attempt to use it will fail because the relationship’s attribute is not set.
Consider the following resource with a belongs_to relationship:
defmodule Helpdesk.Support.Ticket do
use Ash.Resource
actions do
defaults [:create, :read, :update, :destroy]
end
attributes do
uuid_primary_key :id
attribute :subject, :string
end
relationships do
belongs_to :representative, Helpdesk.Support.Representative do
allow_nil? false
end
end
end
One would assume that this would allow the user to create a Ticket by passing a Representative in the input to the create action, like:
Helpdesk.Support.Ticket |> Ash.Changeset.for_create(:create, %{representative: rep}) |> Helpdesk.Support.create!()
But this raises an error that “relationship representative is required”, despite it being provided.
To add to the confusion, this is inconsistent with the behaviour in other situations. For example Ash.Seed.seed!(%Helpdesk.Support.Ticket{representative: rep})
will create the Ticket record as expected, with the relationship attribute inferred correctly. Or if Representative had an update action to create an associated Ticket:
defmodule Helpdesk.Support.Representative do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
actions do
defaults [:create, :read, :update, :destroy]
update :add_ticket do
argument :subject, :string
change manage_relationship(:subject, :tickets, value_is_key: :subject, type: :create)
end
end
attributes do
uuid_primary_key :id
attribute :name, :string
end
relationships do
has_many :tickets, Helpdesk.Support.Ticket
end
end
Calling the add_ticket
action on a Representative will successfully create a new related Ticket:
rep |> Ash.Changeset.for_update(:add_ticket, %{subject: "blah blah"}) |> Helpdesk.Support.update!()
#Helpdesk.Support.Representative<
tickets: [
#Helpdesk.Support.Ticket<
representative: #Ash.NotLoaded<:relationship, field: :representative>,
__meta__: #Ecto.Schema.Metadata<:built, "">,
id: "f403066a-cf0e-43f8-a3f4-b40fac8a7d27",
subject: "blah blah",
representative_id: "bd0e5d0b-5903-4083-ab9f-3fb42790e220",
aggregates: %{},
calculations: %{},
...
>
],
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "bd0e5d0b-5903-4083-ab9f-3fb42790e220",
name: "test",
aggregates: %{},
calculations: %{},
...
>
This is doubly confusing because the add_ticket
update action uses Ticket’s default create action to create the new related Ticket, but in a way that user’s seemingly are not.
I know there are ways to get this to work, either by making the relationship attribute_writable? true
and passing the associated ID directly or by defining a custom create action that uses manage_relationship
, but neither of those are intuitive solutions. I think the default create action should be able to handle passing the related record as part of the input without needing any extra code.
This is using ash version 2.19.9.