Update to current cpan version
[dbsrgits/DBIx-Class-Tree.git] / lib / DBIx / Class / Tree / AdjacencyList.pm
1 package DBIx::Class::Tree::AdjacencyList;
2 # vim: ts=8:sw=4:sts=4:et
3
4 use strict;
5 use warnings;
6
7 use base qw( DBIx::Class );
8 use Carp qw( croak );
9
10 =head1 NAME
11
12 DBIx::Class::Tree::AdjacencyList - Manage a tree of data using the common adjacency list model.
13
14 =head1 SYNOPSIS
15
16 Create a table for your tree data.
17
18   CREATE TABLE employees (
19     employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
20     parent_id INTEGER NOT NULL DEFAULT 0,
21     name TEXT NOT NULL
22   );
23
24 In your Schema or DB class add Tree::AdjacencyList to the top 
25 of the component list.
26
27   __PACKAGE__->load_components(qw( Tree::AdjacencyList ... ));
28
29 Specify the column that contains the parent ID of each row.
30
31   package My::Employee;
32   __PACKAGE__->parent_column('parent_id');
33
34 Optionally, automatically maintane a consistent tree structure.
35
36   __PACKAGE__->repair_tree( 1 );
37
38 Thats it, now you can modify and analyze the tree.
39
40   #!/usr/bin/perl
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
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.
61
62 =head1 METHODS
63
64 =head2 parent_column
65
66   __PACKAGE__->parent_column('parent_id');
67
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) 
71 relationship.
72
73 This method also setups an additional has_many relationship called 
74 parents which is useful when you want to treat an adjacency list 
75 as a DAG.
76
77 =cut
78
79 __PACKAGE__->mk_classdata( '_parent_column' => 'parent_id' );
80
81 sub 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" } );
88         $class->has_many( 'parents' => $class => { "foreign.$primary_col" => "self.$parent_col" }, { cascade_delete => 0 } );
89         $class->_parent_column( $parent_col );
90         return 1;
91     }
92     return $class->_parent_column();
93 }
94
95 =head2 repair_tree
96
97   __PACKAGE__->repair_tree( 1 );
98
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.
103
104 So, for example, if the tree is like this:
105
106   A
107     B
108       C
109       D
110         E
111     F
112
113 And you execute:
114
115   $b->parent( $d );
116
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.
119
120   A
121     C
122     D
123       B
124       E
125     F
126
127 =cut
128
129 __PACKAGE__->mk_classdata( 'repair_tree' => 0 );
130
131 =head2 parent
132
133   my $parent = $employee->parent();
134   $employee->parent( $parent_obj );
135   $employee->parent( $parent_id );
136
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.
140
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 
143 success.
144
145 =cut
146
147 sub parent {
148     my $self = shift;
149     if (@_) {
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');;
154         }
155         return 0 if ($new_parent == ($self->get_column($parent_col)||0));
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
168         $self->set_column( $parent_col => $new_parent );
169         $self->update();
170         return 1;
171     }
172     return $self->_parent();
173 }
174
175 =head2 has_descendant
176
177   if ($employee->has_descendant( $id )) { ... }
178
179 Returns true if the object has a descendant with the
180 specified ID.
181
182 =cut
183
184 sub 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;
191         }
192         return 1 if ($child->has_descendant( $find_id ));
193     }
194
195     return 0;
196 }
197
198 =head2 parents
199
200   my $parents = $node->parents();
201   my @parents = $node->parents();
202
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 
207 it was a DAG.
208
209 =head2 children
210
211   my $children_rs = $employee->children();
212   my @children = $employee->children();
213
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.
218
219 =head2 attach_child
220
221   $parent->attach_child( $child );
222   $parent->attach_child( $child, $child, ... );
223
224 Sets the child, or children, to the new parent.  Returns 1 
225 on success and returns 0 if the parent object already has 
226 the child.
227
228 =cut
229
230 sub attach_child {
231     my $self = shift;
232     my $return = 1;
233     foreach my $child (@_) {
234         $child->parent( $self );
235     }
236     return $return;
237 }
238
239 =head2 siblings
240
241   my $rs = $node->siblings();
242   my @siblings = $node->siblings();
243
244 Returns either a result set or an array of all other objects 
245 with the same parent as the calling object.
246
247 =cut
248
249 sub 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
263 =cut
264
265 =head2 attach_sibling
266
267   $obj->attach_sibling( $sibling );
268   $obj->attach_sibling( $sibling, $sibling, ... );
269
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.
273
274 =cut
275
276 sub attach_sibling {
277     my $self = shift;
278     my $return = 1;
279     foreach my $node (@_) {
280         $return = 0 if (!$node->parent( $self->parent() ));
281     }
282     return $return;
283 }
284
285 =head2 is_leaf
286
287   if ($obj->is_leaf()) { ... }
288
289 Returns 1 if the object has no children, and 0 otherwise.
290
291 =cut
292
293 sub is_leaf {
294     my( $self ) = @_;
295
296     my $has_child = $self->result_source->resultset->search(
297         { $self->_parent_column => $self->id() },
298         { limit => 1 }
299     )->count();
300
301     return $has_child ? 0 : 1;
302 }
303
304 =head2 is_root
305
306   if ($obj->is_root()) { ... }
307
308 Returns 1 if the object has no parent, and 0 otherwise.
309
310 =cut
311
312 sub is_root {
313     my( $self ) = @_;
314     return ( $self->get_column( $self->_parent_column ) ? 0 : 1 );
315 }
316
317 =head2 is_branch
318
319   if ($obj->is_branch()) { ... }
320
321 Returns 1 if the object has a parent and has children.  
322 Returns 0 otherwise.
323
324 =cut
325
326 sub is_branch {
327     my( $self ) = @_;
328     return ( ($self->is_leaf() or $self->is_root()) ? 0 : 1 );
329 }
330
331 =head2 set_primary_key
332
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.
338
339 =cut
340
341 sub set_primary_key {
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
349 1;
350 __END__
351
352 =head1 INHERITED METHODS
353
354 =head2 DBIx::Class
355
356 =over 4
357
358 =item *
359
360 L<mk_classdata|DBIx::Class/mk_classdata>
361
362 =item *
363
364 L<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
374 L<inject_base|DBIx::Class::Componentised/inject_base>
375
376 =item *
377
378 L<load_components|DBIx::Class::Componentised/load_components>
379
380 =item *
381
382 L<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
392 L<mk_classaccessor|Class::Data::Accessor/mk_classaccessor>
393
394 =back
395
396 =head1 AUTHOR
397
398 Aran Clary Deltac <bluefeet@cpan.org>
399
400 =head1 LICENSE
401
402 You may distribute this code under the same terms as Perl itself.
403