# Hierarchical Refactoring Plan ## Overview This document outlines the step-by-step plan to extract common hierarchical behavior from Categories and Storage Locations into a shared `Hierarchical` module, based on the superior category implementation approach. ## Goals 1. **Extract shared hierarchical patterns** into reusable modules 2. **Simplify storage locations** to match category elegance 3. **Maintain AprilTag-specific features** for storage locations 4. **Preserve all existing functionality** during refactor 5. **Improve performance and maintainability** ## Refactoring Strategy ### Phase 1: Extract Hierarchical Behavior Module ⏭️ **NEXT** Create shared behavior module based on category patterns ### Phase 2: Refactor Storage Locations 🔄 **FOLLOW-UP** Simplify storage locations to use new shared module ### Phase 3: Refactor Categories 🔄 **FOLLOW-UP** Update categories to use shared module ### Phase 4: Extract LiveView Components 🔄 **FOLLOW-UP** Create reusable UI components for hierarchical display --- ## Phase 1: Extract Hierarchical Behavior Module ### 1.1 Create Core Hierarchical Module **File**: `lib/components_elixir/inventory/hierarchical.ex` ```elixir defmodule ComponentsElixir.Inventory.Hierarchical do @moduledoc """ Shared hierarchical behavior for entities with parent-child relationships. This module provides common functionality for: - Path computation (e.g., "Parent > Child > Grandchild") - Cycle detection and prevention - Parent/child filtering for UI dropdowns - Tree traversal utilities Based on the elegant category implementation approach. """ @doc """ Computes full hierarchical path for an entity. Uses recursive traversal of parent chain. ## Examples iex> category = %Category{name: "Resistors", parent: %Category{name: "Electronics", parent: nil}} iex> Hierarchical.full_path(category, &(&1.parent)) "Electronics > Resistors" """ def full_path(entity, parent_accessor_fn, separator \\ " > ") @doc """ Filters entities to remove circular reference options for parent selection. Prevents an entity from being its own ancestor. ## Examples iex> categories = [%{id: 1, parent_id: nil}, %{id: 2, parent_id: 1}] iex> Hierarchical.filter_parent_options(categories, 1, &(&1.id), &(&1.parent_id)) [%{id: 2, parent_id: 1}] # ID 1 filtered out (self-reference) """ def filter_parent_options(entities, editing_entity_id, id_accessor_fn, parent_id_accessor_fn) @doc """ Checks if an entity is a descendant of an ancestor entity. Used for cycle detection in parent selection. """ def is_descendant?(entities, descendant_id, ancestor_id, parent_id_accessor_fn) @doc """ Gets all root entities (entities with no parent). """ def root_entities(entities, parent_id_accessor_fn) @doc """ Gets all child entities of a specific parent. """ def child_entities(entities, parent_id, parent_id_accessor_fn) @doc """ Generates display name for entity including parent context. For dropdown displays: "Parent > Child" """ def display_name(entity, parent_accessor_fn, separator \\ " > ") end ``` ### 1.2 Schema Behaviour Definition **File**: `lib/components_elixir/inventory/hierarchical_schema.ex` ```elixir defmodule ComponentsElixir.Inventory.HierarchicalSchema do @moduledoc """ Behaviour for schemas that implement hierarchical relationships. Provides a contract for entities with parent-child relationships. """ @callback full_path(struct()) :: String.t() @callback parent(struct()) :: struct() | nil @callback children(struct()) :: [struct()] defmacro __using__(_opts) do quote do @behaviour ComponentsElixir.Inventory.HierarchicalSchema import ComponentsElixir.Inventory.Hierarchical end end end ``` ### 1.3 Update Category Schema **File**: `lib/components_elixir/inventory/category.ex` ```elixir defmodule ComponentsElixir.Inventory.Category do use ComponentsElixir.Inventory.HierarchicalSchema # ... existing schema definition @impl true def full_path(%Category{} = category) do ComponentsElixir.Inventory.Hierarchical.full_path(category, &(&1.parent)) end @impl true def parent(%Category{parent: parent}), do: parent @impl true def children(%Category{children: children}), do: children end ``` ## Phase 2: Refactor Storage Locations ### 2.1 Simplify Storage Location Schema **Changes to**: `lib/components_elixir/inventory/storage_location.ex` 1. **Remove virtual fields**: `level` and `path` 2. **Add category-style path function** 3. **Remove complex cycle validation** 4. **Keep AprilTag-specific features** ```elixir defmodule ComponentsElixir.Inventory.StorageLocation do use ComponentsElixir.Inventory.HierarchicalSchema # ... existing schema without virtual fields @impl true def full_path(%StorageLocation{} = location) do ComponentsElixir.Inventory.Hierarchical.full_path(location, &(&1.parent), " / ") end # Keep AprilTag-specific functionality def apriltag_format(storage_location), do: storage_location.apriltag_id end ``` ### 2.2 Simplify Storage Location Changeset **Remove**: - `validate_no_circular_reference/1` function - Virtual field handling - Complex cycle detection **Keep**: - AprilTag validation - Basic field validation ### 2.3 Update Inventory Context **Changes to**: `lib/components_elixir/inventory.ex` **Remove**: - `compute_hierarchy_fields_batch/1` - `compute_level_for_single/1` - `compute_path_for_single/1` - All virtual field computation **Simplify**: - `list_storage_locations/0` - use standard preloading - `get_storage_location!/1` - remove virtual field computation ## Phase 3: Refactor Categories ### 3.1 Update Categories to Use Shared Module **Changes to**: `lib/components_elixir/inventory/category.ex` Replace existing `full_path/1` with call to shared module. ### 3.2 Update Categories LiveView **Changes to**: `lib/components_elixir_web/live/categories_live.ex` Replace custom hierarchy functions with shared module calls: ```elixir # Replace custom functions with shared module defp parent_category_options(categories, editing_category_id \\ nil) do available_categories = Hierarchical.filter_parent_options( categories, editing_category_id, &(&1.id), &(&1.parent_id) ) |> Enum.map(fn category -> {Hierarchical.display_name(category, &(&1.parent)), category.id} end) [{"No parent (Root category)", nil}] ++ available_categories end ``` ## Phase 4: Extract LiveView Components ### 4.1 Create Hierarchical LiveView Components **File**: `lib/components_elixir_web/live/hierarchical_components.ex` ```elixir defmodule ComponentsElixirWeb.HierarchicalComponents do use Phoenix.Component alias ComponentsElixir.Inventory.Hierarchical @doc """ Renders a hierarchical tree of entities with depth-based styling. """ def hierarchy_tree(assigns) @doc """ Renders an individual item in a hierarchical tree. """ def hierarchy_item(assigns) @doc """ Renders a parent selection dropdown with cycle prevention. """ def parent_select(assigns) end ``` ### 4.2 Update LiveViews to Use Shared Components Both `CategoriesLive` and `StorageLocationsLive` can use the shared components, with custom slots for entity-specific content (like AprilTag display). ## Implementation Timeline ### Immediate (Phase 1) - 1-2 hours - [ ] Create `Hierarchical` module with core functions - [ ] Create `HierarchicalSchema` behaviour - [ ] Write comprehensive tests for shared module ### Short-term (Phase 2) - 2-3 hours - [ ] Refactor storage location schema to remove virtual fields - [ ] Simplify storage location changeset - [ ] Update inventory context to remove batch computation - [ ] Test storage location functionality ### Medium-term (Phase 3) - 1-2 hours - [ ] Update category schema to use shared module - [ ] Update categories LiveView to use shared functions - [ ] Test category functionality ### Long-term (Phase 4) - 2-3 hours - [ ] Create shared LiveView components - [ ] Refactor both LiveViews to use shared components - [ ] Add entity-specific customization slots - [ ] Comprehensive integration testing ## Benefits After Refactoring ### Code Quality - **50% reduction** in hierarchical logic duplication - **Consistent patterns** across both entity types - **Easier testing** with isolated, pure functions - **Better maintainability** with single source of truth ### Performance - **Elimination of virtual fields** reduces memory usage - **Remove batch computation** improves response times - **Standard Ecto patterns** optimize database queries - **UI-level cycle prevention** reduces validation overhead ### Developer Experience - **Shared components** speed up new hierarchical entity development - **Consistent API** reduces learning curve - **Better documentation** with centralized behavior - **Easier debugging** with simplified call stacks ## Risk Mitigation ### Database Migration Safety 1. **Backup database** before running migrations 2. **Test migrations** on development environment first 3. **Incremental approach** - one table at a time ### Functionality Preservation 1. **Comprehensive test coverage** before refactoring 2. **Feature parity testing** after each phase 3. **AprilTag functionality isolation** to prevent interference ### Rollback Plan 1. **Git branching strategy** for each phase 2. **Database migration rollbacks** prepared 3. **Quick revert capability** if issues discovered ## Testing Strategy ### Unit Tests - [ ] Test all `Hierarchical` module functions - [ ] Test schema `full_path/1` implementations - [ ] Test cycle detection edge cases ### Integration Tests - [ ] Test LiveView parent selection dropdowns - [ ] Test hierarchical tree rendering - [ ] Test AprilTag functionality preservation ### Performance Tests - [ ] Benchmark path computation performance - [ ] Measure memory usage before/after - [ ] Profile database query patterns ## Migration Notes ### Database Changes Required 1. **Remove `is_active` column** from storage_locations (✅ **COMPLETED**) 2. **No other database changes** needed for this refactor ### Deployment Considerations - **Zero downtime**: Refactor is code-only (except `is_active` removal) - **Backward compatible**: No API changes - **Incremental deployment**: Can deploy phase by phase ## Success Criteria ### Functional - [ ] All existing category functionality preserved - [ ] All existing storage location functionality preserved - [ ] AprilTag features remain storage-location specific - [ ] UI behavior identical to current implementation ### Non-Functional - [ ] Code duplication reduced by ≥50% - [ ] Performance maintained or improved - [ ] Test coverage ≥95% for shared modules - [ ] Documentation complete for new modules --- ## Next Steps 1. **Review this plan** with team/stakeholders 2. **Create feature branch** for refactoring work 3. **Begin Phase 1** - Extract hierarchical behavior module 4. **Write comprehensive tests** before any refactoring 5. **Execute phases incrementally** with testing between each This refactoring will significantly improve code quality and maintainability while preserving all existing functionality and preparing the codebase for future hierarchical entities.