Update to current cpan version
[dbsrgits/DBIx-Class-Tree.git] / lib / DBIx / Class / Tree / AdjacencyList.pm
CommitLineData
87427fdb 1package DBIx::Class::Tree::AdjacencyList;
bb17efa0 2# vim: ts=8:sw=4:sts=4:et
3
87427fdb 4use strict;
5use warnings;
bb17efa0 6
87427fdb 7use base qw( DBIx::Class );
8use Carp qw( croak );
9
10=head1 NAME
11
af9120b0 12DBIx::Class::Tree::AdjacencyList - Manage a tree of data using the common adjacency list model.
87427fdb 13
14=head1 SYNOPSIS
15
16Create a table for your tree data.
17
18 CREATE TABLE employees (
19 employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
82958127 20 parent_id INTEGER NOT NULL DEFAULT 0,
87427fdb 21 name TEXT NOT NULL
22 );
23
24In your Schema or DB class add Tree::AdjacencyList to the top
25of the component list.
26
27 __PACKAGE__->load_components(qw( Tree::AdjacencyList ... ));
87427fdb 28
57f04bd5 29Specify the column that contains the parent ID of each row.
87427fdb 30
31 package My::Employee;
32 __PACKAGE__->parent_column('parent_id');
33
98277fa5 34Optionally, automatically maintane a consistent tree structure.
35
36 __PACKAGE__->repair_tree( 1 );
37
87427fdb 38Thats it, now you can modify and analyze the tree.
39
82958127 40 #!/usr/bin/perl
87427fdb 41 use My::Employee;
42
43 my $employee = My::Employee->create({ name=>'Matt S. Trout' });
44
45 my $rs = $employee->children();
46 my @siblings = $employee->children();
47
48 my $parent = $employee->parent();
49 $employee->parent( 7 );
50
51=head1 DESCRIPTION
52
53This module provides methods for working with adjacency lists. The
54adjacency list model is a very common way of representing a tree structure.
55In this model each row in a table has a prent ID column that references the
56primary key of another row in the same table. Because of this the primary
57key must only be one column and is usually some sort of integer. The row
9717d3ca 58with a parent ID of 0 is the root node and is usually the parent of all
59other rows. Although, there is no limitation in this module that would
60stop you from having multiple root nodes.
87427fdb 61
62=head1 METHODS
63
64=head2 parent_column
65
66 __PACKAGE__->parent_column('parent_id');
67
68Declares the name of the column that contains the self-referential
82958127 69ID which defines the parent row. Defaults to "parent_id". This
70will create a has_many (children) and belongs_to (parent)
71relationship.
87427fdb 72
bb17efa0 73This method also setups an additional has_many relationship called
74parents which is useful when you want to treat an adjacency list
75as a DAG.
76
87427fdb 77=cut
78
82958127 79__PACKAGE__->mk_classdata( '_parent_column' => 'parent_id' );
80
81sub parent_column {
82 my $class = shift;
83 if (@_) {
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" } );
98277fa5 88 $class->has_many( 'parents' => $class => { "foreign.$primary_col" => "self.$parent_col" }, { cascade_delete => 0 } );
82958127 89 $class->_parent_column( $parent_col );
90 return 1;
91 }
92 return $class->_parent_column();
93}
87427fdb 94
98277fa5 95=head2 repair_tree
96
97 __PACKAGE__->repair_tree( 1 );
98
99When set a true value this flag causes all changes to a node's parent to
100trigger an integrity check on the tree. If, when changing a node's parent
101to one of it's descendents then all its children will first be moved to have
102the same current parent, and then the node's parent is changed.
103
104So, for example, if the tree is like this:
105
106 A
107 B
108 C
109 D
110 E
111 F
112
113And you execute:
114
115 $b->parent( $d );
116
117Since D is a descendant of B then all of B's siblings get their parent
118changed to A. Then B's parent is set to D.
c0d76c37 119
98277fa5 120 A
121 C
122 D
123 B
124 E
125 F
126
127=cut
128
129__PACKAGE__->mk_classdata( 'repair_tree' => 0 );
c0d76c37 130
87427fdb 131=head2 parent
132
133 my $parent = $employee->parent();
134 $employee->parent( $parent_obj );
135 $employee->parent( $parent_id );
136
82958127 137Retrieves the object's parent object, or changes the object's
9717d3ca 138parent to the specified parent or parent ID. If you would like
139to make the object the root node, just set the parent to 0.
140
141If you are setting the parent then 0 will be returned if the
142specified parent is already the object's parent and 1 on
143success.
87427fdb 144
87427fdb 145=cut
146
147sub parent {
82958127 148 my $self = shift;
149 if (@_) {
150 my $new_parent = shift;
151 my $parent_col = $self->_parent_column();
87427fdb 152 if (ref($new_parent)) {
82958127 153 $new_parent = $new_parent->id() || croak('Parent object does not have an ID');;
87427fdb 154 }
82958127 155 return 0 if ($new_parent == ($self->get_column($parent_col)||0));
98277fa5 156
157 if ($self->repair_tree()) {
158 my $found = $self->has_descendant( $new_parent );
159 if ($found) {
160 my $children = $self->children();
161
162 while (my $child = $children->next()) {
163 $child->parent( $self->$parent_col() );
164 }
165 }
166 }
167
82958127 168 $self->set_column( $parent_col => $new_parent );
87427fdb 169 $self->update();
170 return 1;
171 }
82958127 172 return $self->_parent();
87427fdb 173}
174
98277fa5 175=head2 has_descendant
c0d76c37 176
98277fa5 177 if ($employee->has_descendant( $id )) { ... }
c0d76c37 178
98277fa5 179Returns true if the object has a descendant with the
180specified ID.
c0d76c37 181
182=cut
183
98277fa5 184sub has_descendant {
185 my ($self, $find_id) = @_;
186
187 my $children = $self->children();
188 while (my $child = $children->next()) {
189 if ($child->id() eq $find_id) {
190 return 1;
c0d76c37 191 }
98277fa5 192 return 1 if ($child->has_descendant( $find_id ));
c0d76c37 193 }
c0d76c37 194
98277fa5 195 return 0;
196}
c0d76c37 197
bb17efa0 198=head2 parents
199
200 my $parents = $node->parents();
201 my @parents = $node->parents();
202
203This has_many relationship is not that useful as it will
204never return more than one parent due to the one-to-many
205structure of adjacency lists. The reason this relationship
206is defined is so that this tree type may be treated as if
207it was a DAG.
208
87427fdb 209=head2 children
210
211 my $children_rs = $employee->children();
212 my @children = $employee->children();
213
214Returns a list or record set, depending on context, of all
82958127 215the objects one level below the current one. This method
216is created when parent_column() is called, which sets up a
217has_many relationship called children.
87427fdb 218
98277fa5 219=head2 attach_child
87427fdb 220
98277fa5 221 $parent->attach_child( $child );
222 $parent->attach_child( $child, $child, ... );
87427fdb 223
9717d3ca 224Sets the child, or children, to the new parent. Returns 1
225on success and returns 0 if the parent object already has
226the child.
87427fdb 227
228=cut
229
98277fa5 230sub attach_child {
9717d3ca 231 my $self = shift;
232 my $return = 1;
233 foreach my $child (@_) {
234 $child->parent( $self );
235 }
236 return $return;
87427fdb 237}
238
82958127 239=head2 siblings
240
241 my $rs = $node->siblings();
242 my @siblings = $node->siblings();
243
244Returns either a result set or an array of all other objects
245with the same parent as the calling object.
246
247=cut
248
249sub siblings {
250 my( $self ) = @_;
251 my $parent_col = $self->_parent_column;
252 my $primary_col = ($self->primary_columns())[0];
253 my $rs = $self->result_source->resultset->search(
254 {
255 $parent_col => $self->get_column($parent_col),
256 $primary_col => { '!=' => $self->get_column($primary_col) },
257 },
258 );
259 return $rs->all() if (wantarray());
260 return $rs;
261}
262
98277fa5 263=cut
82958127 264
98277fa5 265=head2 attach_sibling
87427fdb 266
98277fa5 267 $obj->attach_sibling( $sibling );
268 $obj->attach_sibling( $sibling, $sibling, ... );
87427fdb 269
9717d3ca 270Sets the passed in object(s) to have the same parent
271as the calling object. Returns 1 on success and
2720 if the sibling already has the same parent.
87427fdb 273
274=cut
275
98277fa5 276sub attach_sibling {
9717d3ca 277 my $self = shift;
278 my $return = 1;
279 foreach my $node (@_) {
280 $return = 0 if (!$node->parent( $self->parent() ));
281 }
282 return $return;
87427fdb 283}
284
74d97bdc 285=head2 is_leaf
286
287 if ($obj->is_leaf()) { ... }
288
289Returns 1 if the object has no children, and 0 otherwise.
290
291=cut
292
293sub is_leaf {
294 my( $self ) = @_;
98277fa5 295
296 my $has_child = $self->result_source->resultset->search(
74d97bdc 297 { $self->_parent_column => $self->id() },
298 { limit => 1 }
299 )->count();
c0d76c37 300
98277fa5 301 return $has_child ? 0 : 1;
302}
c0d76c37 303
74d97bdc 304=head2 is_root
305
306 if ($obj->is_root()) { ... }
307
308Returns 1 if the object has no parent, and 0 otherwise.
309
310=cut
311
312sub is_root {
313 my( $self ) = @_;
98277fa5 314 return ( $self->get_column( $self->_parent_column ) ? 0 : 1 );
74d97bdc 315}
316
317=head2 is_branch
318
319 if ($obj->is_branch()) { ... }
320
321Returns 1 if the object has a parent and has children.
322Returns 0 otherwise.
323
324=cut
325
326sub is_branch {
327 my( $self ) = @_;
98277fa5 328 return ( ($self->is_leaf() or $self->is_root()) ? 0 : 1 );
c0d76c37 329}
330
74d97bdc 331=head2 set_primary_key
332
333This method is an override of DBIx::Class' method for setting the
334class' primary key column(s). This method passes control right on
335to the normal method after first validating that only one column is
336being selected as a primary key. If more than one column is then
337an error will be thrown.
338
339=cut
340
c0d76c37 341sub set_primary_key {
74d97bdc 342 my $self = shift;
343 if (@_>1) {
344 croak('You may only specify a single column as the primary key for adjacency tree classes');
345 }
346 return $self->next::method( @_ );
347}
348
87427fdb 3491;
350__END__
351
4b44af23 352=head1 INHERITED METHODS
353
354=head2 DBIx::Class
355
356=over 4
357
358=item *
359
360L<mk_classdata|DBIx::Class/mk_classdata>
361
362=item *
363
364L<component_base_class|DBIx::Class/component_base_class>
365
366=back
367
368=head2 DBIx::Class::Componentised
369
370=over 4
371
372=item *
373
374L<inject_base|DBIx::Class::Componentised/inject_base>
375
376=item *
377
378L<load_components|DBIx::Class::Componentised/load_components>
379
380=item *
381
382L<load_own_components|DBIx::Class::Componentised/load_own_components>
383
384=back
385
386=head2 Class::Data::Accessor
387
388=over 4
389
390=item *
391
392L<mk_classaccessor|Class::Data::Accessor/mk_classaccessor>
393
394=back
395
87427fdb 396=head1 AUTHOR
397
398Aran Clary Deltac <bluefeet@cpan.org>
399
400=head1 LICENSE
401
402You may distribute this code under the same terms as Perl itself.
403