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, cascade_copy => 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();
176 @list = $employee->ancestors();
178 Returns a list of ancestors starting with a record's
179 parent and moving toward the tree root.
187 while ($rec = $rec->parent) {
188 push(@ancestors, $rec);
194 =head2 has_descendant
196 if ($employee->has_descendant( $id )) { ... }
198 Returns true if the object has a descendant with the
204 my ($self, $find_id) = @_;
206 my $children = $self->children();
207 while (my $child = $children->next()) {
208 if ($child->id() eq $find_id) {
211 return 1 if ($child->has_descendant( $find_id ));
219 my $parents = $node->parents();
220 my @parents = $node->parents();
222 This has_many relationship is not that useful as it will
223 never return more than one parent due to the one-to-many
224 structure of adjacency lists. The reason this relationship
225 is defined is so that this tree type may be treated as if
230 my $children_rs = $employee->children();
231 my @children = $employee->children();
233 Returns a list or record set, depending on context, of all
234 the objects one level below the current one. This method
235 is created when parent_column() is called, which sets up a
236 has_many relationship called children.
240 $parent->attach_child( $child );
241 $parent->attach_child( $child, $child, ... );
243 Sets the child, or children, to the new parent. Returns 1
244 on success and returns 0 if the parent object already has
252 foreach my $child (@_) {
253 $child->parent( $self );
260 my $rs = $node->siblings();
261 my @siblings = $node->siblings();
263 Returns either a result set or an array of all other objects
264 with the same parent as the calling object.
270 my $parent_col = $self->_parent_column;
271 my $primary_col = ($self->primary_columns())[0];
272 my $rs = $self->result_source->resultset->search(
274 $parent_col => $self->get_column($parent_col),
275 $primary_col => { '!=' => $self->get_column($primary_col) },
278 return $rs->all() if (wantarray());
282 =head2 attach_sibling
284 $obj->attach_sibling( $sibling );
285 $obj->attach_sibling( $sibling, $sibling, ... );
287 Sets the passed in object(s) to have the same parent
288 as the calling object. Returns 1 on success and
289 0 if the sibling already has the same parent.
296 foreach my $node (@_) {
297 $return = 0 if (!$node->parent( $self->parent() ));
304 if ($obj->is_leaf()) { ... }
306 Returns 1 if the object has no children, and 0 otherwise.
313 my $has_child = $self->result_source->resultset->search(
314 { $self->_parent_column => $self->id() },
318 return $has_child ? 0 : 1;
323 if ($obj->is_root()) { ... }
325 Returns 1 if the object has no parent, and 0 otherwise.
331 return ( $self->get_column( $self->_parent_column ) ? 0 : 1 );
336 if ($obj->is_branch()) { ... }
338 Returns 1 if the object has a parent and has children.
345 return ( ($self->is_leaf() or $self->is_root()) ? 0 : 1 );
348 =head2 set_primary_key
350 This method is an override of DBIx::Class' method for setting the
351 class' primary key column(s). This method passes control right on
352 to the normal method after first validating that only one column is
353 being selected as a primary key. If more than one column is then
354 an error will be thrown.
358 sub set_primary_key {
361 croak('You may only specify a single column as the primary key for adjacency tree classes');
363 return $self->next::method( @_ );
369 =head1 INHERITED METHODS
377 L<mk_classdata|DBIx::Class/mk_classdata>
381 L<component_base_class|DBIx::Class/component_base_class>
385 =head2 DBIx::Class::Componentised
391 L<inject_base|DBIx::Class::Componentised/inject_base>
395 L<load_components|DBIx::Class::Componentised/load_components>
399 L<load_own_components|DBIx::Class::Componentised/load_own_components>
403 =head2 Class::Data::Accessor
409 L<mk_classaccessor|Class::Data::Accessor/mk_classaccessor>
415 Aran Clary Deltac <bluefeet@cpan.org>
419 You may distribute this code under the same terms as Perl itself.