Conditional Relationship Creation

I'm trying to create a profile when a user registers, but only if one doesn't already exist. I tried using manage_relationship/4 with type: :direct_control and it does create the profile, but if the user already has one - with more data fulfilled - it overwrites it with the partial available data at user registration — which is not ideal. Is manage_relationship/4 the right way to handle this? Or should I use an after_action hook to conditionally create the profile?
Solution:
That's right, this makes it ```elixir change after_action(fn changeset, user, context -> profile = Ash.load!(user, :profile) |> Map.fetch!(:profile)...
Jump to solution
6 Replies
ken-kost
ken-kost2mo ago
you probably want :create, not :direct_control for create it's
[
on_no_match: :create,
on_match: :ignore
]
[
on_no_match: :create,
on_match: :ignore
]
Joan Gavelán
Joan GavelánOP2mo ago
I tried that as well, but it creates new profiles 🤔 This is my action
create :register_with_oauth2 do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
argument :profile, :map, allow_nil?: false

upsert? true
upsert_identity :unique_email
upsert_fields []

change AshAuthentication.GenerateTokenChange

change fn changeset, _context ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)

Ash.Changeset.change_attributes(changeset, Map.take(user_info, ["email"]))
end

change manage_relationship(:profile, type: :create)

change set_attribute(:confirmed_at, &DateTime.utc_now/0)
end
create :register_with_oauth2 do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
argument :profile, :map, allow_nil?: false

upsert? true
upsert_identity :unique_email
upsert_fields []

change AshAuthentication.GenerateTokenChange

change fn changeset, _context ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)

Ash.Changeset.change_attributes(changeset, Map.take(user_info, ["email"]))
end

change manage_relationship(:profile, type: :create)

change set_attribute(:confirmed_at, &DateTime.utc_now/0)
end
barnabasj
barnabasj2mo ago
what exacly are you passing for a profile? Ash compares primary keys to see if the old/new values are the same by default.
Joan Gavelán
Joan GavelánOP2mo ago
profile: %{
"first_name" => credentials.user["given_name"],
"last_name" => credentials.user["family_name"],
"picture" => credentials.user["picture"]
}
profile: %{
"first_name" => credentials.user["given_name"],
"last_name" => credentials.user["family_name"],
"picture" => credentials.user["picture"]
}
I'm guessing Ash can't find a relation purely on that data. So what are my options considering the user may be registering for the first time, or signing in for an x time
barnabasj
barnabasj2mo ago
In that case I would probably just add an after_action that does the lookup/write or use an upsert on the fk on profile.
Solution
Joan Gavelán
Joan Gavelán2mo ago
That's right, this makes it
change after_action(fn changeset, user, context ->
profile = Ash.load!(user, :profile) |> Map.fetch!(:profile)

if is_nil(profile) do
MyApp.Users.create_profile!(
changeset.arguments.profile,
actor: user,
authorize?: false
)
end

{:ok, user}
end)
change after_action(fn changeset, user, context ->
profile = Ash.load!(user, :profile) |> Map.fetch!(:profile)

if is_nil(profile) do
MyApp.Users.create_profile!(
changeset.arguments.profile,
actor: user,
authorize?: false
)
end

{:ok, user}
end)

Did you find this page helpful?