feat: add groups resource #371
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
8e9fbe76cf
commit
6db64bf996
12 changed files with 742 additions and 18 deletions
128
lib/membership/member_group.ex
Normal file
128
lib/membership/member_group.ex
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
defmodule Mv.Membership.MemberGroup do
|
||||
@moduledoc """
|
||||
Ash resource representing the join table for the many-to-many relationship
|
||||
between Members and Groups.
|
||||
|
||||
## Overview
|
||||
MemberGroup is a join table that links members to groups. It enables the
|
||||
many-to-many relationship where:
|
||||
- A member can belong to multiple groups
|
||||
- A group can contain multiple members
|
||||
|
||||
## Attributes
|
||||
- `member_id` - Foreign key to Member (required)
|
||||
- `group_id` - Foreign key to Group (required)
|
||||
|
||||
## Relationships
|
||||
- `belongs_to :member` - Relationship to Member
|
||||
- `belongs_to :group` - Relationship to Group
|
||||
|
||||
## Constraints
|
||||
- Unique constraint on `(member_id, group_id)` - prevents duplicate memberships
|
||||
- CASCADE delete: Removing member removes all group associations
|
||||
- CASCADE delete: Removing group removes all member associations
|
||||
|
||||
## Examples
|
||||
# Add member to group
|
||||
MemberGroup.create!(%{member_id: member.id, group_id: group.id})
|
||||
|
||||
# Remove member from group
|
||||
member_group = MemberGroup.get_by_member_and_group!(member.id, group.id)
|
||||
MemberGroup.destroy!(member_group)
|
||||
"""
|
||||
use Ash.Resource,
|
||||
domain: Mv.Membership,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
||||
require Ash.Query
|
||||
import Ash.Expr
|
||||
|
||||
postgres do
|
||||
table "member_groups"
|
||||
repo Mv.Repo
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
accept [:member_id, :group_id]
|
||||
end
|
||||
end
|
||||
|
||||
validations do
|
||||
validate present(:member_id)
|
||||
validate present(:group_id)
|
||||
|
||||
# Prevent duplicate associations
|
||||
validate fn changeset, _context ->
|
||||
member_id = Ash.Changeset.get_attribute(changeset, :member_id)
|
||||
group_id = Ash.Changeset.get_attribute(changeset, :group_id)
|
||||
current_id = Ash.Changeset.get_attribute(changeset, :id)
|
||||
|
||||
if member_id && group_id do
|
||||
check_duplicate_association(member_id, group_id, current_id)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_v7_primary_key :id
|
||||
|
||||
attribute :member_id, :uuid do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
attribute :group_id, :uuid do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :member, Mv.Membership.Member do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
belongs_to :group, Mv.Membership.Group do
|
||||
allow_nil? false
|
||||
end
|
||||
end
|
||||
|
||||
identities do
|
||||
identity :unique_member_group, [:member_id, :group_id]
|
||||
end
|
||||
|
||||
# Private helper function to check for duplicate associations
|
||||
defp check_duplicate_association(member_id, group_id, exclude_id) do
|
||||
alias Mv.Helpers
|
||||
alias Mv.Helpers.SystemActor
|
||||
|
||||
query =
|
||||
Mv.Membership.MemberGroup
|
||||
|> Ash.Query.filter(member_id == ^member_id and group_id == ^group_id)
|
||||
|> maybe_exclude_id(exclude_id)
|
||||
|
||||
system_actor = SystemActor.get_system_actor()
|
||||
opts = Helpers.ash_actor_opts(system_actor)
|
||||
|
||||
case Ash.read(query, opts) do
|
||||
{:ok, []} ->
|
||||
:ok
|
||||
|
||||
{:ok, _} ->
|
||||
{:error, field: :member_id, message: "Member is already in this group", value: member_id}
|
||||
|
||||
{:error, _reason} ->
|
||||
# Fail-open: if query fails, allow operation to proceed
|
||||
# Database constraint will catch duplicates anyway
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_exclude_id(query, nil), do: query
|
||||
defp maybe_exclude_id(query, id), do: Ash.Query.filter(query, id != ^id)
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue