Need to ensure that only 1 row for a query criteria has a specific column value

I have a Campaign and that Campaign has multiple Crew Members. Only one of these Crew Members can have the is_leader column set to true. I think what I need is to be able to UPDATE crew_members SET is_leader = true WHERE campaign_id = $1, and then update the specific Crew Member. I haven't really seen the ideomatic way of doing this in Ash.
7 Replies
AngryBadger
AngryBadgerOP5w ago
I think that's only part of the problem. The specific part I'm struggling with is when I want to switch which crew member is the leader. identities just helps enforce uniqueness, right?
franckstifler
franckstifler5w ago
yes that's what they do. I would use a bulk_update in my action that sets every person that is true in that campain to false, in a before_action.
AngryBadger
AngryBadgerOP5w ago
Here's what I came up with but it feels super heavy-handed:
defmodule FiveApps.Campaigns.Changes.EnsureSingleLeader do
@moduledoc """
Ensures only one crew member can be leader per campaign.

When a crew member's is_leader is set to true, all other crew members
in the same campaign have their is_leader flag set to false.
"""

use Ash.Resource.Change

@impl true
def change(changeset, _opts, _context) do
case Ash.Changeset.get_attribute(changeset, :is_leader) do
true ->
# Get the campaign_id from the current record
campaign_id =
Ash.Changeset.get_attribute(changeset, :campaign_id) ||
changeset.data.campaign_id

if campaign_id do
Ash.Changeset.after_action(changeset, fn _changeset, record ->
clear_other_leaders(record, campaign_id)
{:ok, record}
end)
else
changeset
end

_other ->
changeset
end
end

defp clear_other_leaders(current_crew_member, campaign_id) do
FiveApps.Campaigns.CrewMember
|> Ash.Query.filter(
campaign_id == ^campaign_id and
id != ^current_crew_member.id and
is_leader == true
)
|> Ash.read!()
|> Enum.each(fn crew_member ->
crew_member
|> Ash.Changeset.for_update(:update, %{is_leader: false})
|> Ash.update!()
end)
end
end
defmodule FiveApps.Campaigns.Changes.EnsureSingleLeader do
@moduledoc """
Ensures only one crew member can be leader per campaign.

When a crew member's is_leader is set to true, all other crew members
in the same campaign have their is_leader flag set to false.
"""

use Ash.Resource.Change

@impl true
def change(changeset, _opts, _context) do
case Ash.Changeset.get_attribute(changeset, :is_leader) do
true ->
# Get the campaign_id from the current record
campaign_id =
Ash.Changeset.get_attribute(changeset, :campaign_id) ||
changeset.data.campaign_id

if campaign_id do
Ash.Changeset.after_action(changeset, fn _changeset, record ->
clear_other_leaders(record, campaign_id)
{:ok, record}
end)
else
changeset
end

_other ->
changeset
end
end

defp clear_other_leaders(current_crew_member, campaign_id) do
FiveApps.Campaigns.CrewMember
|> Ash.Query.filter(
campaign_id == ^campaign_id and
id != ^current_crew_member.id and
is_leader == true
)
|> Ash.read!()
|> Enum.each(fn crew_member ->
crew_member
|> Ash.Changeset.for_update(:update, %{is_leader: false})
|> Ash.update!()
end)
end
end
franckstifler
franckstifler5w ago
I see. just a few updates on your clrea_other_leaders. You don't have to do Enum.each you can batch the update And you don't even need to read with bulk_updates
defp clear_other_leaders(current_crew_member, campaign_id) do
FiveApps.Campaigns.CrewMember
|> Ash.Query.filter(
campaign_id == ^campaign_id and
id != ^current_crew_member.id and
is_leader == true
)
|> Ash.bulk_update!(:update, %{is_leader: false})
end
defp clear_other_leaders(current_crew_member, campaign_id) do
FiveApps.Campaigns.CrewMember
|> Ash.Query.filter(
campaign_id == ^campaign_id and
id != ^current_crew_member.id and
is_leader == true
)
|> Ash.bulk_update!(:update, %{is_leader: false})
end
try this, I think it should work and be more performant.
AngryBadger
AngryBadgerOP5w ago
Hm, I might even be able to make it simpler with a specific action. Like if I had update :set_leader do
franckstifler
franckstifler5w ago
sorry? Does the code you sent works currently? When I see you have an after_action that sets all leaders true to false. I see. You filter out the current_crew_member in the query. Should be good.

Did you find this page helpful?