File Coverage

blib/lib/Class/ReluctantORM/FilterSupport.pm
Criterion Covered Total %
statement 18 195 9.2
branch 0 68 0.0
condition 0 19 0.0
subroutine 6 20 30.0
pod 8 8 100.0
total 32 310 10.3


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::FilterSupport;
2 1     1   5 use strict;
  1         2  
  1         24  
3 1     1   5 use warnings;
  1         2  
  1         19  
4 1     1   5 use Class::ReluctantORM::Exception;
  1         3  
  1         30  
5              
6              
7             =head1 NAME
8              
9             Class::ReluctantORM::FilterSupport - Mix-In to TB to provide hooks for filters
10              
11             =head1 SYNOPSIS
12              
13             # No user-servicable parts.
14              
15             =head1 Description
16              
17             Contains the guts of the Filter mechanism. For more info, see L .
18              
19             =head1 AUTHOR
20              
21             Clinton Wolfe clinton@omniti.com
22              
23             =cut
24              
25             1;
26              
27             package Class::ReluctantORM;
28 1     1   4 use strict;
  1         2  
  1         18  
29 1     1   10 use warnings;
  1         2  
  1         23  
30 1     1   4 use Class::ReluctantORM::Utilities qw(conditional_load);
  1         2  
  1         1813  
31              
32             =for devdocs
33              
34             =head2 $obj->attach_filter()
35              
36             Bad method name, add an alias.
37              
38             =cut
39              
40             sub attach_filter {
41 0     0 1   my $inv = shift;
42 0 0         if (ref($inv)) {
43 0           $inv->append_filter(@_);
44             } else {
45 0           $inv->attach_class_filter(@_);
46             }
47             }
48              
49             sub attach_class_filter {
50 0     0 1   my $class = shift;
51 0 0         if (ref $class) { $class = ref $class; }
  0            
52 0           my ($filter_class, @fields) = $class->__read_attach_filter_params(@_);
53              
54 0           my $metadata = $class->__metadata();
55 0   0       $metadata->{filters} ||= {};
56 0           foreach my $field (@fields) {
57 0   0       $metadata->{filters}{$field} ||= [];
58 0           push @{$metadata->{filters}{$field}}, $filter_class;
  0            
59             }
60 0           return $filter_class;
61             }
62              
63             sub __read_attach_filter_params {
64 0     0     my $inv = shift;
65 0 0         my $class = (ref $inv) ? ref($inv) : $inv;
66 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
67 0           my %args = @_;
68              
69 0 0         unless ($args{class}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'class'); }
  0            
70 0           my $filter_class = $args{class};
71 0           delete $args{class};
72 0           conditional_load($filter_class);
73              
74 0 0         my @fields = @{$args{fields} || []};
  0            
75 0           delete $args{fields};
76 0 0         unless (@fields) {
77             # Default to all fields, excluding primary keys
78 0           @fields = grep { !$inv->is_field_primary_key($_) } $class->field_names_including_relations();
  0            
79             }
80              
81 0 0         if (keys %args) { Class::ReluctantORM::Exception::Param::Spurious->croak(param => (join(',',keys %args))); }
  0            
82              
83 0           return ($filter_class, @fields);
84              
85             }
86              
87             sub append_filter {
88 0     0 1   my $self = shift;
89 0           $self->__copy_filter_list_on_write();
90 0           my ($filter_class, @fields) = $self->__read_attach_filter_params(@_);
91 0           my $filters = $self->get('object_filters');
92              
93 0           foreach my $field (@fields) {
94 0   0       $filters->{$field} ||= [];
95 0           push @{$filters->{$field}}, $filter_class;
  0            
96             }
97 0           $self->set('object_filters', $filters);
98              
99 0           return $filter_class;
100             }
101              
102             sub set_filters {
103 0     0 1   my $self = shift;
104              
105 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
106 0           my %args = @_;
107 0   0       $args{fields} ||= [];
108 0 0         unless ($args{classes}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'classes'); }
  0            
