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