we-promise/sure · error · Category::Merger::UnauthorizedCategoryError

Cannot merge a category with subcategories into a subcategor

Error message

Cannot merge a category with subcategories into a subcategory

What it means

Category::Merger#validate_reparenting! blocks merges where the target is itself a subcategory (parent_id present) and at least one source category has children (family.categories.exists?(parent_id: source.id)). Reparenting a parent-with-subcategories under a subcategory would push an entire subtree below a leaf, which the merger does not support.

Source

Thrown at app/models/category/merger.rb:56

    def validate_category_belongs_to_family!(category, label)
      return if category&.family_id == family.id

      raise UnauthorizedCategoryError, "#{label} does not belong to this family"
    end

    def validate_hierarchy!
      target_ancestor_ids = ancestor_ids_for(target_category)
      return unless source_categories.any? { |source| target_ancestor_ids.include?(source.id) }

      raise UnauthorizedCategoryError, "A parent category cannot be merged into its own subcategory"
    end

    def validate_reparenting!
      return if target_category.parent_id.blank?
      return unless source_categories.any? { |source| family.categories.exists?(parent_id: source.id) }

      raise UnauthorizedCategoryError, "Cannot merge a category with subcategories into a subcategory"
    end

    def ancestor_ids_for(category)
      ids = []
      seen_ids = Set.new
      current = category

      while current&.parent_id.present? && seen_ids.exclude?(current.parent_id)
        ids << current.parent_id
        seen_ids << current.parent_id
        current = family.categories.find_by(id: current.parent_id)
      end

      ids
    end

    def merge_budget_categories(source)
      family.budget_categories.where(category_id: source.id).find_each do |source_budget_category|

View on GitHub (pinned to e69894adb9)

Solutions

  1. Merge into a top-level (parentless) target category instead of a subcategory
  2. First move or merge away the source's subcategories, then retry the original merge
  3. Surface the rule in the UI: disable subcategory targets whenever any selected source has children
Defensive patterns

Strategy: validation

Validate before calling

target_is_subcategory = target_category.parent_id.present?
sources_have_children = family.categories.exists?(parent_id: source_categories.map(&:id))
unsafe = target_is_subcategory && sources_have_children

Prevention

When it happens

Trigger: Choosing a subcategory as the merge target while any selected source category still has its own subcategories: target.parent_id is set AND a category with parent_id == source.id exists in the family.

Common situations: Reorganizing category trees by collapsing a broad category into a more specific leaf; merge tools that allow arbitrary targets without checking source subtrees.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/01bbadc44983edaf. Report an issue: GitHub.