109 0 0         unless (ref($args{classes}) eq 'ARRAY') { Class::ReluctantORM::Exception::Param::ExpectedArrayRef->croak(param => 'classes'); }
  0            
110 0 0         unless (ref($args{fields}) eq 'ARRAY') { Class::ReluctantORM::Exception::Param::ExpectedArrayRef->croak(param => 'fields'); }
  0            
111 0           my @classes = @{$args{classes}};
  0            
112 0           my @fields = @{$args{fields}};
  0            
113 0           delete @args{qw(classes fields)};
114 0 0         if (keys %args) { Class::ReluctantORM::Exception::Param::Spurious->croak(param => (join(',',keys %args))); }
  0            
115              
116 0           $self->__copy_filter_list_on_write();
117              
118 0           $self->clear_filters(fields => \@fields);
119 0           foreach my $filter_class (@classes) {
120 0           $self->append_filter(class => $filter_class, fields => \@fields);
121             }
122             }
123              
124             sub clear_filters {
125 0     0 1   my $self = shift;
126 0           my $class = ref $self;
127              
128 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
129 0           my %args = @_;
130              
131             # Default to all fields, including primary key
132 0   0       $args{fields} ||= [$self->field_names_including_relations()];
133 0 0         unless (ref($args{fields}) eq 'ARRAY') { Class::ReluctantORM::Exception::Param::ExpectedArrayRef->croak(param => 'fields'); }
  0            
134 0           my @fields = @{$args{fields}};
  0            
135 0           delete $args{fields};
136 0           foreach my $field (@fields) {
137 0 0         unless (grep {$_ eq $field} $class->field_names_including_relations()) {
  0            
138 0           Class::ReluctantORM::Exception::Param::BadValue->croak(param => 'field', value => $field, error => "'$field' is not a field of $class");
139             }
140             }
141              
142 0 0         if (keys %args) { Class::ReluctantORM::Exception::Param::Spurious->croak(param => (join(',',keys %args))); }
  0            
143              
144 0           $self->__copy_filter_list_on_write();
145 0           my $filters = $self->get('object_filters');
146 0           foreach my $field (@fields) {
147 0           $filters->{$field} = [];
148             }
149 0           $self->set('object_filters', $filters);
150             }
151              
152              
153             sub remove_filter {
154 0     0 1   my $self = shift;
155 0           my $class = ref $self;
156              
157 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
158 0           my %args = @_;
159              
160 0 0         unless ($args{class}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'class'); }
  0            
161              
162             # Default to all fields, including primary key
163 0   0       $args{fields} ||= [$self->field_names_including_relations()];
164 0 0         unless (ref($args{fields}) eq 'ARRAY') { Class::ReluctantORM::Exception::Param::ExpectedArrayRef->croak(param => 'fields'); }
  0            
165              
166 0           my $filter_class = $args{class};
167 0           my @fields = @{$args{fields}};
  0            
168 0           delete @args{qw(class fields)};
169              
170 0           foreach my $field (@fields) {
171 0 0         unless (grep {$_ eq $field} $class->field_names_including_relations()) {
  0            
172 0           Class::ReluctantORM::Exception::Param::BadValue->croak(param => 'field', value => $field, error => "'$field' is not a field of $class");
173             }
174             }
175              
176 0 0         if (keys %args) { Class::ReluctantORM::Exception::Param::Spurious->croak(param => (join(',',keys %args))); }
  0            
