Optimize member count queries to avoid N+1 problem
Load all member counts in a single query during mount. Counts are stored in assigns as a map and retrieved without additional queries.
This commit is contained in:
parent
46af6bbbed
commit
18766df224
1 changed files with 36 additions and 12 deletions
|
|
@ -18,15 +18,20 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|
|||
|
||||
alias Mv.MembershipFees
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias Mv.Membership
|
||||
alias Mv.Membership.Member
|
||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
fee_types = load_membership_fee_types()
|
||||
member_counts = load_member_counts(fee_types)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, gettext("Membership Fee Types"))
|
||||
|> assign(:membership_fee_types, load_membership_fee_types())}
|
||||
|> assign(:membership_fee_types, fee_types)
|
||||
|> assign(:member_counts, member_counts)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -66,7 +71,7 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|
|||
</:col>
|
||||
|
||||
<:col :let={mft} label={gettext("Members")}>
|
||||
<span class="badge badge-ghost">{get_member_count(mft)}</span>
|
||||
<span class="badge badge-ghost">{get_member_count(mft, @member_counts)}</span>
|
||||
</:col>
|
||||
|
||||
<:action :let={mft}>
|
||||
|
|
@ -82,18 +87,20 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|
|||
data-confirm={gettext("Are you sure?")}
|
||||
class={[
|
||||
"btn btn-ghost btn-xs",
|
||||
if(get_member_count(mft) > 0,
|
||||
if(get_member_count(mft, @member_counts) > 0,
|
||||
do: "text-error opacity-50 cursor-not-allowed",
|
||||
else: "text-error"
|
||||
)
|
||||
]}
|
||||
title={
|
||||
if get_member_count(mft) > 0,
|
||||
if get_member_count(mft, @member_counts) > 0,
|
||||
do:
|
||||
gettext("Cannot delete - %{count} member(s) assigned", count: get_member_count(mft)),
|
||||
gettext("Cannot delete - %{count} member(s) assigned",
|
||||
count: get_member_count(mft, @member_counts)
|
||||
),
|
||||
else: gettext("Delete")
|
||||
}
|
||||
disabled={get_member_count(mft) > 0}
|
||||
disabled={get_member_count(mft, @member_counts) > 0}
|
||||
>
|
||||
<.icon name="hero-trash" class="size-4" />
|
||||
</button>
|
||||
|
|
@ -112,10 +119,12 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|
|||
case Ash.destroy(fee_type, domain: MembershipFees) do
|
||||
:ok ->
|
||||
updated_types = Enum.reject(socket.assigns.membership_fee_types, &(&1.id == id))
|
||||
updated_counts = Map.delete(socket.assigns.member_counts, id)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:membership_fee_types, updated_types)
|
||||
|> assign(:member_counts, updated_counts)
|
||||
|> put_flash(:info, gettext("Membership fee type deleted"))}
|
||||
|
||||
{:error, error} ->
|
||||
|
|
@ -131,12 +140,27 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|
|||
|> Ash.read!(domain: MembershipFees)
|
||||
end
|
||||
|
||||
defp get_member_count(fee_type) do
|
||||
# Count members with this fee type
|
||||
case Ash.count(Member |> Ash.Query.filter(membership_fee_type_id == ^fee_type.id)) do
|
||||
{:ok, count} -> count
|
||||
_ -> 0
|
||||
# Loads all member counts for fee types in a single query to avoid N+1 queries
|
||||
defp load_member_counts(fee_types) do
|
||||
fee_type_ids = Enum.map(fee_types, & &1.id)
|
||||
|
||||
# Load all members with membership_fee_type_id in a single query
|
||||
members =
|
||||
Member
|
||||
|> Ash.Query.filter(membership_fee_type_id in ^fee_type_ids)
|
||||
|> Ash.Query.select([:membership_fee_type_id])
|
||||
|> Ash.read!(domain: Membership)
|
||||
|
||||
# Group by membership_fee_type_id and count
|
||||
members
|
||||
|> Enum.group_by(& &1.membership_fee_type_id)
|
||||
|> Enum.map(fn {fee_type_id, members_list} -> {fee_type_id, length(members_list)} end)
|
||||
|> Map.new()
|
||||
end
|
||||
|
||||
# Gets member count from preloaded assigns map
|
||||
defp get_member_count(fee_type, member_counts) do
|
||||
Map.get(member_counts, fee_type.id, 0)
|
||||
end
|
||||
|
||||
defp format_error(%Ash.Error.Invalid{} = error) do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue