File Coverage

blib/lib/DBICx/MaterializedPath.pm
Criterion Covered Total %
statement 65 69 94.2
branch 6 8 75.0
condition 2 7 28.5
subroutine 13 14 92.8
pod 8 8 100.0
total 94 106 88.6


line stmt bran cond sub pod time code
1             package DBICx::MaterializedPath;
2 3     3   244823 use warnings;
  3         8  
  3         88  
3 3     3   14 use strict;
  3         6  
  3         90  
4 3     3   1222 use parent "DBIx::Class";
  3         532  
  3         16  
5 3     3   84440 use Carp;
  3         6  
  3         5075  
6              
7             our $VERSION = "0.03";
8             our $AUTHORITY = "cpan:ASHLEY";
9              
10             __PACKAGE__->mk_classdata( parent_column => "parent" );
11             __PACKAGE__->mk_classdata( path_column => "materialized_path" );
12             __PACKAGE__->mk_classdata( path_separator => "/" );
13             __PACKAGE__->mk_classdata( max_depth => 500 );
14              
15             # Max depth setting? See notes on sanity check inline below.
16              
17             sub _compute_ancestors :method {
18 181     181   22826 my ( $self, @ancestors ) = @_;
19 181         5076 my $parent_column = $self->parent_column;
20 181         12492 my $parent = $self->$parent_column;
21 181 100       579250 return @ancestors unless $parent;
22 140         654 unshift @ancestors, $parent;
23 140 50       4738 croak "Circular lineage loop in $self suspected!" if @ancestors > $self->max_depth;
24 140         8237 $parent->_compute_ancestors(@ancestors);
25             }
26              
27             sub ancestors :method {
28 3     3 1 8 my $self = shift;
29 3         44 my ( $pk_name ) = $self->primary_columns;
30 3         250 my $path_column = $self->path_column;
31 3         60 my @path = $self->_nodelist;
32 3         50 pop @path;
33 3 50       12 return unless @path;
34 3         39 $self->result_source
35             ->resultset
36             ->search({ $pk_name => { -in => \@path } },
37             { order_by => \"LENGTH($path_column)" }); # "
38             }
39              
40             sub node_depth :method {
41 7     7 1 5716 scalar(+shift->_nodelist);
42             }
43              
44             sub _nodelist :method {
45 14     14   33 my $self = shift;
46 14   50     402 my $path_column = $self->path_column || "";
47 14         675 my $separator = quotemeta( $self->path_separator );
48 14   50     979 split($separator, $self->$path_column || "");
49             }
50              
51             sub root_node :method {
52 4     4 1 5186 my $self = shift;
53 4         19 my ( $root_id ) = $self->_nodelist;
54 4         78 $self->result_source->resultset->find($root_id);
55             }
56              
57             # Note caveat, instructions about children method.
58              
59             # How can order_by get into this mix?
60             sub grandchildren {
61 5     5 1 13014 my $self = shift;
62            
63 5         149 my $path_separator = $self->path_separator;
64 5         471 my $path_column = $self->path_column;
65 5         224 my $id = $self->id;
66              
67             # Example: 1/2/3
68             # to find descendants of 1, use LIKE "1/%"
69             # to find descendants of 2, use LIKE "%/2/%"
70 5         82 my $like_if_root = "${id}${path_separator}\%";
71 5         15 my $like_not_root = "\%${path_separator}${id}${path_separator}\%";
72              
73 5         21 my @grandkids = $self->result_source->resultset->search(
74             {
75             -or => [
76             $path_column => { 'like', $like_if_root },
77             $path_column => { 'like', $like_not_root },
78             ]
79             },
80             {
81             order_by => \"LENGTH($path_column)"
82             },
83             );
84 5         17266 return @grandkids;
85             }
86              
87             sub set_materialized_path :method {
88 38     38 1 82 my $self = shift;
89 38         1206 my $parent_column = $self->parent_column;
90 38         2818 my $path_column = $self->path_column;
91 38         865 my @path_parts = map { $_->id } $self->_compute_ancestors;
  134         4865  
92 38         1307 push @path_parts, $self->id;
93 38         1385 my $materialized_path = join( $self->path_separator, @path_parts );
94 38         2856 $self->$path_column( $materialized_path );
95 38         5729 return $materialized_path; # For good measure.
96             }
97              
98             sub insert :method {
99 25     25 1 1206072 my $self = shift;
100 25         240 $self->next::method(@_);
101 25         50029 $self->set_materialized_path;
102 25         119 $self->update;
103             }
104              
105             sub update :method {
106 38     38 1 3621 my $self = shift;
107 38         184 my %to_update = $self->get_dirty_columns;
108 38         1398 my $parent_column = $self->parent_column;
109 38         1768 $self->next::method(@_);
110 38 100       48975 return $self unless $to_update{$parent_column};
111             # This should be configurable as a transaction I think. 321
112 1         5 $self->set_materialized_path;
113 1         4 for my $descendant ( $self->grandchildren )
114             {
115 12         51 $descendant->set_materialized_path;
116 12         61 $descendant->update;
117             }
118 1         224 return $self;
119             }
120              
121             # Previous and next support here.
122              
123             sub siblings :method {
124 0     0 1   my $self = shift;
125 0           my $parent_column = $self->parent_column;
126 0   0       my $sort = [ $self->_sibling_order || $self->primary_columns ];
127 0           $self->result_source
128             ->resultset
129             ->search({ $parent_column => $self->$parent_column },
130             { order_by => $sort });
131             }
132              
133             1;
134              
135             __END__