.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "DBIx::Class::Tree::NestedSet 3pm" .TH DBIx::Class::Tree::NestedSet 3pm "2021-01-05" "perl v5.32.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" DBIx::Class::Tree::NestedSet \- Manage trees of data using the nested set model .SH "SYNOPSIS" .IX Header "SYNOPSIS" Create a table for your tree data. .PP .Vb 8 \& CREATE TABLE Department ( \& id INTEGER PRIMARY KEY AUTOINCREMENT, \& root_id integer, \& lft integer NOT NULL, \& rgt integer NOT NULL, \& level integer NOT NULL, \& name text NOT NULL, \& ); .Ve .PP In your Schema or \s-1DB\s0 class add Tree::NestedSet to the top of the component list. .PP .Vb 1 \& _\|_PACKAGE_\|_\->load_components(qw( Tree::NestedSet ... )); .Ve .PP Specify the columns required by the module. .PP .Vb 7 \& package My::Department; \& _\|_PACKAGE_\|_\->tree_columns({ \& root_column => \*(Aqroot_id\*(Aq, \& left_column => \*(Aqlft\*(Aq, \& right_column => \*(Aqrgt\*(Aq, \& level_column => \*(Aqlevel\*(Aq, \& }); .Ve .PP Using it: .PP .Vb 2 \& my $root = My::Department\->create({ ... }); \& my $child = $root\->add_to_children({ ... }); \& \& my $rs = $root\->children; \& my @descendants = $root\->children; \& \& my $parent = $child\->parent; \& my $rs = $child\->ancestors; \& my @ancestors = $child\->ancestors; .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" This module provides methods for working with nested set trees. The nested tree model is a way of representing hierarchical information in a database. This takes a different approach to the Adjacency List implementation. (see DBIx::Class::Tree::AdjacencyList which uses \f(CW\*(C`parent\*(C'\fR relationships in a recursive manner. .PP The NestedSet implementation can be more efficient for most searches than the Adjacency List Implementation, for example, to obtain all descendants requires recursive queries in the Adjacency List implementation but is a single query in the NestedSet implementation. .PP The trade-off is that NestedSet inserts are more expensive so it is most useful if you have an application that does many reads but few inserts. .PP More about NestedSets can be found at .PP Oh, and although I give some code examples of familial relationships (where there are usually two parents), both Adjacency List and NestedSet implementations can only have one parent. .SH "RELATIONS" .IX Header "RELATIONS" This module automatically creates several relationships. .SS "root" .IX Subsection "root" .Vb 1 \& $root_node = $node\->root; .Ve .PP A belongs_to relation to the root of \f(CW$node\fRs tree. .SS "nodes" .IX Subsection "nodes" .Vb 2 \& $all_nodes = $node\->nodes; \& $new_node = $node\->add_to_nodes({name => \*(AqMens Wear\*(Aq}); .Ve .PP A has_many relationship to all the nodes of \f(CW$node\fRs tree. .PP Adding to this relationship creates a rightmost child to \f(CW$node\fR. .SS "parent" .IX Subsection "parent" .Vb 1 \& $parent = $node\->parent; .Ve .PP A belongs_to relationship to the parent node of \f(CW$node\fRs tree. .PP Note that only the root node does not have a parent. .SS "children" .IX Subsection "children" .Vb 3 \& $rs = $node\->children; \& @children = $node\->children; \& $child = $node\->add_to_children({name => \*(AqToys\*(Aq}); .Ve .PP A has_many relation to the children of \f(CW$node\fR. .PP Adding to this relationship creates a rightmost child to \f(CW$node\fR. .SS "descendants" .IX Subsection "descendants" .Vb 3 \& $rs = $node\->descendants; \& @descendants = $node\->descendants; \& $child = $node\->add_to_descendants({name => \*(AqMens Wear\*(Aq}); .Ve .PP A has_many relation to the descendants of \f(CW$node\fR. .PP Adding to this relationship creates a rightmost child to \f(CW$node\fR. .SS "ancestors" .IX Subsection "ancestors" .Vb 3 \& $rs = $node\->ancestors; \& @ancestors = $node\->ancestors; \& $parent = $node\->add_to_ancestors({name => \*(AqHead office\*(Aq}); .Ve .PP A has_many relation to the ancestors of \f(CW$node\fR. .PP Adding to this relationship creates a new node in place of \f(CW$node\fR and makes it the parent of \f(CW$node\fR. All descendants of \f(CW$node\fR will likewise be pushed town the hierarchy. .SH "METHODS" .IX Header "METHODS" Many methods have alternative names, e.g. \f(CW\*(C`left_siblings\*(C'\fR and \f(CW\*(C`previous_siblings\*(C'\fR .PP This is in deference to the DBIx::Class::Ordered module which uses terms \&\f(CW\*(C`previous\*(C'\fR \f(CW\*(C`next\*(C'\fR \f(CW\*(C`first\*(C'\fR and \f(CW\*(C`last\*(C'\fR. .PP Similarly DBIx::Class::Tree::AdjacencyList::Ordered uses terms \f(CW\*(C`append\*(C'\fR, \f(CW\*(C`prepend\*(C'\fR, \&\f(CW\*(C`before\*(C'\fR and \f(CW\*(C`after\*(C'\fR .PP However, my preference to use terms \f(CW\*(C`left\*(C'\fR and \f(CW\*(C`right\*(C'\fR consistently when using this module. However, the other names are available if you are more familiar with those modules. .SS "tree_columns" .IX Subsection "tree_columns" .Vb 6 \& _\|_PACKAGE_\|_\->tree_columns({ \& left_column => \*(Aqlft\*(Aq, \& right_column => \*(Aqrgt\*(Aq, \& root_column => \*(Aqroot_id\*(Aq, \& level_column => \*(Aqlevel\*(Aq, \& }); .Ve .PP Declare the name of the columns defined in the database schema. .PP None of these columns should be modified outside if this module. left_column and right_column are unlikely to be of any use to your application. They should be integer fields. .PP Multiple trees are allowed in the same table, each tree will have a unique value in the root_column. In the current implementation this should be an integer field .PP The level_column may be of use in your application, it defines the depth of each node in the tree (with the root at level zero). .SS "create" .IX Subsection "create" .Vb 3 \& my $tree = $schema\->resultset(\*(AqMy::Department\*(Aq)\->create({ \& name = \*(AqHead Office\*(Aq, \& }); \& \& my $tree = $schema\->resultset(\*(AqMy::Department\*(Aq)\->create({ \& name = \*(AqUK Office\*(Aq, \& root_id = $uk_office_ident, \& }); .Ve .PP Creates a new root node. .PP If the root_column (root_id) is not provided then it defaults to producing a node where the root_column has the same value as the primary key. This will croak if the table is defined with multiple key primary index. .PP Note that no checks (yet) are made to stop you creating another key with the same root_id as an existing tree. If you do so you will get into a terrible mess! .SS "delete" .IX Subsection "delete" .Vb 1 \& $department\->delete; .Ve .PP This will delete the node and all descendants. Cascade Delete is turned off in the has_many relationships \f(CW\*(C`nodes\*(C'\fR \f(CW\*(C`children\*(C'\fR \f(CW\*(C`descendants\*(C'\fR so that delete \s-1DTRT.\s0 .SS "is_root" .IX Subsection "is_root" .Vb 3 \& if ($node\->is_root) { \& print "Node is a root\en"; \& } .Ve .PP Returns true if the \f(CW$node\fR is a root node .SS "is_branch" .IX Subsection "is_branch" .Vb 1 \& $has_children = $node\->is_branch; .Ve .PP Returns true if the node is a branche (i.e. has children) .SS "is_leaf" .IX Subsection "is_leaf" .Vb 1 \& $is_terminal_node = $node\->is_leaf; .Ve .PP Returns true if the node is a leaf (i.e. it has no children) .SS "siblings" .IX Subsection "siblings" .Vb 2 \& @siblings = $node\->siblings; \& $siblings_rs = $node\->siblings; .Ve .PP Returns all siblings of this \f(CW$node\fR excluding \f(CW$node\fR itself. .PP Since a root node has no siblings it returns undef. .SS "left_siblings (or previous_siblings)" .IX Subsection "left_siblings (or previous_siblings)" .Vb 2 \& @younger_siblings = $node\->left_siblings; \& $younger_siblings_rs = $node\->left_siblings; .Ve .PP Returns all siblings of this \f(CW$node\fR to the left this \f(CW$node\fR. .PP Since a root node has no siblings it returns undef. .SS "right_siblings (or next_siblings)" .IX Subsection "right_siblings (or next_siblings)" .Vb 2 \& @older_siblings = $node\->right_siblings; \& $older_siblings_rs = $node\->right_siblings; .Ve .PP Returns all siblings of this \f(CW$node\fR to the right of this \f(CW$node\fR. .PP Since a root node has no siblings it returns undef. .SS "left_sibling (or previous_sibling)" .IX Subsection "left_sibling (or previous_sibling)" .Vb 1 \& $younger_sibling = $node\->left_sibling; .Ve .PP Returns the sibling immediately to the left of this \f(CW$node\fR (if any). .SS "right_sibling (or next_sibling)" .IX Subsection "right_sibling (or next_sibling)" .Vb 1 \& $older_sibling = $node\->right_sibling; .Ve .PP Returns the sibling immediately to the right of this \f(CW$node\fR (if any). .SS "leftmost_sibling (or first_sibling)" .IX Subsection "leftmost_sibling (or first_sibling)" .Vb 1 \& $youngest_sibling = $node\->leftmost_sibling; .Ve .PP Returns the left most sibling relative to this \f(CW$node\fR (if any). .PP Does not return this \f(CW$node\fR if this node is the leftmost sibling. .SS "rightmost_sibling (or last_sibling)" .IX Subsection "rightmost_sibling (or last_sibling)" .Vb 1 \& $oldest_sibling = $node\->rightmost_sibling; .Ve .PP Returns the right most sibling relative to this \f(CW$node\fR (if any). .PP Does not return this \f(CW$node\fR if this node is the rightmost sibling. .SS "\s-1CREATE METHODS\s0" .IX Subsection "CREATE METHODS" The following create methods create a new node in relation to an existing node. .SS "create_right_sibling" .IX Subsection "create_right_sibling" .Vb 1 \& $bart\->create_right_sibling({ name => \*(AqLisa\*(Aq }); .Ve .PP Create a new node as a right sibling to \f(CW$bart\fR. .SS "create_left_sibling" .IX Subsection "create_left_sibling" .Vb 1 \& $bart\->create_left_sibling({ name => \*(AqMaggie\*(Aq }); .Ve .PP Create a new node as a left sibling to \f(CW$bart\fR. .SS "create_rightmost_child" .IX Subsection "create_rightmost_child" .Vb 1 \& $homer\->create_rightmost_child({ name => \*(AqLisa\*(Aq }); .Ve .PP Create a new node as a rightmost child to \f(CW$homer\fR .SS "create_leftmost_child" .IX Subsection "create_leftmost_child" .Vb 1 \& $homer\->create_leftmost_child({ name => \*(AqMaggie\*(Aq }); .Ve .PP Create a new node as a leftmost child to \f(CW$homer\fR .SS "\s-1ATTACH METHODS\s0" .IX Subsection "ATTACH METHODS" The following attach methods take an existing node (and all of it's descendants) and attaches them to the tree in relation to an existing node. .PP The node being inserted can either be from the same tree (as identified by the root_column) or from another tree. If the root of another tree is attached then the whole of that tree becomes a sub-tree of this node's tree. .PP The only restriction is that the node being attached cannot be an ancestor of this node. .PP When attaching multiple nodes we try to \s-1DWIM\s0 so that the order they are specified in the call represents the order they appear in the siblings list. .PP e.g. if we had a parent with children A,B,C,D,E .PP and we attached nodes 1,2,3 in the following calls, we expect the following results. .PP .Vb 1 \& $parent\->attach_rightmost_child 1,2,3 gives us children A,B,C,D,E,1,2,3 \& \& $parent\->attach_leftmost_child 1,2,3 gives us children 1,2,3,A,B,C,D,E \& \& $child_C\->attach_right_sibling 1,2,3 gives us children A,B,C,1,2,3,D,E \& \& $child_C\->attach_left_sibling 1,2,3 gives us children A,B,1,2,3,C,D,E \& \& $child_C\->attach_rightmost_sibling 1,2,3 gives us children A,B,C,D,E,1,2,3 \& \& $child_C\->attach_leftmost_sibling 1,2,3 gives us children 1,2,3,A,B,C,D,E .Ve .SS "attach_rightmost_child (or append_child)" .IX Subsection "attach_rightmost_child (or append_child)" .Vb 2 \& $parent\->attach_rightmost_child($other_node); \& $parent\->attach_rightmost_child($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$parent\fR as the rightmost children. .SS "attach_leftmost_child" .IX Subsection "attach_leftmost_child" .Vb 2 \& $parent\->attach_leftmost_child($other_node); \& $parent\->attach_leftmost_child($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$parent\fR as the leftmost children. .SS "attach_right_sibling (or attach_after)" .IX Subsection "attach_right_sibling (or attach_after)" .Vb 2 \& $node\->attach_right_sibling($other_node); \& $node\->attach_right_sibling($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$node\fR as it's siblings. .SS "attach_left_sibling" .IX Subsection "attach_left_sibling" .Vb 2 \& $node\->attach_left_sibling($other_node); \& $node\->attach_left_sibling($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$node\fR as it's left siblings. .SS "attach_rightmost_sibling" .IX Subsection "attach_rightmost_sibling" .Vb 2 \& $node\->attach_rightmost_sibling($other_node); \& $node\->attach_rightmost_sibling($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$node\fR as it's rightmost siblings. .SS "attach_leftmost_sibling" .IX Subsection "attach_leftmost_sibling" .Vb 2 \& $node\->attach_leftmost_sibling($other_node); \& $node\->attach_leftmost_sibling($other_node_1, $other_node_2, ...); .Ve .PP Attaches the other_nodes to \f(CW$node\fR as it's leftmost siblings. .SS "move_left (or move_previous)" .IX Subsection "move_left (or move_previous)" .Vb 1 \& $node\->move_left; .Ve .PP Exchange the \f(CW$node\fR with the sibling immediately to the left and return the node it exchanged with. .PP If the \f(CW$node\fR is already the leftmost node then no exchange takes place and the method returns undef. .SS "move_right (or move_next)" .IX Subsection "move_right (or move_next)" .Vb 1 \& $node\->move_right; .Ve .PP Exchange the \f(CW$node\fR with the sibling immediately to the right and return the node it exchanged with. .PP If the \f(CW$node\fR is already the rightmost node then no exchange takes place and the method returns undef. .SS "move_leftmost (or move_first)" .IX Subsection "move_leftmost (or move_first)" .Vb 1 \& $node\->move_leftmost; .Ve .PP Exchange the \f(CW$node\fR with the leftmost sibling and return the node it exchanged with. .PP If the \f(CW$node\fR is already the leftmost node then no exchange takes place and the method returns undef. .SS "move_rightmost (or move_last)" .IX Subsection "move_rightmost (or move_last)" .Vb 1 \& $node\->move_rightmost; .Ve .PP Exchange the \f(CW$node\fR with the rightmost sibling and return the node it exchanged with. .PP If the \f(CW$node\fR is already the rightmost node then no exchange takes place and the method returns undef. .SS "\s-1CUTTING METHODS\s0" .IX Subsection "CUTTING METHODS" .SS "take_cutting" .IX Subsection "take_cutting" Cuts the invocant and its descendants out of the tree they are in, making the invocant the root of a new tree. Returns the modified invocant. .SS "dissolve" .IX Subsection "dissolve" Dissolves the entire thread, that is turn each node of the thread into a single-item tree of its own. .SH "CAVEATS" .IX Header "CAVEATS" .SS "Multiple Column Primary Keys" .IX Subsection "Multiple Column Primary Keys" Support for Multiple Column Primary Keys is limited (mainly because I rarely use them) but I have tried to make it possible to use them. Please let me know if this does not work as well as you expect. .SS "discard_changes" .IX Subsection "discard_changes" By the nature of Nested Set implementations, moving, inserting or deleting nodes in the tree will potentially update many (sometimes most) other nodes. .PP Even if you have preloaded some of the objects, if you make a change to one object the other objects will not reflect their new value until you have reloaded them from the database. (see \*(L"discard_changes\*(R" in DBIx::Class::Row) .PP A simple demonstration of this .PP .Vb 3 \& $grampa = $schema\->schema\->resultset(\*(AqSimpsons\*(Aq)\->create({ name => \*(AqAbraham\*(Aq }); \& $homer = $grampa\->add_children({name => \*(AqHomer\*(Aq}); \& $bart = $homer\->add_children({name => \*(AqBart\*(Aq}); .Ve .PP The methods in this module will do their best to keep instances that they know about updated. For example the first call to \f(CW\*(C`add_children\*(C'\fR in the above example will update \f(CW$grampa\fR and \f(CW$homer\fR with the latest changes to the database. .PP However, the second call to \f(CW\*(C`add_children\*(C'\fR only knows about \f(CW$homer\fR and \f(CW$bart\fR and in adding a new node to the tree it will update the \f(CW$grampa\fR node in the database. To ensure you have the latest changes do the following. .PP .Vb 1 \& $grampa\->discard_changes. .Ve .PP Not doing so will have unpredictable results. .SH "AUTHORS" .IX Header "AUTHORS" Code by Ian Docherty .PP Based on original code by Florian Ragwitz .PP Incorporating ideas and code from Pedro Melo .PP Special thanks to Moritz Lenz who sent in lots of patches and changes for version 0.08 .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" Copyright (c) 2009\-2011 The above authors .PP This is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available.