refactor(elixir): hierarchical refactor
to extract common code patterns from category/storage location systems
This commit is contained in:
@@ -11,73 +11,25 @@ defmodule ComponentsElixir.Inventory do
|
||||
## Storage Locations
|
||||
|
||||
@doc """
|
||||
Returns the list of storage locations with computed hierarchy fields.
|
||||
Returns the list of storage locations with optimized parent preloading.
|
||||
Preloads up to 5 levels of parent associations to minimize database queries in full_path calculations.
|
||||
"""
|
||||
def list_storage_locations do
|
||||
# Get all locations with preloaded parents in a single query
|
||||
locations =
|
||||
StorageLocation
|
||||
|> order_by([sl], asc: sl.name)
|
||||
|> preload(:parent)
|
||||
|> preload([parent: [parent: [parent: [parent: :parent]]]])
|
||||
|> Repo.all()
|
||||
|
||||
# Compute hierarchy fields for all locations efficiently
|
||||
processed_locations =
|
||||
compute_hierarchy_fields_batch(locations)
|
||||
|> Enum.sort_by(&{&1.level, &1.name})
|
||||
|
||||
# Ensure AprilTag SVGs exist for all locations
|
||||
spawn(fn ->
|
||||
ComponentsElixir.AprilTag.generate_all_apriltag_svgs()
|
||||
end)
|
||||
|
||||
processed_locations
|
||||
locations
|
||||
end
|
||||
|
||||
# Efficient batch computation of hierarchy fields
|
||||
defp compute_hierarchy_fields_batch(locations) do
|
||||
# Create a map for quick parent lookup to avoid N+1 queries
|
||||
location_map = Map.new(locations, fn loc -> {loc.id, loc} end)
|
||||
|
||||
Enum.map(locations, fn location ->
|
||||
level = compute_level_efficient(location, location_map, 0)
|
||||
path = compute_path_efficient(location, location_map, 0)
|
||||
|
||||
%{location | level: level, path: path}
|
||||
end)
|
||||
end
|
||||
|
||||
defp compute_level_efficient(%{parent_id: nil}, _location_map, _depth), do: 0
|
||||
|
||||
defp compute_level_efficient(%{parent_id: parent_id}, location_map, depth) when depth < 10 do
|
||||
case Map.get(location_map, parent_id) do
|
||||
# Orphaned record
|
||||
nil -> 0
|
||||
parent -> 1 + compute_level_efficient(parent, location_map, depth + 1)
|
||||
end
|
||||
end
|
||||
|
||||
# Prevent infinite recursion
|
||||
defp compute_level_efficient(_location, _location_map, _depth), do: 0
|
||||
|
||||
defp compute_path_efficient(%{parent_id: nil, name: name}, _location_map, _depth), do: name
|
||||
|
||||
defp compute_path_efficient(%{parent_id: parent_id, name: name}, location_map, depth)
|
||||
when depth < 10 do
|
||||
case Map.get(location_map, parent_id) do
|
||||
# Orphaned record
|
||||
nil ->
|
||||
name
|
||||
|
||||
parent ->
|
||||
parent_path = compute_path_efficient(parent, location_map, depth + 1)
|
||||
"#{parent_path}/#{name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Prevent infinite recursion
|
||||
defp compute_path_efficient(%{name: name}, _location_map, _depth), do: name
|
||||
|
||||
@doc """
|
||||
Returns the list of root storage locations (no parent).
|
||||
"""
|
||||
@@ -89,37 +41,12 @@ defmodule ComponentsElixir.Inventory do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single storage location with computed hierarchy fields.
|
||||
Gets a single storage location with preloaded associations.
|
||||
"""
|
||||
def get_storage_location!(id) do
|
||||
location =
|
||||
StorageLocation
|
||||
|> preload(:parent)
|
||||
|> Repo.get!(id)
|
||||
|
||||
# Compute hierarchy fields
|
||||
level = compute_level_for_single(location)
|
||||
path = compute_path_for_single(location)
|
||||
%{location | level: level, path: path}
|
||||
end
|
||||
|
||||
# Simple computation for single location (allows DB queries)
|
||||
defp compute_level_for_single(%{parent_id: nil}), do: 0
|
||||
|
||||
defp compute_level_for_single(%{parent_id: parent_id}) do
|
||||
case Repo.get(StorageLocation, parent_id) do
|
||||
nil -> 0
|
||||
parent -> 1 + compute_level_for_single(parent)
|
||||
end
|
||||
end
|
||||
|
||||
defp compute_path_for_single(%{parent_id: nil, name: name}), do: name
|
||||
|
||||
defp compute_path_for_single(%{parent_id: parent_id, name: name}) do
|
||||
case Repo.get(StorageLocation, parent_id) do
|
||||
nil -> name
|
||||
parent -> "#{compute_path_for_single(parent)}/#{name}"
|
||||
end
|
||||
StorageLocation
|
||||
|> preload(:parent)
|
||||
|> Repo.get!(id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -130,15 +57,6 @@ defmodule ComponentsElixir.Inventory do
|
||||
|> where([sl], sl.apriltag_id == ^apriltag_id)
|
||||
|> preload(:parent)
|
||||
|> Repo.one()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
location ->
|
||||
level = compute_level_for_single(location)
|
||||
path = compute_path_for_single(location)
|
||||
%{location | level: level, path: path}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -221,7 +139,7 @@ defmodule ComponentsElixir.Inventory do
|
||||
def compute_storage_location_path(nil), do: nil
|
||||
|
||||
def compute_storage_location_path(%StorageLocation{} = location) do
|
||||
compute_path_for_single(location)
|
||||
StorageLocation.full_path(location)
|
||||
end
|
||||
|
||||
# Convert string keys to atoms for consistency
|
||||
@@ -239,11 +157,12 @@ defmodule ComponentsElixir.Inventory do
|
||||
## Categories
|
||||
|
||||
@doc """
|
||||
Returns the list of categories.
|
||||
Returns the list of categories with optimized parent preloading.
|
||||
Preloads up to 5 levels of parent associations to minimize database queries in full_path calculations.
|
||||
"""
|
||||
def list_categories do
|
||||
Category
|
||||
|> preload(:parent)
|
||||
|> preload([parent: [parent: [parent: [parent: :parent]]]])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user