refactor: reduce complexity of with_advisory_lock function
All checks were successful
continuous-integration/drone/push Build is passing

Split the complex with_advisory_lock function into smaller, focused
functions to improve readability and reduce cyclomatic complexity:

- with_advisory_lock_in_transaction: Handles case when already in transaction
- with_advisory_lock_new_transaction: Handles case when starting new transaction
- execute_within_transaction: Executes function within transaction
- normalize_fun_result: Normalizes function results to consistent format
- handle_transaction_result: Handles transaction results and sends notifications

This fixes Credo warnings:
- Function body nested too deep (was 3, now max 2)
- Function too complex (was 11, now max 9)
This commit is contained in:
Moritz 2025-12-15 12:33:20 +01:00
parent b81ed99571
commit 34aabaec47
Signed by: moritz
GPG key ID: 1020A035E5DD0824

View file

@ -190,29 +190,33 @@ defmodule Mv.MembershipFees.CycleGenerator do
# Check if we're already in a transaction (e.g., called from Ash action) # Check if we're already in a transaction (e.g., called from Ash action)
if Repo.in_transaction?() do if Repo.in_transaction?() do
with_advisory_lock_in_transaction(lock_key, fun)
else
with_advisory_lock_new_transaction(lock_key, fun)
end
end
# Already in transaction: use advisory lock directly without starting new transaction # Already in transaction: use advisory lock directly without starting new transaction
# This prevents nested transactions which can cause deadlocks # This prevents nested transactions which can cause deadlocks
defp with_advisory_lock_in_transaction(lock_key, fun) do
Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key])
normalize_fun_result(fun.())
case fun.() do
{:ok, result, notifications} when is_list(notifications) ->
# Notifications will be sent after the outer transaction commits
# Return in same format as non-transaction case for consistency
{:ok, result}
{:ok, result} ->
{:ok, result}
{:error, reason} ->
{:error, reason}
end end
else
# Not in transaction: start new transaction with advisory lock # Not in transaction: start new transaction with advisory lock
defp with_advisory_lock_new_transaction(lock_key, fun) do
result = result =
Repo.transaction(fn -> Repo.transaction(fn ->
# Acquire advisory lock for this transaction # Acquire advisory lock for this transaction
Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key])
execute_within_transaction(fun)
end)
handle_transaction_result(result)
end
# Execute function within transaction and return normalized result
defp execute_within_transaction(fun) do
case fun.() do case fun.() do
{:ok, result, notifications} when is_list(notifications) -> {:ok, result, notifications} when is_list(notifications) ->
# Return result and notifications separately # Return result and notifications separately
@ -225,22 +229,28 @@ defmodule Mv.MembershipFees.CycleGenerator do
{:error, reason} -> {:error, reason} ->
Repo.rollback(reason) Repo.rollback(reason)
end end
end) end
# Extract result and notifications, send notifications after transaction # Normalize function result to consistent format
case result do defp normalize_fun_result({:ok, result, _notifications}) do
{:ok, {cycles, notifications}} -> # Notifications will be sent after the outer transaction commits
# Return in same format as non-transaction case for consistency
{:ok, result}
end
defp normalize_fun_result({:ok, result}), do: {:ok, result}
defp normalize_fun_result({:error, reason}), do: {:error, reason}
# Handle transaction result and send notifications if needed
defp handle_transaction_result({:ok, {cycles, notifications}}) do
if Enum.any?(notifications) do if Enum.any?(notifications) do
Ash.Notifier.notify(notifications) Ash.Notifier.notify(notifications)
end end
{:ok, cycles} {:ok, cycles}
end
{:error, reason} -> defp handle_transaction_result({:error, reason}), do: {:error, reason}
{:error, reason}
end
end
end
defp do_generate_cycles(member, today) do defp do_generate_cycles(member, today) do
# Reload member with relationships to ensure fresh data # Reload member with relationships to ensure fresh data