root/lang/perl/DBIx-Class-Tree-NestedSet/trunk/lib/DBIx/Class/Tree/NestedSet.pm @ 3385

Revision 3385, 9.5 kB (checked in by chiba, 7 years ago)

lang/perl/DBIx-Class-Tree-NestedSet?:

  • added method 'single_path'
  • fixed root node's siblings not use having result_set
  • added root node's siblings test case
Line 
1package DBIx::Class::Tree::NestedSet;
2
3use strict;
4use warnings;
5use 5.8.1;
6our $VERSION = '0.02';
7
8use base qw/ DBIx::Class /;
9
10
11__PACKAGE__->mk_classdata( 'left_column_name'         => 'lft' );
12__PACKAGE__->mk_classdata( 'right_column_name'        => 'rgt' );
13__PACKAGE__->mk_classdata( 'moving_flg_column_name'   => 'mvg' );
14__PACKAGE__->mk_classdata( 'repair_tree'       => 1     );
15
16
17
18
19sub new {
20    my ( $self, $attrs ) = @_;
21   
22    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
23    if ( !defined( $attrs->{$left_col} ) ) {
24        my $left = 1;
25        my $a = $attrs->{-result_source}->resultset->search(undef, {
26            select => [{
27                max => "me.$right_col",
28            }],
29            as => 'max_right',
30        });
31        if ($a->first) {
32            $left = ($a->first->get_column('max_right') || 0) + 1;
33        }
34
35        $attrs->{$left_col}  = $left;
36        $attrs->{$right_col} = $left + 1;
37       
38    }
39    $self->next::method( $attrs, @_ );
40}
41
42sub self_and_descendants {
43    my $self = shift;
44
45    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
46    $self->result_source->resultset->search({
47        "me.$left_col" => [
48            -and =>
49                { '>=' => $self->$left_col },
50                { '<=' => $self->$right_col },
51        ],
52    }, {
53        order_by => "me.$left_col ASC",
54    });
55}
56sub descendants {
57    my $self = shift;
58
59    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
60    $self->result_source->resultset->search({
61        "me.$left_col" => [
62            -and =>
63                { '>' => $self->$left_col },
64                { '<' => $self->$right_col },
65        ],
66    }, {
67        order_by => "me.$left_col ASC",
68    });
69}
70
71sub children {
72    my $self = shift;
73   
74    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
75
76    my $table_name = $self->result_source->from;
77    my ($parent_left_col, $parent_right_col) = ($self->$left_col, $self->$right_col);
78
79    my $sub_sql = "
80        NOT IN (
81            select 1 from $table_name as some_parent
82            where
83             some_parent.$left_col > $parent_left_col and some_parent.$right_col < $parent_right_col
84             and some_parent.$left_col < me.$left_col and some_parent.$right_col > me.$right_col
85        )
86    ";
87    return $self->descendants->search({
88        "1" => \$sub_sql,
89    });
90}
91
92sub single_path {
93    my $self = shift;
94
95    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
96   
97    return $self->result_source->resultset->search({
98        $left_col  => { '<' => $self->$left_col  },
99        $right_col => { '>' => $self->$right_col },
100    }, {
101        order_by => "$left_col ASC",
102    });
103}
104
105sub parents {
106    my $self = shift;
107
108    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
109   
110    return $self->single_path->search(undef, {
111        order_by => "$left_col DESC",
112        rows     => 1,
113    });
114
115}
116
117sub parent {
118    my $self = shift;
119
120    if (@_) {
121        return $self->reparent( shift );
122    }
123    return $self->parents->first;
124}
125
126sub reparent {
127    my ($self, $new_parent) = @_;
128   
129    my $primary_col = ($self->primary_columns())[0];
130   
131    # renewal object
132    $self = $self->result_source->resultset->find($self->$primary_col);
133    if ( $new_parent ) {
134        if ( ref $new_parent ) {
135            $new_parent = $self->result_source->resultset->find($new_parent->$primary_col);
136        }
137        else {
138            $new_parent = $self->result_source->resultset->find($new_parent);
139        }
140    }
141
142    my $self_parent = $self->parent;
143    if ( $new_parent ) {
144        return 0 if ( $self->$primary_col == $new_parent->$primary_col );
145        return 0 if ( $self_parent && $new_parent->$primary_col == $self_parent->$primary_col );
146    }
147    else {
148        return 0 if !$self_parent;
149    }
150
151    my ($left_col, $right_col, $moving_flg_col) = ($self->left_column_name, $self->right_column_name, $self->moving_flg_column_name);
152   
153    if ($new_parent && $self->repair_tree) {
154        my $found = $self->has_descendant( $new_parent->$primary_col );
155        if ($found) {
156            my $children = $self->children;
157            my $self_parent = $self->parent;
158            while (my $child = $children->next) {
159                $child->parent( $self_parent );
160            }
161            $self = $self->result_source->resultset->find($self->$primary_col);
162            $new_parent = $self->result_source->resultset->find($new_parent->$primary_col) if $new_parent;
163        }
164    }
165
166    my ($orig_left, $orig_right) = ($self->$left_col, $self->$right_col);
167    my $width = $orig_right - $orig_left + 1;
168
169    $self->self_and_descendants->update({
170        $moving_flg_col => 1,
171    });
172   
173    $self->result_source->resultset->search({
174        "me.$right_col" => { '>' => $orig_right },
175    })->update({
176        "$right_col" => \"$right_col - $width",
177    });
178    $self->result_source->resultset->search({
179        "me.$left_col" => { '>' => $orig_right },
180    })->update({
181        "$left_col" => \"$left_col - $width",
182    });
183
184    my $diff;
185
186    if (defined $new_parent) {
187        $new_parent = $self->result_source->resultset->find($new_parent->$primary_col);
188
189        my $dest_right = $new_parent->$right_col;
190        $diff = $dest_right - $orig_left;
191
192        $self->result_source->resultset->search({
193            "me.$moving_flg_col" => 0,
194            "me.$right_col"      => { '>=' => $dest_right },
195        })->update({
196            "$right_col" => \"$right_col + $width",
197        });
198        $self->result_source->resultset->search({
199            "me.$moving_flg_col" => 0,
200            "me.$left_col"       => { '>=' => $dest_right },
201        })->update({
202            "$left_col" => \"$left_col + $width",
203        });
204    }
205    else {
206        my $dest_right = $self->result_source->resultset->search({
207                "me.$moving_flg_col" => 0,
208            },{
209            select => [{
210                max => "me.$right_col",
211            }],
212            as => 'max_right',
213        })->first->get_column('max_right');
214
215        $diff = $dest_right - $orig_left + 1;
216    }
217
218    $self->result_source->resultset->search({
219        "me.$moving_flg_col" => 1,
220    })->update({
221        "$moving_flg_col" => 0,
222        "$left_col"       => \"$left_col + $diff",
223        "$right_col"      => \"$right_col + $diff",
224    });
225
226    return 1;
227}
228
229
230sub has_descendant {
231    my ($self, $find_id) = @_;
232
233    return 1 if $self->descendants->find($find_id);
234    return 0;
235}
236
237
238sub attach_child {
239    my $self = shift;
240
241    my $return = 1;
242    foreach my $child (@_) {
243        $return = $child->reparent( $self );
244    }
245
246    return $return;
247}
248
249sub siblings {
250    my $self = shift;
251
252    my $primary_col = ($self->primary_columns())[0];
253    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
254
255    my $parent = $self->parent;
256    my $rs;
257    if ($parent) {
258        $rs = $parent->children->search({
259            "me.$primary_col" => { '!=' => $self->$primary_col },
260        });
261    }
262    else {
263        # get root nodes
264        my $table_name = $self->result_source->from;
265        my $sub_sql = "
266            NOT IN (
267                select 1 from $table_name as some_parent
268                where
269                 some_parent.$left_col < me.$left_col and some_parent.$right_col > me.$right_col
270            )
271        ";
272        return $self->result_source->resultset->search({
273            "1" => \$sub_sql,
274        });
275    }
276
277    return $rs->all if (wantarray());
278    return $rs;
279}
280
281sub attach_sibling {
282    my $self = shift;
283
284    my $return = 1;
285    foreach my $node (@_) {
286        $return = $node->reparent( $self->parent );
287    }
288    return $return;
289}
290
291sub is_reaf {
292    my $self = shift;
293
294    my ($left_col, $right_col) = ($self->left_column_name, $self->right_column_name);
295    return ( ( $self->$right_col - $self->$left_col ) == 1 );
296}
297
298sub is_root {
299    my $self = shift;
300
301    return $self->parent ? 0 : 1;
302}
303
304sub is_branch {
305    my $self = shift;
306
307    return ( $self->is_reaf or $self->is_root ) ? 0 : 1;
308}
309
310
3111;
312
313
314__END__
315
316=head1 NAME
317
318DBIx::Class::Tree::NestedSet
319
320=head1 SYNOPSIS
321
322Create a table for your tree data.
323
324  CREATE TABLE categories (
325    id INTEGER PRIMARY KEY AUTOINCREMENT,
326    name TEXT,
327    lft INTEGER NOT NULL,
328    rgt INTEGER NOT NULL,
329    mvg TINYINT DEFAUL 0
330  );
331
332In your Schema
333
334  __PACKAGE__->load_components(qw/Tree::NestedSet/);
335  __PACKAGE__->left_column_name( 'lft'); # DEFAULT value is 'lft'
336  __PACKAGE__->right_column_name( 'rgt'); # DEFAULT value is 'rgt'
337  __PACKAGE__->moving_flg_column_name( 'mvg'); # DEFAULT value is 'mvg'
338
339
340In your Code
341
342  my $category = $resultset->create({ name => 'ELECTRONICS' });
343
344  my $children = $category->children;
345  my $siblings = $category->siblings;
346  my $descendants = $category->descendants;
347
348  my $parent = $category->parent;
349  $category->parent( $other_parent );
350
351
352=head1 DESCRIPTION
353
354This module provides methods for working with nestedset lists.
355
356=head1 METHODS
357
358=head2 left_column_name
359
360=head2 right_column_name
361
362=head2 moving_flg_column_name
363
364
365=head2 parent
366
367=head2 has_descendant
368
369=head2 parents
370
371=head2 descendants
372
373=head2 children
374
375=head2 attach_child
376
377=head2 siblings
378
379=head2 attach_sibling
380
381=head2 is_leaf
382
383=head2 is_root
384
385=head2 is_branch
386
387
388=head1 AUTHOR
389
390Masahiro Chiba E<lt>chiba@geminium.comE<gt>
391
392=head1 LICENSE
393
394This library is free software; you can redistribute it and/or modify
395it under the same terms as Perl itself.
396
397=head1 SEE ALSO
398
399http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
400
401DBIx::Class::Tree - copied from this module's API & Test
402
403DBIx::OO::Tree
404
405DBIx::Tree::NestedSet
406
407
408=cut
Note: See TracBrowser for help on using the browser.