Relate_actor results in "could not relate to actor, as no actor was found (and :allow_nil? is false)

I’m trying to have User.ex :create action to also create a default Calendar as soon as the user account is created. I thought passing actor in API.create will make it available in Calendar’s create action, but it doesn’t seem to work. Any idea why it’s not working?

<user create action>
  change fn changeset, _ ->
    Ash.Changeset.after_action(changeset, fn changeset, result ->
      first_name = Ash.Changeset.get_attribute(changeset, :first_name)
      IO.inspect(changeset.attributes, label: "changeset after action #########")

      Tasks.Calendar
      |> Ash.Changeset.for_create(:create, %{
        calendar_name: "#{first_name}'s First Calendar",
        created_by_id: Ash.Changeset.get_attribute(changeset, :id)
      })
      |> Tasks.create(actor: changeset.attributes)
      |> IO.inspect(label: "calendar created #########")

    end)
  end

<calendar.ex>
create :create do
  primary? true

  change relate_actor(:created_by)

end

Hi @jason.o - welcome to the forum :wave:

Okay, so the first thing I’d point out is that you should be using result as your actor for creating the calendar - it’s the actual user record. Secondly your after_action function must return an ok/error tuple containing the result.

change fn changeset, _ ->
  Ash.Changeset.after_action(changeset, fn changeset, result ->
    calendar_changeset = Taskcalendars.Tasks.Calendar
      |> Ash.Changeset.for_create(:create, %{calendar_name: "#{result.first_name}'s First Calendar"})

    with {:ok, _calendar} <- Tasks.create(calendar_changeset, actor: result) do
      {:ok, result
    end
  end)
end

Additionally, can you provide more information about the failure? Is there an error or backtrace for example?

Thank you. I updated the code as you showed above.

Here is the stacktrace.

[error] Something went wrong in authentication

activity: {:password, :register}

reason: ** (Ash.Error.Invalid) Input Invalid

* Invalid value provided for created_by: could not relate to actor, as no actor was found (and :allow_nil? is false).
  (ash 2.15.1) lib/ash/resource/change/relate_actor.ex:23: Ash.Resource.Change.RelateActor.change/3
  (ash 2.15.1) lib/ash/changeset/changeset.ex:1237: anonymous fn/6 in Ash.Changeset.run_action_changes/6
  (elixir 1.15.5) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
  (ash 2.15.1) lib/ash/changeset/changeset.ex:876: Ash.Changeset.do_for_action/4
 (taskcalendars 0.1.1) lib/taskcalendars/accounts/resources/user.ex:263: anonymous fn/2 in Taskcalendars.Accounts.User.change_0_generated_A68D3D8E2E534404B67DE1C0176366CF/2
  (ash 2.15.1) lib/ash/changeset/changeset.ex:2223: anonymous fn/2 in Ash.Changeset.run_after_actions/3
  (elixir 1.15.5) lib/enum.ex:4830: Enumerable.List.reduce/3
  (elixir 1.15.5) lib/enum.ex:2564: Enum.reduce_while/3
  (ash 2.15.1) lib/ash/changeset/changeset.ex:1810: anonymous fn/2 in Ash.Changeset.with_hooks/3
  (ecto_sql 3.10.2) lib/ecto/adapters/sql.ex:1352: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
  (db_connection 2.5.0) lib/db_connection.ex:1630: DBConnection.run_transaction/4
  (ash 2.15.1) lib/ash/changeset/changeset.ex:1808: anonymous fn/3 in Ash.Changeset.with_hooks/3
  (ash 2.15.1) lib/ash/changeset/changeset.ex:1928: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
  (ash 2.15.1) lib/ash/changeset/changeset.ex:1790: Ash.Changeset.with_hooks/3
  (ash 2.15.1) lib/ash/actions/create/create.ex:340: anonymous fn/11 in Ash.Actions.Create.as_requests/5
  (ash 2.15.1) lib/ash/engine/request.ex:1111: Ash.Engine.Request.do_try_resolve_local/4
  (ash 2.15.1) lib/ash/engine/request.ex:284: Ash.Engine.Request.do_next/1
  (ash 2.15.1) lib/ash/engine/request.ex:213: Ash.Engine.Request.next/1
    (ash 2.15.1) lib/ash/error/error.ex:477: Ash.Error.choose_error/2
    (ash 2.15.1) lib/ash/error/error.ex:225: Ash.Error.to_error_class/2
    (ash 2.15.1) lib/ash/actions/create/create.ex:135: Ash.Actions.Create.do_run/4
    (ash 2.15.1) lib/ash/actions/create/create.ex:38: Ash.Actions.Create.run/4
    (taskcalendars 0.1.1) lib/taskcalendars/tasks/tasks.ex:1: Taskcalendars.Tasks.create/2
    (taskcalendars 0.1.1) lib/taskcalendars/accounts/resources/user.ex:268: anonymous fn/2 in Taskcalendars.Accounts.User.change_0_generated_A68D3D8E2E534404B67DE1C0176366CF/2
    (ash 2.15.1) lib/ash/changeset/changeset.ex:2223: anonymous fn/2 in Ash.Changeset.run_after_actions/3

Okay, rather than passing the actor in in the Task.create options, can you try passing it as an option to Ash.Changeset.for_create instead?

That did the trick! Thanks. So, is there a general rule of thumb about when do I put actor in Ash.Changeset.for_create vs. API.create?

Actually I think it’s just a bug that needs to be fixed.

1 Like

In this case I don’t think it is a bug. If you use a change that needs the actor, actor has to be specified when creating the changeset. i.e

Resource
|> Ash.Changeset.for_create(...., actor; actor)
|> Api.create!()

If you don’t have any of those changes, then

Resource
|> Ash.Changeset.for_create(....)
|> Api.create!(actor: actor)

will work. The former should be preferred in pretty much all cases, and the code interface will provide the actor in both calls.