No actor in after_action context

I wondering what is the correct or recommended way of passing the actor down to a after_change callback. I've tried to use the context but the actor was always nil. I've ended up doing this:
actions do
defaults [:read, :destroy]

create :create do
primary? true
accept [:name, :description, :metadata, :warehouse_id]

validate present(:name)
validate present(:warehouse_id)

change after_action(fn changeset, model, _context ->
# Access actor from the correct location in changeset.context
case get_in(changeset.context, [:private, :actor]) do
nil ->
{:error, "Actor context is required for creating initial model version"}

actor ->
# Create initial empty version
empty_model = %{
"entities" => [],
"entity_relationships" => [],
"metadata" => %{},
"version_notes" => "Initial empty model"
}

_version =
Astrobee.DataPipeline.ModelVersion
|> Ash.Changeset.for_create(:create, %{
ontology_id: model.id,
model: empty_model,
commit_message: "Initial version"
})
|> Ash.create!(actor: actor)

{:ok, model}
end
end)
end
end
actions do
defaults [:read, :destroy]

create :create do
primary? true
accept [:name, :description, :metadata, :warehouse_id]

validate present(:name)
validate present(:warehouse_id)

change after_action(fn changeset, model, _context ->
# Access actor from the correct location in changeset.context
case get_in(changeset.context, [:private, :actor]) do
nil ->
{:error, "Actor context is required for creating initial model version"}

actor ->
# Create initial empty version
empty_model = %{
"entities" => [],
"entity_relationships" => [],
"metadata" => %{},
"version_notes" => "Initial empty model"
}

_version =
Astrobee.DataPipeline.ModelVersion
|> Ash.Changeset.for_create(:create, %{
ontology_id: model.id,
model: empty_model,
commit_message: "Initial version"
})
|> Ash.create!(actor: actor)

{:ok, model}
end
end)
end
end
But I'm not sure if I'm doing it properly and I'm missing something here?
5 Replies
Meeq
Meeq•2w ago
actor should be in the third argument of the after_action callback, which you are discarding as _context
edgar
edgarOP•2w ago
I tried that before, but I was always getting actor: nil from it.
[lib/astrobee/data_pipeline/resources/ontology.ex:38: Astrobee.DataPipeline.Model.change_after_action_0_generated_3F28CBE9B8B360BC05BEBC79FFACFD9C/3]
_context #=> %Ash.Resource.Change.Context{
actor: nil,
tenant: nil,
authorize?: true,
tracer: nil,
bulk?: false,
source_context: %{
private: %{
upsert?: false,
upsert_fields: nil,
upsert_identity: nil,
upsert_condition: nil,
return_skipped_upsert?: false,
authorize?: true
}
}
}

4) test create creates model with valid attributes (Astrobee.DataPipeline.ModelTest)
test/astrobee/data_pipeline/model_test.exs:10
match (=) failed
code: assert {:ok, model} = Model |> Ash.Changeset.for_create(:create, attrs) |> Ash.create(actor: user)
left: {:ok, model}
right: {
:error,
%Ash.Error.Invalid{bread_crumbs: ["Error returned from: Astrobee.DataPipeline.Model.create"], changeset: "#Changeset<>", errors: [%Ash.Error.Changes.InvalidChanges{fields: nil, message: "Actor context is required for creating initial model version", validation: nil, value: nil, splode: Ash.Error, bread_crumbs: ["Error returned from: Astrobee.DataPipeline.Model.create"], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :invalid}]}
}
stacktrace:
test/astrobee/data_pipeline/model_test.exs:41: (test)
[lib/astrobee/data_pipeline/resources/ontology.ex:38: Astrobee.DataPipeline.Model.change_after_action_0_generated_3F28CBE9B8B360BC05BEBC79FFACFD9C/3]
_context #=> %Ash.Resource.Change.Context{
actor: nil,
tenant: nil,
authorize?: true,
tracer: nil,
bulk?: false,
source_context: %{
private: %{
upsert?: false,
upsert_fields: nil,
upsert_identity: nil,
upsert_condition: nil,
return_skipped_upsert?: false,
authorize?: true
}
}
}

4) test create creates model with valid attributes (Astrobee.DataPipeline.ModelTest)
test/astrobee/data_pipeline/model_test.exs:10
match (=) failed
code: assert {:ok, model} = Model |> Ash.Changeset.for_create(:create, attrs) |> Ash.create(actor: user)
left: {:ok, model}
right: {
:error,
%Ash.Error.Invalid{bread_crumbs: ["Error returned from: Astrobee.DataPipeline.Model.create"], changeset: "#Changeset<>", errors: [%Ash.Error.Changes.InvalidChanges{fields: nil, message: "Actor context is required for creating initial model version", validation: nil, value: nil, splode: Ash.Error, bread_crumbs: ["Error returned from: Astrobee.DataPipeline.Model.create"], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :invalid}]}
}
stacktrace:
test/astrobee/data_pipeline/model_test.exs:41: (test)
the test was assert {:ok, model} = Model |> Ash.Changeset.for_create(:create, attrs) |> Ash.create(actor: user) and it works if I get it from changeset.context but clearly that :private there doesn't feel right 😛
ZachDaniel
ZachDaniel•2w ago
Pass the actor option when building the changeset If you don't it won't be available in the context
edgar
edgarOP•3d ago
Thanks @Zach!!! That fixed the issue. Later I also found it some reference in the docs . Wondering if either removing the Ash.<action>(actor: ...) configuration or finding a way to bypass it to the incoming changeset would be possible. IMO the current configuration makes a bit obscure because it may not work depending on how the underlaying action was configured.
ZachDaniel
ZachDaniel•3d ago
We can possibly warn or raise if a different, non-nil actor is passed there than what was originally provided.

Did you find this page helpful?