| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package DBIx::Class::FilterColumn; | 
| 2 | 4 |  |  | 4 |  | 8474 | use strict; | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 115 |  | 
| 3 | 4 |  |  | 4 |  | 21 | use warnings; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 112 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 4 |  |  | 4 |  | 20 | use base 'DBIx::Class::Row'; | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 482 |  | 
| 6 | 4 |  |  | 4 |  | 30 | use SQL::Abstract 'is_literal_value'; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 205 |  | 
| 7 | 4 |  |  | 4 |  | 24 | use namespace::clean; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 27 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | sub filter_column { | 
| 10 | 6 |  |  | 6 | 1 | 3863 | my ($self, $col, $attrs) = @_; | 
| 11 |  |  |  |  |  |  |  | 
| 12 | 6 |  |  |  |  | 168 | 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 |  |  | 57 | if defined $colinfo->{_inflate_info} and $self->isa('DBIx::Class::InflateColumn'); | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 5 | 50 |  |  |  | 118 | $self->throw_exception("No such column $col to filter") | 
| 18 |  |  |  |  |  |  | unless $self->has_column($col); | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 5 | 50 |  |  |  | 20 | $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 | 100 |  |  | 26 | unless $attrs->{filter_from_storage} || $attrs->{filter_to_storage}; | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 4 |  |  |  |  | 19 | $colinfo->{_filter_info} = $attrs; | 
| 27 | 4 |  |  |  |  | 9 | my $acc = $colinfo->{accessor}; | 
| 28 | 4 | 50 |  |  |  | 52 | $self->mk_group_accessors(filtered_column => [ (defined $acc ? $acc : $col), $col]); | 
| 29 | 4 |  |  |  |  | 1887 | return 1; | 
| 30 |  |  |  |  |  |  | } | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | sub _column_from_storage { | 
| 33 | 14 |  |  | 14 |  | 72 | my ($self, $col, $value) = @_; | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 14 | 50 |  |  |  | 51 | return $value if is_literal_value($value); | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 14 | 50 |  |  |  | 94 | my $info = $self->result_source->column_info($col) | 
| 38 |  |  |  |  |  |  | or $self->throw_exception("No column info for $col"); | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 14 | 50 |  |  |  | 43 | return $value unless exists $info->{_filter_info}; | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 14 |  |  |  |  | 32 | my $filter = $info->{_filter_info}{filter_from_storage}; | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 14 | 100 |  |  |  | 65 | return defined $filter ? $self->$filter($value) : $value; | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub _column_to_storage { | 
| 48 | 20 |  |  | 20 |  | 46 | my ($self, $col, $value) = @_; | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 20 | 100 |  |  |  | 60 | return $value if is_literal_value($value); | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 16 | 50 |  |  |  | 129 | my $info = $self->result_source->column_info($col) or | 
| 53 |  |  |  |  |  |  | $self->throw_exception("No column info for $col"); | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 16 | 50 |  |  |  | 42 | return $value unless exists $info->{_filter_info}; | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 16 |  |  |  |  | 35 | my $unfilter = $info->{_filter_info}{filter_to_storage}; | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 16 | 100 |  |  |  | 61 | return defined $unfilter ? $self->$unfilter($value) : $value; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | sub get_filtered_column { | 
| 63 | 33 |  |  | 33 | 1 | 401 | my ($self, $col) = @_; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | $self->throw_exception("$col is not a filtered column") | 
| 66 | 33 | 50 |  |  |  | 132 | unless exists $self->result_source->column_info($col)->{_filter_info}; | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | return $self->{_filtered_column}{$col} | 
| 69 | 33 | 100 |  |  |  | 166 | if exists $self->{_filtered_column}{$col}; | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 14 |  |  |  |  | 47 | my $val = $self->get_column($col); | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 14 |  |  |  |  | 59 | return $self->{_filtered_column}{$col} = $self->_column_from_storage( | 
| 74 |  |  |  |  |  |  | $col, $val | 
| 75 |  |  |  |  |  |  | ); | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | sub get_column { | 
| 79 | 65 |  |  | 65 | 1 | 133 | 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 |  |  | 210 | $col, $self->{_filtered_column}{$col} | 
| 87 |  |  |  |  |  |  | ); | 
| 88 |  |  |  |  |  |  |  | 
| 89 | 65 |  |  |  |  | 233 | 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 | 18 | my $self = shift; | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | $self->{_column_data}{$_} = $self->_column_to_storage ( | 
| 97 |  |  |  |  |  |  | $_, $self->{_filtered_column}{$_} | 
| 98 | 11 |  |  |  |  | 59 | ) for grep | 
| 99 | 11 |  |  |  |  | 55 | { ! exists $self->{_column_data}{$_} } | 
| 100 | 11 | 50 |  |  |  | 50 | keys %{$self->{_filtered_column}||{}} | 
| 101 |  |  |  |  |  |  | ; | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 11 |  |  |  |  | 59 | $self->next::method (@_); | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | # and *another* separate codepath, argh! | 
| 107 |  |  |  |  |  |  | sub get_dirty_columns { | 
| 108 | 13 |  |  | 13 | 1 | 27 | 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 |  |  | 19 | for keys %{$self->{_filtered_column}||{}}; | 
|  | 13 |  | 66 |  |  | 116 |  | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 13 |  |  |  |  | 58 | $self->next::method(@_); | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | sub store_column { | 
| 123 | 26 |  |  | 26 | 1 | 401 | my ($self, $col) = (shift, @_); | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | # blow cache | 
| 126 | 26 |  |  |  |  | 62 | delete $self->{_filtered_column}{$col}; | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 26 |  |  |  |  | 66 | $self->next::method(@_); | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub has_column_loaded { | 
| 132 | 17 |  |  | 17 | 1 | 43 | my ($self, $col) = @_; | 
| 133 | 17 | 100 |  |  |  | 90 | 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 | 2012 | 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 |  |  | 144 | if ( | 
| 144 |  |  |  |  |  |  | ! $self->in_storage | 
| 145 |  |  |  |  |  |  | or | 
| 146 |  |  |  |  |  |  | $self->is_column_changed($col) | 
| 147 |  |  |  |  |  |  | ) { | 
| 148 | 10 |  |  |  |  | 367 | $self->make_column_dirty($col); | 
| 149 | 10 |  |  |  |  | 22 | delete $self->{_column_data}{$col}; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | else { | 
| 152 | 12 |  |  |  |  | 45 | $self->set_column($col, $self->_column_to_storage($col, $filtered)); | 
| 153 |  |  |  |  |  |  | }; | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 22 |  |  |  |  | 81 | return $self->{_filtered_column}{$col} = $filtered; | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | sub update { | 
| 159 | 9 |  |  | 9 | 1 | 4164 | my ($self, $data, @rest) = @_; | 
| 160 |  |  |  |  |  |  |  | 
| 161 | 9 |  |  |  |  | 36 | my $colinfos = $self->result_source->columns_info; | 
| 162 |  |  |  |  |  |  |  | 
| 163 | 9 | 100 |  |  |  | 19 | foreach my $col (keys %{$data||{}}) { | 
|  | 9 |  |  |  |  | 55 |  | 
| 164 | 4 | 50 |  |  |  | 19 | if ( exists $colinfos->{$col}{_filter_info} ) { | 
| 165 | 4 |  |  |  |  | 27 | $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 |  |  |  | 19 | $self->get_column($col) unless exists $self->{_column_data}{$col}; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 | 9 |  |  |  |  | 42 | return $self->next::method($data, @rest); | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | sub new { | 
| 178 | 7 |  |  | 7 | 1 | 265 | my ($class, $data, @rest) = @_; | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | my $rsrc = $data->{-result_source} | 
| 181 | 7 | 50 |  |  |  | 27 | or $class->throw_exception('Sourceless rows are not supported with DBIx::Class::FilterColumn'); | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 7 |  |  |  |  | 28 | my $obj = $class->next::method($data, @rest); | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 7 |  |  |  |  | 24 | my $colinfos = $rsrc->columns_info; | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 7 | 50 |  |  |  | 13 | foreach my $col (keys %{$data||{}}) { | 
|  | 7 |  |  |  |  | 22 |  | 
| 188 | 6 | 50 |  |  |  | 20 | if (exists $colinfos->{$col}{_filter_info} ) { | 
| 189 | 6 |  |  |  |  | 23 | $obj->set_filtered_column($col, $data->{$col}); | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 7 |  |  |  |  | 35 | return $obj; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | 1; | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | __END__ | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head1 NAME | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | DBIx::Class::FilterColumn - Automatically convert column data | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | In your Schema or DB class add "FilterColumn" to the top of the component list. | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | __PACKAGE__->load_components(qw( FilterColumn ... )); | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | Set up filters for the columns you want to convert. | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | __PACKAGE__->filter_column( money => { | 
| 213 |  |  |  |  |  |  | filter_to_storage => 'to_pennies', | 
| 214 |  |  |  |  |  |  | filter_from_storage => 'from_pennies', | 
| 215 |  |  |  |  |  |  | }); | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub to_pennies   { $_[1] * 100 } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | sub from_pennies { $_[1] / 100 } | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | 1; | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | This component is meant to be a more powerful, but less DWIM-y, | 
| 227 |  |  |  |  |  |  | L<DBIx::Class::InflateColumn>.  One of the major issues with said component is | 
| 228 |  |  |  |  |  |  | that it B<only> works with references.  Generally speaking anything that can | 
| 229 |  |  |  |  |  |  | be done with L<DBIx::Class::InflateColumn> can be done with this component. | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | =head1 METHODS | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =head2 filter_column | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | __PACKAGE__->filter_column( colname => { | 
| 236 |  |  |  |  |  |  | filter_from_storage => 'method'|\&coderef, | 
| 237 |  |  |  |  |  |  | filter_to_storage   => 'method'|\&coderef, | 
| 238 |  |  |  |  |  |  | }) | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | This is the method that you need to call to set up a filtered column. It takes | 
| 241 |  |  |  |  |  |  | exactly two arguments; the first being the column name the second being a hash | 
| 242 |  |  |  |  |  |  | reference with C<filter_from_storage> and C<filter_to_storage> set to either | 
| 243 |  |  |  |  |  |  | a method name or a code reference. In either case the filter is invoked as: | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | $result->$filter_specification ($value_to_filter) | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | with C<$filter_specification> being chosen depending on whether the | 
| 248 |  |  |  |  |  |  | C<$value_to_filter> is being retrieved from or written to permanent | 
| 249 |  |  |  |  |  |  | storage. | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | If a specific directional filter is not specified, the original value will be | 
| 252 |  |  |  |  |  |  | passed to/from storage unfiltered. | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | =head2 get_filtered_column | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | $obj->get_filtered_column('colname') | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | Returns the filtered value of the column | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =head2 set_filtered_column | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | $obj->set_filtered_column(colname => 'new_value') | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | Sets the filtered value of the column | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | =head1 EXAMPLE OF USE | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | Some databases have restrictions on values that can be passed to | 
| 269 |  |  |  |  |  |  | boolean columns, and problems can be caused by passing value that | 
| 270 |  |  |  |  |  |  | perl considers to be false (such as C<undef>). | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | One solution to this is to ensure that the boolean values are set | 
| 273 |  |  |  |  |  |  | to something that the database can handle - such as numeric zero | 
| 274 |  |  |  |  |  |  | and one, using code like this:- | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | __PACKAGE__->filter_column( | 
| 277 |  |  |  |  |  |  | my_boolean_column => { | 
| 278 |  |  |  |  |  |  | filter_to_storage   => sub { $_[1] ? 1 : 0 }, | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  | ); | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | In this case the C<filter_from_storage> is not required, as just | 
| 283 |  |  |  |  |  |  | passing the database value through to perl does the right thing. | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | =head1 FURTHER QUESTIONS? | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>. | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE> | 
| 292 |  |  |  |  |  |  | by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can | 
| 293 |  |  |  |  |  |  | redistribute it and/or modify it under the same terms as the | 
| 294 |  |  |  |  |  |  | L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>. |