Fix copy
[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" } );
1dfd20a4 88 $class->has_many( 'parents' => $class => { "foreign.$primary_col" => "self.$parent_col" }, { cascade_delete => 0, cascade_copy => 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=head2 attach_sibling
87427fdb 264
98277fa5 265 $obj->attach_sibling( $sibling );
266 $obj->attach_sibling( $sibling, $sibling, ... );
87427fdb 267
9717d3ca 268Sets the passed in object(s) to have the same parent
269as the calling object. Returns 1 on success and
2700 if the sibling already has the same parent.
87427fdb 271
272=cut
273
98277fa5 274sub attach_sibling {
9717d3ca 275 my $self = shift;
276 my $return = 1;
277 foreach my $node (@_) {
278 $return = 0 if (!$node->parent( $self->parent() ));
279 }
280 return $return;
87427fdb 281}
282
74d97bdc 283=head2 is_leaf
284
285 if ($obj->is_leaf()) { ... }
286
287Returns 1 if the object has no children, and 0 otherwise.
288
289=cut
290
291sub is_leaf {
292 my( $self ) = @_;
98277fa5 293
294 my $has_child = $self->result_source->resultset->search(
74d97bdc 295 { $self->_parent_column => $self->id() },
296 { limit => 1 }
297 )->count();
c0d76c37 298
98277fa5 299 return $has_child ? 0 : 1;
300}
c0d76c37 301
74d97bdc 302=head2 is_root
303
304 if ($obj->is_root()) { ... }
305
306Returns 1 if the object has no parent, and 0 otherwise.
307
308=cut
309
310sub is_root {
311 my( $self ) = @_;
98277fa5 312 return ( $self->get_column( $self->_parent_column ) ? 0 : 1 );
74d97bdc 313}
314
315=head2 is_branch
316
317 if ($obj->is_branch()) { ... }
318
319Returns 1 if the object has a parent and has children.
320Returns 0 otherwise.
321
322=cut
323
324sub is_branch {
325 my( $self ) = @_;
98277fa5 326 return ( ($self->is_leaf() or $self->is_root()) ? 0 : 1 );
c0d76c37 327}
328
74d97bdc 329=head2 set_primary_key
330
331This method is an override of DBIx::Class' method for setting the
332class' primary key column(s). This method passes control right on
333to the normal method after first validating that only one column is
334being selected as a primary key. If more than one column is then
335an error will be thrown.
336
337=cut
338
c0d76c37 339sub set_primary_key {
74d97bdc 340 my $self = shift;
341 if (@_>1) {
342 croak('You may only specify a single column as the primary key for adjacency tree classes');
343 }
344 return $self->next::method( @_ );
345}
346
87427fdb 3471;
348__END__
349
4b44af23 350=head1 INHERITED METHODS
351
352=head2 DBIx::Class
353
354=over 4
355
356=item *
357
358L<mk_classdata|DBIx::Class/mk_classdata>
359
360=item *
361
362L<component_base_class|DBIx::Class/component_base_class>
363
364=back
365
366=head2 DBIx::Class::Componentised
367
368=over 4
369
370=item *
371
372L<inject_base|DBIx::Class::Componentised/inject_base>
373
374=item *
375
376L<load_components|DBIx::Class::Componentised/load_components>
377
378=item *
379
380L<load_own_components|DBIx::Class::Componentised/load_own_components>
381
382=back
383
384=head2 Class::Data::Accessor
385
386=over 4
387
388=item *
389
390L<mk_classaccessor|Class::Data::Accessor/mk_classaccessor>
391
392=back
393
87427fdb 394=head1 AUTHOR
395
396Aran Clary Deltac <bluefeet@cpan.org>
397
398=head1 LICENSE
399
400You may distribute this code under the same terms as Perl itself.
401