File Coverage

blib/lib/Class/DBI/Relationship/HasManyOrdered.pm
Criterion Covered Total %
statement 9 183 4.9
branch 0 80 0.0
condition 0 22 0.0
subroutine 3 16 18.7
pod 2 2 100.0
total 14 303 4.6


line stmt bran cond sub pod time code
1             package Class::DBI::Relationship::HasManyOrdered;
2              
3 1     1   23674 use strict;
  1         3  
  1         49  
4 1     1   6 use warnings;
  1         2  
  1         51  
5              
6             our $VERSION = '0.03';
7              
8 1     1   6 use base qw(Class::DBI::Relationship::HasMany);
  1         7  
  1         1326  
9              
10             ##########
11             # over-ridden Class::DBI::Relationship methods
12              
13             sub methods {
14 0     0 1   my $self = shift;
15 0           my $accessor = $self->accessor;
16             return (
17 0           $accessor => $self->_has_many_ordered_method,
18             "${accessor}_asIndex" => $self->_has_many_ordered_asindex_method,
19             "append_to_$accessor" => $self->_method_insert('append'),
20             "prepend_to_$accessor" => $self->_method_insert('prepend'),
21             "append_$accessor" => $self->_method_insert('append'),
22             "prepend_$accessor" => $self->_method_insert('prepend'),
23             "insert_$accessor" => $self->_method_insert,
24             "delete_$accessor" => $self->_method_delete,
25             "replace_$accessor" => $self->_method_replace,
26             );
27             }
28              
29             sub triggers {
30 0     0 1   my $self = shift;
31 0           my $accessor = $self->accessor;
32             return (
33             before_delete => sub {
34 0     0     my $self = shift;
35 0           my $meta = ref($self)->meta_info(has_many => $accessor);
36 0           my ($f_class, $f_key, $args) =
37             ($meta->foreign_class, $meta->args->{foreign_key}, $meta->args);
38 0 0         if ($meta->args->{map}) {
39 0           my $pk = $self->columns('Primary');
40 0           my $sth = $self->db_Main->prepare("delete from ".$meta->args->{map}." where $pk = ?");
41 0           my $rv = $sth->execute($self->id);
42             } else {
43 0 0         return if $self->args->{no_cascade_delete}; # undocumented and untested!
44 0           $f_class->search($f_key => $self->id)->delete_all;
45             }
46 0           });
47             }
48              
49             ###########
50              
51             sub _method_insert {
52 0     0     my $self = shift;
53 0           my $mode = shift;
54 0           my $accessor = $self->accessor;
55 0 0         my $methodname = ($mode) ? "${mode}_to_$accessor" : "insert_$accessor" ;
56             return sub {
57 0     0     my ($self, $data,$position) = @_;
58 0 0 0       $mode = 'append' unless (defined $position || $mode);
59 0 0         $position = 0 if ($mode eq 'prepend');
60 0 0         my $class = ref $self
61             or return $self->_croak("$methodname called as class method");
62 0 0         return $self->_croak("$methodname needs data")
63             unless defined $data;
64              
65 0           my $meta = $class->meta_info(has_many_ordered => $accessor);
66 0           my $order_column = $meta->args->{order_by};
67 0           my $pk = $self->columns('Primary');
68 0           my ($f_class, $f_key) = ($meta->foreign_class, $meta->foreign_class->columns('Primary'));
69              
70 0 0         if ($mode eq 'append') {
71 0 0         my $sql = ($meta->args->{map}) ? "select max($order_column) + 1 from ".$meta->args->{map} ." where $pk = ?" : "select max($order_column) + 1 from ".$self->table." where $f_key = ?";
72 0           my $sth = $self->db_Main->prepare($sql);
73 0           my $rv = $sth->execute($self->id);
74 0 0         if ($rv) {
75 0           ($position) = $sth->fetchrow_array();
76             }
77             }
78 0   0       $position ||= 0;
79 0   0       my $maptable = $meta->args->{map} || '';
80 0           my $orderby = $meta->args->{order_by};
81 0           my $fclass_table = $f_class->table;
82              
83 0 0         my @objects = ((ref $data eq 'ARRAY') ? @$data : $data);
84 0           foreach my $data (@objects) {
85             # check if data is one of string (must be id), object, hash or array of either
86 0           my $f_object;
87             my $f_object_id;
88 0 0         if (ref $data eq 'HASH') {
    0          
89             # create new object
90 0           $f_object = $f_class->create($data);
91 0           $f_object_id = $f_object->id;
92             } elsif (ref $data eq $f_class) { # data is object of foreign class
93 0           $f_object = $data;
94 0           $f_object_id = $f_object->id;
95             } else { # data is object id
96 0 0         if (ref $data) { # check is scalar
97 0           warn "got ",ref $data," expected $f_class \n";
98 0           die "$methodname requires one or more valid object ids, objects, or hashes - got an unexpected reference";
99             }
100 0           $data =~ s/\s//g;
101 0 0         if ($data =~ /\D/) { # check is numeric
102 0           die "$methodname requires one or more valid object ids, objects, or hashes - got an unexpected value";
103             }
104 0           $f_object_id = $data;
105             }
106              
107 0 0         if ($maptable) {
108             # reset positions
109 0 0         unless ($mode eq 'append') {
110 0           my $query = "update $maptable set $orderby = $orderby + 1 where $orderby >= ? and $pk = ?";
111 0           my $sth = $self->db_Main->prepare($query);
112 0           my $rv = $sth->execute($position,$self->id);
113             }
114              
115             # insert new side-table entry
116 0           my $sth = $self->db_Main->prepare("insert into $maptable ($pk, $f_key, $orderby) values ( ?, ?, ? )");
117 0           my $rv = $sth->execute($self->id, $f_object_id ,$position);
118             } else {
119 0 0         unless ($mode eq 'append') {
120 0           my $query = "update $fclass_table set $orderby = $orderby + 1 where $orderby >= ? and $pk = ?";
121 0           my $sth = $self->db_Main->prepare($query);
122 0           my $rv = $sth->execute($position,$self->id);
123             }
124 0 0         $f_object = $f_class->retrieve($f_object_id) unless (ref $f_object eq $f_class);
125 0           $f_object->{$f_key} = $self->id;
126 0           $f_object->{$orderby} = $position;
127 0           $f_object->update();
128             }
129 0           $position++;
130             }
131 0           return scalar @objects;
132 0           };
133             }
134              
135              
136             sub _method_delete {
137 0     0     my $self = shift;
138 0           my $accessor = $self->accessor;
139 0           my $methodname = "delete_$accessor";
140             return sub {
141 0     0     my ($self, @args) = @_;
142 0 0         my $class = ref $self
143             or return $self->_croak("$methodname called as class method");
144 0 0         return $self->_croak("$methodname needs position or objects")
145             unless defined $args[0];
146 0           my $meta = $class->meta_info(has_many_ordered => $accessor);
147 0           my $pk = $self->columns('Primary');
148 0           my ($f_class, $f_key) = ($meta->foreign_class, $meta->foreign_class->columns('Primary'));
149 0           my $fclass_table = $f_class->table;
150              
151 0 0         if ($args[0] =~ /object/) {
152             # data must be one of string (must be id) or object or array of either
153 0           my $data = $args[1];
154 0 0         my @objects = ((ref $data eq 'ARRAY') ? @$data : $data);
155 0           foreach my $data (@objects) {
156 0 0         if (ref $data eq $f_class) { # is object of foreign class
157 0 0         if ($meta->args->{map}) { # check if using mapping table
158 0           my $sth = $self->db_Main->prepare("delete from ".$meta->args->{map}." where $pk = ? and $f_key = ?");
159 0           my $rv = $sth->execute($self->id, $data->id);
160             } else {
161 0           $data->{$f_key} = $self->id; # FIXME: may not work for inherited relationships
162 0           $data->delete();
163             }
164             } else { # is object id
165 0 0         if (ref $data) { # check is scalar
166 0           die "$methodname requires one or more valid object ids, objects, or hashes - got an unexpected reference";
167             }
168              
169 0 0         if ($data =~ /\D/) { # check is numeric
170 0           die "$methodname requires one or more valid object ids, objects, or hashes - got an unexpected value";
171             }
172              
173 0 0         if ($meta->args->{map}) { # check if using mapping table
174 0           my $sth = $self->db_Main->prepare("delete from ".$meta->args->{map}." where $pk = ? and $f_key = ?");
175 0           my $rv = $sth->execute($self->id, $data);
176             } else {
177 0           my $f_object = $f_class->retrieve($data);
178 0 0         unless ($f_object) {
179 0           die "$data is not a valid id for ".$f_class->table." in $methodname\n";
180             }
181 0           $f_object->delete();
182             }
183             }
184             }
185             } else {
186 0 0         my @elements = ((ref $args[0] eq 'ARRAY') ? @{$args[0]} : @args);
  0            
187 0           my $placeholder = join(',',map( '?',@elements));
188 0           my $orderby = $meta->args->{order_by};
189 0 0         if ($meta->args->{map}) { # check if using mapping table
190 0           my $sth = $self->db_Main->prepare("delete from ".$meta->args->{map}." where $pk = ? and $orderby IN ($placeholder)");
191 0           my $rv = $sth->execute($self->id, @elements);
192             } else {
193 0           my $query = "select $f_key as PK from $fclass_table where $pk = ? and $orderby in ($placeholder)";
194 0           my $sth = $self->db_Main->prepare($query);
195 0           my $rv = $sth->execute(@elements);
196 0           my @ids = keys %{$sth->fetchall_hashref( 'PK' )};
  0            
197 0           foreach (@ids) {
198 0           my $f_object = $f_class->retrieve($_);
199 0           $f_object->delete();
200             }
201             }
202             }
203 0           };
204             }
205              
206             sub _method_replace {
207 0     0     my $self = shift;
208 0           my $accessor = $self->accessor;
209 0           my $methodname = "replace_$accessor";
210 0           my $deletemethod = "delete_$accessor";
211 0           my $insertmethod = "insert_$accessor";
212             return sub {
213 0     0     my ($self, $data) = @_;
214 0 0         my $class = ref $self
215             or return $self->_croak("$methodname called as class method");
216 0 0         return $self->_croak("$methodname needs objects or id's")
217             unless defined $data;
218             # remove current object
219 0           $self->$deletemethod($self->$accessor);
220             # insert new objects
221 0           return $self->$insertmethod($data);
222 0           };
223             }
224              
225             sub _has_many_ordered_asindex_method {
226 0     0     my $self = shift;
227 0           my $accessor = $self->accessor;
228             return sub {
229 0     0     my ($self,$id_field,$title_field) = @_;
230 0           my $meta = ref($self)->meta_info(has_many_ordered => $accessor);
231 0           my $pk = $self->columns('Primary');
232 0           my ($f_class, $f_key) = ($meta->foreign_class, $meta->foreign_class->columns('Primary'));
233 0   0       $id_field ||= $f_key;
234 0 0         if (ref $self) {
235 0   0       $title_field ||= $self->{"_${accessor}_index"} ||
      0        
236             ($self->{"_${accessor}_index"} = (grep(/(name|title)/i, sort($f_class->columns('All'))))[0]) ;
237             } else {
238 0   0       $title_field ||= (grep(/(name|title)/i, sort($f_class->columns('All'))))[0];
239             }
240 0 0         die unless ($title_field);
241              
242 0           my $maptable = $meta->args->{map};
243 0           my $orderby = $meta->args->{order_by};
244 0           my $f_table = $f_class->table;
245             # FIXME: probably doesn't handle inherited fields in id or title fields
246 0           my @args = ();
247 0           my $query = "select $id_field, $title_field from $f_table where $pk = ? order by $orderby";
248 0 0         if ($maptable) {
249 0           $query = "select ${f_table}.${id_field}, ${f_table}.$title_field from ${f_table}, $maptable " .
250             "where ${maptable}.$f_key = ${f_table}.$f_key and ${maptable}.$pk = ? order by $orderby";
251             }
252 0           my $sth = $f_class->db_Main->prepare($query);
253 0           my $rv = $sth->execute($self->id);
254 0           return $sth->fetchall_arrayref();
255 0           };
256             }
257              
258             sub _has_many_ordered_method {
259 0     0     my $self = shift;
260 0           my $accessor = $self->accessor;
261             return sub {
262 0     0     my ($self,$key,$value) = @_;
263 0           my $meta = ref($self)->meta_info(has_many_ordered => $accessor);
264 0           my $pk = $self->columns('Primary');
265 0           my ($f_class, $f_key) = ($meta->foreign_class, $meta->foreign_class->columns('Primary'));
266 0           my $maptable = $meta->args->{map};
267 0           my $orderby = $meta->args->{order_by};
268              
269 0 0         if ($maptable) {
270 0           my $f_table = $f_class->table;
271 0 0         my @columns = ( $f_class->columns('Essential') ) ? $f_class->columns('Essential') : $f_class->columns('All');
272 0           my $query = 'SELECT '. join(', ',map { s/$f_key/$f_table.$f_key/i; $_; } @columns). " FROM $maptable, $f_table WHERE " .
  0            
  0            
273             "${maptable}.$f_key = ${f_table}.$f_key and ${maptable}.$pk = ? order by ${maptable}.$orderby";
274 0           my $sth = $f_class->db_Main->prepare($query);
275 0           my $rv = $sth->execute($self->id);
276 0           return $f_class->sth_to_objects($sth);
277             } else {
278 0           my @args = ($f_key => $self->id);
279 0 0 0       if ($key && defined $value) {
    0          
280 0           push(@args,($key => $value));
281             } elsif (defined $key) {
282 0           push(@args,($orderby => $key));
283             }
284 0           return $f_class->search(@args);
285             }
286 0           };
287             }
288              
289             #
290              
291             1;
292              
293             __END__