1 package DBIx::Class::Tree::AdjacencyList;
2 # vim: ts=8:sw=4:sts=4:et
7 use base qw( DBIx::Class );
12 DBIx::Class::Tree::AdjacencyList - Manage a tree of data using the common adjacency list model.
16 Create a table for your tree data.
18 CREATE TABLE employees (
19 employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
20 parent_id INTEGER NOT NULL DEFAULT 0,
24 In your Schema or DB class add Tree::AdjacencyList to the top
25 of the component list.
27 __PACKAGE__->load_components(qw( Tree::AdjacencyList ... ));
29 Specify the column that contains the parent ID of each row.
32 __PACKAGE__->parent_column('parent_id');
34 Optionally, automatically maintane a consistent tree structure.
36 __PACKAGE__->repair_tree( 1 );
38 Thats it, now you can modify and analyze the tree.
43 my $employee = My::Employee->create({ name=>'Matt S. Trout' });
45 my $rs = $employee->children();
46 my @siblings = $employee->children();
48 my $parent = $employee->parent();
49 $employee->parent( 7 );
53 This module provides methods for working with adjacency lists. The
54 adjacency list model is a very common way of representing a tree structure.
55 In this model each row in a table has a prent ID column that references the
56 primary key of another row in the same table. Because of this the primary
57 key must only be one column and is usually some sort of integer. The row
58 with a parent ID of 0 is the root node and is usually the parent of all
59 other rows. Although, there is no limitation in this module that would
60 stop you from having multiple root nodes.
66 __PACKAGE__->parent_column('parent_id');
68 Declares the name of the column that contains the self-referential
69 ID which defines the parent row. Defaults to "parent_id". This
70 will create a has_many (children) and belongs_to (parent)
73 This method also setups an additional has_many relationship called
74 parents which is useful when you want to treat an adjacency list
79 __PACKAGE__->mk_classdata( '_parent_column' => 'parent_id' );
84 my $parent_col = shift;
85 my $primary_col = ($class->primary_columns())[0];
86 $class->belongs_to( '_parent' => $class => { "foreign.$primary_col" => "self.$parent_col" } );
87 $class->has_many( 'children' => $class => { "foreign.$parent_col" => "self.$primary_col" } );
88 $class->has_many( 'parents' => $class => { "foreign.$primary_col" => "self.$parent_col" }, { cascade_delete => 0 } );
89 $class->_parent_column( $parent_col );
92 return $class->_parent_column();
97 __PACKAGE__->repair_tree( 1 );
99 When set a true value this flag causes all changes to a node's parent to
100 trigger an integrity check on the tree. If, when changing a node's parent
101 to one of it's descendents then all its children will first be moved to have
102 the same current parent, and then the node's parent is changed.
104 So, for example, if the tree is like this:
117 Since D is a descendant of B then all of B's siblings get their parent
118 changed to A. Then B's parent is set to D.
129 __PACKAGE__->mk_classdata( 'repair_tree' => 0 );
133 my $parent = $employee->parent();
134 $employee->parent( $parent_obj );
135 $employee->parent( $parent_id );
137 Retrieves the object's parent object, or changes the object's
138 parent to the specified parent or parent ID. If you would like
139 to make the object the root node, just set the parent to 0.
141 If you are setting the parent then 0 will be returned if the
142 specified parent is already the object's parent and 1 on
150 my $new_parent = shift;
151 my $parent_col = $self->_parent_column();
152 if (ref($new_parent)) {
153 $new_parent = $new_parent->id() || croak('Parent object does not have an ID');;
155 return 0 if ($new_parent == ($self->get_column($parent_col)||0));
157 if ($self->repair_tree()) {
158 my $found = $self->has_descendant( $new_parent );
160 my $children = $self->children();
162 while (my $child = $children->next()) {
163 $child->parent( $self->$parent_col() );
168 $self->set_column( $parent_col => $new_parent );
172 return $self->_parent();
175 =head2 has_descendant
177 if ($employee->has_descendant( $id )) { ... }
179 Returns true if the object has a descendant with the
185 my ($self, $find_id) = @_;
187 my $children = $self->children();
188 while (my $child = $children->next()) {
189 if ($child->id() eq $find_id) {
192 return 1 if ($child->has_descendant( $find_id ));
200 my $parents = $node->parents();
201 my @parents = $node->parents();
203 This has_many relationship is not that useful as it will
204 never return more than one parent due to the one-to-many
205 structure of adjacency lists. The reason this relationship
206 is defined is so that this tree type may be treated as if
211 my $children_rs = $employee->children();
212 my @children = $employee->children();
214 Returns a list or record set, depending on context, of all
215 the objects one level below the current one. This method
216 is created when parent_column() is called, which sets up a
217 has_many relationship called children.
221 $parent->attach_child( $child );
222 $parent->attach_child( $child, $child, ... );
224 Sets the child, or children, to the new parent. Returns 1
225 on success and returns 0 if the parent object already has
233 foreach my $child (@_) {
234 $child->parent( $self );
241 my $rs = $node->siblings();
242 my @siblings = $node->siblings();
244 Returns either a result set or an array of all other objects
245 with the same parent as the calling object.
251 my $parent_col = $self->_parent_column;
252 my $primary_col = ($self->primary_columns())[0];
253 my $rs = $self->result_source->resultset->search(
255 $parent_col => $self->get_column($parent_col),
256 $primary_col => { '!=' => $self->get_column($primary_col) },
259 return $rs->all() if (wantarray());
265 =head2 attach_sibling
267 $obj->attach_sibling( $sibling );
268 $obj->attach_sibling( $sibling, $sibling, ... );
270 Sets the passed in object(s) to have the same parent
271 as the calling object. Returns 1 on success and
272 0 if the sibling already has the same parent.
279 foreach my $node (@_) {
280 $return = 0 if (!$node->parent( $self->parent() ));
287 if ($obj->is_leaf()) { ... }
289 Returns 1 if the object has no children, and 0 otherwise.
296 my $has_child = $self->result_source->resultset->search(
297 { $self->_parent_column => $self->id() },
301 return $has_child ? 0 : 1;
306 if ($obj->is_root()) { ... }
308 Returns 1 if the object has no parent, and 0 otherwise.
314 return ( $self->get_column( $self->_parent_column ) ? 0 : 1 );
319 if ($obj->is_branch()) { ... }
321 Returns 1 if the object has a parent and has children.
328 return ( ($self->is_leaf() or $self->is_root()) ? 0 : 1 );
331 =head2 set_primary_key
333 This method is an override of DBIx::Class' method for setting the
334 class' primary key column(s). This method passes control right on
335 to the normal method after first validating that only one column is
336 being selected as a primary key. If more than one column is then
337 an error will be thrown.
341 sub set_primary_key {
344 croak('You may only specify a single column as the primary key for adjacency tree classes');
346 return $self->next::method( @_ );
352 =head1 INHERITED METHODS
360 L<mk_classdata|DBIx::Class/mk_classdata>
364 L<component_base_class|DBIx::Class/component_base_class>
368 =head2 DBIx::Class::Componentised
374 L<inject_base|DBIx::Class::Componentised/inject_base>
378 L<load_components|DBIx::Class::Componentised/load_components>
382 L<load_own_components|DBIx::Class::Componentised/load_own_components>
386 =head2 Class::Data::Accessor
392 L<mk_classaccessor|Class::Data::Accessor/mk_classaccessor>
398 Aran Clary Deltac <bluefeet@cpan.org>
402 You may distribute this code under the same terms as Perl itself.