File Coverage

blib/lib/DBIx/Class/FilterColumn.pm
Criterion Covered Total %
statement 82 82 100.0
branch 32 46 69.5
condition 15 18 83.3
subroutine 17 17 100.0
pod 10 10 100.0
total 156 173 90.1


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