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