1 package DBIx::Class::FilterColumn;
5 use base 'DBIx::Class::Row';
6 use SQL::Abstract 'is_literal_value';
10 my ($self, $col, $attrs) = @_;
12 my $colinfo = $self->result_source_instance->column_info($col);
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');
17 $self->throw_exception("No such column $col to filter")
18 unless $self->result_source_instance->has_column($col);
20 $self->throw_exception('filter_column expects a hashref of filter specifications')
21 unless ref $attrs eq 'HASH';
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};
26 $colinfo->{_filter_info} = $attrs;
27 my $acc = $colinfo->{accessor};
28 $self->mk_group_accessors(filtered_column => [ (defined $acc ? $acc : $col), $col]);
32 sub _column_from_storage {
33 my ($self, $col, $value) = @_;
35 return $value if is_literal_value($value);
37 my $info = $self->result_source->column_info($col)
38 or $self->throw_exception("No column info for $col");
40 return $value unless exists $info->{_filter_info};
42 my $filter = $info->{_filter_info}{filter_from_storage};
44 return defined $filter ? $self->$filter($value) : $value;
47 sub _column_to_storage {
48 my ($self, $col, $value) = @_;
50 return $value if is_literal_value($value);
52 my $info = $self->result_source->column_info($col) or
53 $self->throw_exception("No column info for $col");
55 return $value unless exists $info->{_filter_info};
57 my $unfilter = $info->{_filter_info}{filter_to_storage};
59 return defined $unfilter ? $self->$unfilter($value) : $value;
62 sub get_filtered_column {
63 my ($self, $col) = @_;
65 $self->throw_exception("$col is not a filtered column")
66 unless exists $self->result_source->column_info($col)->{_filter_info};
68 return $self->{_filtered_column}{$col}
69 if exists $self->{_filtered_column}{$col};
71 my $val = $self->get_column($col);
73 return $self->{_filtered_column}{$col} = $self->_column_from_storage(
79 my ($self, $col) = @_;
81 ! exists $self->{_column_data}{$col}
83 exists $self->{_filtered_column}{$col}
85 $self->{_column_data}{$col} = $self->_column_to_storage (
86 $col, $self->{_filtered_column}{$col}
89 return $self->next::method ($col);
92 # sadly a separate codepath in Row.pm ( used by insert() )
96 $self->{_column_data}{$_} = $self->_column_to_storage (
97 $_, $self->{_filtered_column}{$_}
99 { ! exists $self->{_column_data}{$_} }
100 keys %{$self->{_filtered_column}||{}}
103 $self->next::method (@_);
106 # and *another* separate codepath, argh!
107 sub get_dirty_columns {
110 $self->{_dirty_columns}{$_}
112 ! exists $self->{_column_data}{$_}
114 $self->{_column_data}{$_} = $self->_column_to_storage (
115 $_, $self->{_filtered_column}{$_}
117 for keys %{$self->{_filtered_column}||{}};
119 $self->next::method(@_);
123 my ($self, $col) = (shift, @_);
126 delete $self->{_filtered_column}{$col};
128 $self->next::method(@_);
131 sub has_column_loaded {
132 my ($self, $col) = @_;
133 return 1 if exists $self->{_filtered_column}{$col};
134 return $self->next::method($col);
137 sub set_filtered_column {
138 my ($self, $col, $filtered) = @_;
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
146 $self->is_column_changed($col)
148 $self->make_column_dirty($col);
149 delete $self->{_column_data}{$col};
152 $self->set_column($col, $self->_column_to_storage($col, $filtered));
155 return $self->{_filtered_column}{$col} = $filtered;
159 my ($self, $data, @rest) = @_;
161 my $colinfos = $self->result_source->columns_info;
163 foreach my $col (keys %{$data||{}}) {
164 if ( exists $colinfos->{$col}{_filter_info} ) {
165 $self->set_filtered_column($col, delete $data->{$col});
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 $self->get_column($col) unless exists $self->{_column_data}{$col};
174 return $self->next::method($data, @rest);
178 my ($class, $data, @rest) = @_;
180 my $rsrc = $data->{-result_source}
181 or $class->throw_exception('Sourceless rows are not supported with DBIx::Class::FilterColumn');
183 my $obj = $class->next::method($data, @rest);
185 my $colinfos = $rsrc->columns_info;
187 foreach my $col (keys %{$data||{}}) {
188 if (exists $colinfos->{$col}{_filter_info} ) {
189 $obj->set_filtered_column($col, $data->{$col});
202 DBIx::Class::FilterColumn - Automatically convert column data
206 In your Schema or DB class add "FilterColumn" to the top of the component list.
208 __PACKAGE__->load_components(qw( FilterColumn ... ));
210 Set up filters for the columns you want to convert.
212 __PACKAGE__->filter_column( money => {
213 filter_to_storage => 'to_pennies',
214 filter_from_storage => 'from_pennies',
217 sub to_pennies { $_[1] * 100 }
219 sub from_pennies { $_[1] / 100 }
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.
235 __PACKAGE__->filter_column( colname => {
236 filter_from_storage => 'method'|\&coderef,
237 filter_to_storage => 'method'|\&coderef,
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:
245 $result->$filter_specification ($value_to_filter)
247 with C<$filter_specification> being chosen depending on whether the
248 C<$value_to_filter> is being retrieved from or written to permanent
251 If a specific directional filter is not specified, the original value will be
252 passed to/from storage unfiltered.
254 =head2 get_filtered_column
256 $obj->get_filtered_column('colname')
258 Returns the filtered value of the column
260 =head2 set_filtered_column
262 $obj->set_filtered_column(colname => 'new_value')
264 Sets the filtered value of the column
266 =head1 EXAMPLE OF USE
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>).
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:-
276 __PACKAGE__->filter_column(
277 my_boolean_column => {
278 filter_to_storage => sub { $_[1] ? 1 : 0 },
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.
285 =head1 FURTHER QUESTIONS?
287 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
289 =head1 COPYRIGHT AND LICENSE
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>.