Skip to content

Conversation

@vsmay98
Copy link
Contributor

@vsmay98 vsmay98 commented Nov 19, 2025

Overview

This PR adds support for scoping root nodes and sibling ordering, allowing users to maintain separate ordering sequences for different scope groups (e.g., per user, per tenant, etc.). This addresses the issue where root nodes are ordered globally across the entire table, which can cause performance problems and sparse ordering sequences when dealing with many separate trees.

Motivation

As described in PR #442 by @AlexeyMatskevich, when using numeric ordering with root nodes, the current implementation assigns order values globally across the whole database table. This means:

  1. Root nodes from different users/tenants share the same ordering sequence
  2. Reordering root nodes for one user affects the ordering of all other users
  3. This results in sparse sequences (1, 10, 48...) when reordering

This PR implements a solution similar to how awesome_nested_set handles scoping, allowing root nodes and siblings to be scoped by one or more columns.

Changes

New Option: scope

The scope option accepts:

  • A single symbol: scope: :user_id
  • An array of symbols: scope: [:user_id, :group_id]

Behavior

When scope is specified:

  1. Root Nodes: Root queries can be filtered by scope values:

    Block.roots.where(user_id: 123)
  2. Sibling Queries: Siblings are automatically scoped - only nodes with matching scope values are considered siblings:

    block1.siblings  # Only returns siblings with the same user_id
  3. Reordering: When reordering siblings or children, only nodes with matching scope values are reordered:

    block1._ct_reorder_siblings  # Only reorders siblings with the same user_id
  4. Children Reordering: Children reordering respects the parent's scope values, ensuring that children with different scope values maintain separate ordering sequences.

Implementation Details

  • Added validation to ensure scope is a Symbol or Array of Symbols
  • Updated self_and_siblings to apply scope conditions
  • Updated reorder_with_parent_id in all adapters (MySQL, PostgreSQL, Generic) to accept and apply scope conditions
  • Updated reordering methods (_ct_reorder_siblings, _ct_reorder_children) to pass scope values from instances

Example Usage

class Block < ApplicationRecord
  has_closure_tree order: 'sort_order', numeric_order: true, scope: :user_id
end

# Single scope
user1_roots = Block.roots.where(user_id: 1)
user2_roots = Block.roots.where(user_id: 2)

# Multiple scope
class Block < ApplicationRecord
  has_closure_tree order: 'sort_order', numeric_order: true, scope: [:user_id, :group_id]
end

scoped_roots = Block.roots.where(user_id: 1, group_id: 10)

Credits

This PR is based on the original work by @AlexeyMatskevich in PR #442, which introduced the order_belong_to option. This implementation uses the scope option name (as suggested by @seuros in the PR discussion) to align with the syntax used by acts_as_list and awesome_nested_set.

Backward Compatibility

This change is fully backward compatible. Models without the scope option continue to work exactly as before. The scope option is purely additive and does not affect existing functionality.

@vsmay98 vsmay98 changed the title Add ability to scope root nodes ordering Add scope option for root nodes and sibling ordering Nov 19, 2025
@vsmay98 vsmay98 marked this pull request as ready for review November 19, 2025 14:25
@vsmay98
Copy link
Contributor Author

vsmay98 commented Nov 19, 2025

@seuros This PR is ready for review. Please take a look when you get a chance. Thanks!

@seuros seuros merged commit 87be762 into ClosureTree:master Nov 19, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants