File Coverage

blib/lib/DBIx/Class/FilterColumn.pm
Criterion Covered Total %
statement 83 83 100.0
branch 35 52 67.3
condition 14 18 77.7
subroutine 17 17 100.0
pod 10 10 100.0
total 159 180 88.3


line stmt bran cond sub pod time code
1             package DBIx::Class::FilterColumn;
2 4     4   6737 use strict;
  4         26  
  4         125  
3 4     4   16 use warnings;
  4         5  
  4         122  
4              
5 4     4   18 use base 'DBIx::Class::Row';
  4         7  
  4         410  
6 4     4   24 use SQL::Abstract 'is_literal_value';
  4         8  
  4         245  
7 4     4   20 use namespace::clean;
  4         7  
  4         36  
8              
9             sub filter_column {
10 6     6 1 2567 my ($self, $col, $attrs) = @_;
11              
12 6         139 my $colinfo = $self->column_info($col);
13              
14             $self->throw_exception("FilterColumn can not be used on a column with a declared InflateColumn inflator")
15 6 100 66     43 if defined $colinfo->{_inflate_info} and $self->isa('DBIx::Class::InflateColumn');
16              
17 5 50       90 $self->throw_exception("No such column $col to filter")
18             unless $self->has_column($col);
19              
20 5 50       14 $self->throw_exception('filter_column expects a hashref of filter specifications')
21             unless ref $attrs eq 'HASH';
22              
23             $self->throw_exception('An invocation of filter_column() must specify either a filter_from_storage or filter_to_storage')
24 5 100 66     20 unless $attrs->{filter_from_storage} || $attrs->{filter_to_storage};
25              
26 4         7 $colinfo->{_filter_info} = $attrs;
27 4         16 my $acc = $colinfo->{accessor};
28 4 50       43 $self->mk_group_accessors(filtered_column => [ (defined $acc ? $acc : $col), $col]);
29 4         1476 return 1;
30             }
31              
32             sub _column_from_storage {
33 14     14   23 my ($self, $col, $value) = @_;
34              
35 14 50       36 return $value if is_literal_value($value);
36              
37 14 50       79 my $info = $self->result_source->column_info($col)
38             or $self->throw_exception("No column info for $col");
39              
40 14 50       31 return $value unless exists $info->{_filter_info};
41              
42 14         22 my $filter = $info->{_filter_info}{filter_from_storage};
43              
44 14 100       45 return defined $filter ? $self->$filter($value) : $value;
45             }
46              
47             sub _column_to_storage {
48 20     20   29 my ($self, $col, $value) = @_;
49              
50 20 100       68 return $value if is_literal_value($value);
51              
52 16 50       103 my $info = $self->result_source->column_info($col) or
53             $self->throw_exception("No column info for $col");
54              
55 16 50       35 return $value unless exists $info->{_filter_info};
56              
57 16         24 my $unfilter = $info->{_filter_info}{filter_to_storage};
58              
59 16 100       55 return defined $unfilter ? $self->$unfilter($value) : $value;
60             }
61              
62             sub get_filtered_column {
63 33     33 1 269 my ($self, $col) = @_;
64              
65             $self->throw_exception("$col is not a filtered column")
66 33 50       76 unless exists $self->result_source->column_info($col)->{_filter_info};
67              
68             return $self->{_filtered_column}{$col}
69 33 100       109 if exists $self->{_filtered_column}{$col};
70              
71 14         29 my $val = $self->get_column($col);
72              
73 14         42 return $self->{_filtered_column}{$col} = $self->_column_from_storage(
74             $col, $val
75             );
76             }
77              
78             sub get_column {
79 65     65 1 72 my ($self, $col) = @_;
80              
81             ! exists $self->{_column_data}{$col}
82             and
83             exists $self->{_filtered_column}{$col}
84             and
85             $self->{_column_data}{$col} = $self->_column_to_storage (
86 65 50 66     144 $col, $self->{_filtered_column}{$col}
87             );
88              
89 65         168 return $self->next::method ($col);
90             }
91              
92             # sadly a separate codepath in Row.pm ( used by insert() )
93             sub get_columns {
94 11     11 1 11 my $self = shift;
95              
96             $self->{_column_data}{$_} = $self->_column_to_storage (
97             $_, $self->{_filtered_column}{$_}
98 11         12 ) for grep
99 11         39 { ! exists $self->{_column_data}{$_} }
100 11 50       37 keys %{$self->{_filtered_column}||{}}
101             ;
102              
103 11         39 $self->next::method (@_);
104             }
105              
106             # and *another* separate codepath, argh!
107             sub get_dirty_columns {
108 13     13 1 15 my $self = shift;
109              
110             $self->{_dirty_columns}{$_}
111             and
112             ! exists $self->{_column_data}{$_}
113             and
114             $self->{_column_data}{$_} = $self->_column_to_storage (
115             $_, $self->{_filtered_column}{$_}
116             )
117 13 50 100     10 for keys %{$self->{_filtered_column}||{}};
  13   66     95  
118              
119 13         32 $self->next::method(@_);
120             }
121              
122             sub store_column {
123 26     26 1 252 my ($self, $col) = (shift, @_);
124              
125             # blow cache
126 26         34 delete $self->{_filtered_column}{$col};
127              
128 26         45 $self->next::method(@_);
129             }
130              
131             sub has_column_loaded {
132 17     17 1 28 my ($self, $col) = @_;
133 17 100       55 return 1 if exists $self->{_filtered_column}{$col};
134 3         12 return $self->next::method($col);
135             }
136              
137             sub set_filtered_column {
138 22     22 1 1250 my ($self, $col, $filtered) = @_;
139              
140             # unlike IC, FC does not need to deal with the 'filter' abomination
141             # thus we can short-curcuit filtering entirely and never call set_column
142             # in case this is already a dirty change OR the row never touched storage
143 22 100 100     136 if (
144             ! $self->in_storage
145             or
146             $self->is_column_changed($col)
147             ) {
148 10         217 $self->make_column_dirty($col);
149 10         13 delete $self->{_column_data}{$col};
150             }
151             else {
152 12         28 $self->set_column($col, $self->_column_to_storage($col, $filtered));
153             };
154              
155 22         50 return $self->{_filtered_column}{$col} = $filtered;
156             }
157              
158             sub update {
159 9     9 1 2605 my ($self, $data, @rest) = @_;
160              
161 9         23 my $colinfos = $self->result_source->columns_info;
162              
163 9 100       9 foreach my $col (keys %{$data||{}}) {
  9         44  
164 4 50       11 if ( exists $colinfos->{$col}{_filter_info} ) {
165 4         13 $self->set_filtered_column($col, delete $data->{$col});
166              
167             # FIXME update() reaches directly into the object-hash
168             # and we may *not* have a filtered value there - thus
169             # the void-ctx filter-trigger
170 4 50       10 $self->get_column($col) unless exists $self->{_column_data}{$col};
171             }
172             }
173              
174 9         27 return $self->next::method($data, @rest);
175             }
176              
177             sub new {
178 7     7 1 144 my ($class, $data, @rest) = @_;
179              
180             my $rsrc = $data->{-result_source}
181 7 50       25 or $class->throw_exception('Sourceless rows are not supported with DBIx::Class::FilterColumn');
182              
183 7         20 my $obj = $class->next::method($data, @rest);
184              
185 7         20 my $colinfos = $rsrc->columns_info;
186              
187 7 50       7 foreach my $col (keys %{$data||{}}) {
  7         21  
188 6 50       13 if (exists $colinfos->{$col}{_filter_info} ) {
189 6         14 $obj->set_filtered_column($col, $data->{$col});
190             }
191             }
192              
193 7         18 return $obj;
194             }
195              
196             1;
197              
198             __END__