177              
178 0           $self->__copy_filter_list_on_write();
179 0           my $filters = $self->get('object_filters');
180 0           foreach my $field (@fields) {
181 0 0         $filters->{$field} = [grep { $_ ne $filter_class } @{ $filters->{$field} || []}];
  0            
  0            
182             }
183 0           $self->set('object_filters', $filters);
184             }
185              
186             sub read_filters_on_field {
187 0     0 1   my $inv = shift;
188 0           my $field = shift;
189 0 0         my $class = ref($inv) ? ref($inv) : $inv;
190              
191 0 0         unless ($field) {
192 0           Class::ReluctantORM::Exception::Param::Missing->croak(param => 'field');
193             }
194 0 0         unless (grep {$_ eq $field} $class->field_names_including_relations()) {
  0            
195 0           Class::ReluctantORM::Exception::Param::BadValue->croak(param => 'field', value => $field, error => "'$field' is not a field of $class");
196             }
197              
198 0           my $class_meta = $class->__metadata();
199 0           my $class_filter_metadata = $class_meta->{filters};
200 0   0       my $object_filter_metadata = (ref($inv) ? $inv->get('object_filters') : {}) || undef;
201              
202 0 0         if (defined $object_filter_metadata) {
203 0 0         return @{$object_filter_metadata->{$field} || []};
  0            
204             } else {
205 0 0         return @{$class_filter_metadata->{$field} || []};
  0            
206             }
207             }
208              
209             sub write_filters_on_field {
210 0     0 1   my $inv = shift;
211              
212             # Note: we currently do not distinguish between write and read filters
213             # (the distinction comes about in the implementation of the filter,
214             # whether they implement apply_read_filter and/or apply_write_filter
215             # Thus the list is the same as for read_filters_on_field, EXCEPT that
216             # the order is reversed.
217              
218 0           return reverse $inv->read_filters_on_field(@_);
219              
220             }
221              
222              
223             sub __apply_field_read_filters {
224 0     0     my $self = shift;
225 0           my $field = shift;
226 0   0       my $raw_value = shift || $self->raw_field_value($field);
227              
228 0           my $value = $raw_value;
229 0           foreach my $filter ($self->read_filters_on_field($field)) {
230 0           $value = $filter->apply_read_filter($value, $self, $field);
231             }
232              
233 0           return $value;
234             }
235              
236             sub __apply_field_write_filters {
237 0     0     my $self = shift;
238 0           my $field = shift;
239 0           my $new_value = shift;
240              
241 0           my $value = $new_value;
242 0           foreach my $filter ($self->write_filters_on_field($field)) {
243 0           $value = $filter->apply_write_filter($value, $self, $field);
244             }
245              
246 0           return $value;
247             }
248              
249             sub __copy_filter_list_on_write {
250 0     0     my $self = shift;
251 0           my $object_filter_list = $self->get('object_filters');
252 0 0         return if $object_filter_list; # Already copied
253              
254             # Start with empty object list
255 0           $object_filter_list = {};
256              
257 0           my $class = ref $self;
258 0   0       my $class_filters = $class->__metadata()->{filters} || {};
259              
260             # Deep copy
261 0           foreach my $field (keys %{$class_filters}) {
  0            
262 0 0         $object_filter_list->{$field} = [ @{$class_filters->{$field} || []} ];
  0            
263             }
264              
265 0           $self->set('object_filters', $object_filter_list);
266             # Self now has its own private copy of the class filter list
267              
268             }
269              
270             # Fetch-deep support
271              
272             # my $filter_info = $class->_extract_deep_filter_args(\%args);
273             sub _extract_deep_filter_args {
274 0     0     my $class = shift;
275 0           my $arg_ref = shift;
276 0           my @filter_options = qw(append_filter remove_filter clear_filters set_filters);
277 0           my $info;
278 0           foreach my $option (@filter_options) {
279 0 0         next unless exists($arg_ref->{$option});
280 0 0         if ($info) {
281 0           Class::ReluctantORM::Exception::Param::MutuallyExclusive->croak(param => "$option, " . $info->{method});
282             }
283 0           $info = { method => $option, args => $arg_ref->{$option} };
284 0           delete $arg_ref->{$option};
285             }
286 0           return $info;
287             }
288              
289              
290             sub _apply_deep_filter_args {
291 0     0     my $class = shift;
292 0           my $info = shift;
293 0           my $objects_ref = shift;
294              
295 0 0         return unless ($info);
296 0           my $method = $info->{method};
297 0           foreach my $obj (@$objects_ref) {
298 0           $obj->$method(%{$info->{args}});
  0            
299             }
300             }
301              
302             1;