Skip to content

ticketbis/grails-nestedset

Repository files navigation

Nestedset

Build Status

The nested set model is a particular technique for representing nested sets (also known as trees or hierarchies) in relational databases. This plugin provides nestedset behaviour to domain classes.

Installation

Add dependency to your BuildConfig;

compile "com.ticketbis:nestedset:0.1.0"

Usage

The following example shows how to add nestedset behaviour to a domain class:

// grails-app/domain/Category.groovy
import com.ticketbis.nestedset.ast.Nestedset

@Nestedset
class Category {
    String name
}

Properties added to domain class are listed below:

Integer lft // Nestedset left value
Integer rgt // Nestedset right value
Integer depth // Node depth inside the tree
Category parent // Parent node

Changing lft, rgt and depth values manually will corrupt the tree. Please, DO NOT change them manually, use addNode, deleteNode and moveNode instead.

Methods added to domain class are listed below:

Boolean isLeaf() // does not have children.
Boolean isRootNode() // does not have parent node.
List getDescendants() // Its descendants. A subtree with the node as root.
Long countDescendants() // Number of descendants.
List getLeafs() // Descendants without children (Leafs nodes)
List getAncestors(boolean include_itself=false) // Ancestors (breadcrumb)
def getChildren(params=[:]) // Direct children
Long countChildren() // Count direct children
Category getLastChild() // Last child node
Category getRoot() // Its root node
Boolean isDescendant(Category node) 

Three static methods are available to alter the tree:

// Adding nodes to the tree
Category.addNode(Category node, Category parent)
Category.addNode(Category node) // node.parent must be setted first otherwise will be a root node
// Removing nodes from the tree
Category.deleteNode(Category node)
// Moving one node (changing its parent)
Category.moveNode(Category node, Category newParent)
Category.roots // list of all roots

Known issues

addNode, deleteNode and moveNode methods may change lft, rgt and depth values of other nodes, so it is recommended to refresh them within the same session when you need access to one of these properties on other nodes.

The following example shows how parent properties are not updated:

Category.addNode(parent)
Category.addNode(category, parent)
Category.addNode(category2, parent)
Category.addNode(category3, category2)

assert parent.rgt == 6 // Wrong value. parent.rgt value wasn't updated after Category.addNode(category3, category2)
parent.refresh()
assert parent.rgt == 8 // Right value.
Unit tests

Nestedset plugin protects lft, rft and depth properties from manual modification. Since executeUpdates are not supported for unit testing, addNode doesn't work, so in order to bypass nestedset protection you can do the following:

Category category = new Categoria(name: 'Category 1')
category.nestedsetMutable = true
category.save()