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