we-promise/sure · error · Category::Merger::UnauthorizedCategoryError
#{label} does not belong to this family
Error message
#{label} does not belong to this family What it means
Category::Merger#validate_category_belongs_to_family! raises UnauthorizedCategoryError when a category passed as the merge target or as one of the sources belongs to a different family than the merger's family. It is a multi-tenant ownership guard: category.family_id must equal family.id for every category handed to the merge.
Source
Thrown at app/models/category/merger.rb:42
Category.transaction { merge_sources! }
true
end
private
def merge_sources!
source_categories.each do |source|
family.transactions.where(category_id: source.id).update_all(category_id: target_category.id)
merge_budget_categories(source)
family.categories.where(parent_id: source.id).where.not(id: target_category.id).update_all(parent_id: target_category.id)
family.categories.find(source.id).destroy!
@merged_count += 1
end
end
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 = []View on GitHub (pinned to e69894adb9)
Solutions
- Resolve every category through the family scope: family.categories.find(id) instead of Category.find(id)
- Return 404/422 from the endpoint when an ID does not exist within the current family instead of letting the merger raise
- If occurrences are real traffic, audit the client for cross-family ID leakage
Example fix
// before target = Category.find(params[:target_id]) merger = Category::Merger.new(family: family, target_category: target, source_categories: sources) // after target = family.categories.find(params[:target_id]) # RecordNotFound stays scoped to the family merger = Category::Merger.new(family: family, target_category: target, source_categories: sources)
Defensive patterns
Strategy: validation
Validate before calling
family.categories.where(id: target_category_ids + source_category_ids).count == ids.uniq.length # all IDs resolvable within the family
Try / catch
rescue Category::Merger::UnauthorizedCategoryError (or the merger's error class) in the controller and return 422 without leaking that the IDs exist in another family
Prevention
- Never resolve merge IDs with global Category.find; always scope family.categories.find
- Treat out-of-scope category IDs as 404, not 403, to avoid cross-family probing
- Add controller tests that submit foreign-family category IDs
When it happens
Trigger: Building Category::Merger.new(family:, target_category:, source_categories:) with category objects fetched outside the family scope — params-supplied IDs resolved with Category.find, IDs belonging to another family, or stale client IDs after a family switch.
Common situations: Controller/API endpoints that trust client-provided category IDs without scoping to current_family; scripts or imports referencing category IDs across families; tests using fixtures from a different family.
Related errors
- A parent category cannot be merged into its own subcategory
- Cannot merge a category with subcategories into a subcategor
- #{label} does not belong to this family
- request_failed
- access_forbidden
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/7663358d8cfc0424.
Report an issue: GitHub.