Restore ability to handle underdefined root (t/prefetch/incomplete.t)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / FilterColumn.pm
CommitLineData
51bec050 1package DBIx::Class::FilterColumn;
51bec050 2use strict;
3use warnings;
4
5use base qw/DBIx::Class::Row/;
6
7sub filter_column {
8 my ($self, $col, $attrs) = @_;
9
52416317 10 my $colinfo = $self->column_info($col);
11
eec182b6 12 $self->throw_exception('FilterColumn does not work with InflateColumn')
c227b295 13 if $self->isa('DBIx::Class::InflateColumn') &&
52416317 14 defined $colinfo->{_inflate_info};
c227b295 15
51bec050 16 $self->throw_exception("No such column $col to filter")
17 unless $self->has_column($col);
18
eec182b6 19 $self->throw_exception('filter_column expects a hashref of filter specifications')
51bec050 20 unless ref $attrs eq 'HASH';
21
eec182b6 22 $self->throw_exception('An invocation of filter_column() must specify either a filter_from_storage or filter_to_storage')
23 unless $attrs->{filter_from_storage} || $attrs->{filter_to_storage};
24
52416317 25 $colinfo->{_filter_info} = $attrs;
26 my $acc = $colinfo->{accessor};
d7d38bef 27 $self->mk_group_accessors(filtered_column => [ (defined $acc ? $acc : $col), $col]);
51bec050 28 return 1;
29}
30
4c0c3038 31sub _column_from_storage {
9b2c0de6 32 my ($self, $col, $value) = @_;
51bec050 33
34 return $value unless defined $value;
35
36 my $info = $self->column_info($col)
37 or $self->throw_exception("No column info for $col");
38
39 return $value unless exists $info->{_filter_info};
40
d7d38bef 41 my $filter = $info->{_filter_info}{filter_from_storage};
51bec050 42
eec182b6 43 return defined $filter ? $self->$filter($value) : $value;
51bec050 44}
45
4c0c3038 46sub _column_to_storage {
9b2c0de6 47 my ($self, $col, $value) = @_;
51bec050 48
49 my $info = $self->column_info($col) or
50 $self->throw_exception("No column info for $col");
51
52 return $value unless exists $info->{_filter_info};
53
d7d38bef 54 my $unfilter = $info->{_filter_info}{filter_to_storage};
eec182b6 55
56 return defined $unfilter ? $self->$unfilter($value) : $value;
51bec050 57}
58
d7d38bef 59sub get_filtered_column {
51bec050 60 my ($self, $col) = @_;
61
62 $self->throw_exception("$col is not a filtered column")
63 unless exists $self->column_info($col)->{_filter_info};
64
65 return $self->{_filtered_column}{$col}
66 if exists $self->{_filtered_column}{$col};
67
68 my $val = $self->get_column($col);
69
9b2c0de6 70 return $self->{_filtered_column}{$col} = $self->_column_from_storage($col, $val);
51bec050 71}
72
491c8ff9 73sub get_column {
74 my ($self, $col) = @_;
75 if (exists $self->{_filtered_column}{$col}) {
76 return $self->{_column_data}{$col} ||= $self->_column_to_storage ($col, $self->{_filtered_column}{$col});
77 }
78
79 return $self->next::method ($col);
80}
81
82# sadly a separate codepath in Row.pm ( used by insert() )
83sub get_columns {
84 my $self = shift;
85
86 foreach my $col (keys %{$self->{_filtered_column}||{}}) {
87 $self->{_column_data}{$col} ||= $self->_column_to_storage ($col, $self->{_filtered_column}{$col})
88 if exists $self->{_filtered_column}{$col};
89 }
90
91 $self->next::method (@_);
92}
93
94sub store_column {
85439e0c 95 my ($self, $col) = (shift, @_);
96
97 # blow cache
98 delete $self->{_filtered_column}{$col};
99
100 $self->next::method(@_);
101}
102
d7d38bef 103sub set_filtered_column {
51bec050 104 my ($self, $col, $filtered) = @_;
105
cde96798 106 # do not blow up the cache via set_column unless necessary
107 # (filtering may be expensive!)
108 if (exists $self->{_filtered_column}{$col}) {
109 return $filtered
110 if ($self->_eq_column_values ($col, $filtered, $self->{_filtered_column}{$col} ) );
51bec050 111
cde96798 112 $self->make_column_dirty ($col); # so the comparison won't run again
113 }
114
115 $self->set_column($col, $self->_column_to_storage($col, $filtered));
51bec050 116
491c8ff9 117 return $self->{_filtered_column}{$col} = $filtered;
51bec050 118}
119
7b461f8a 120sub update {
121 my ($self, $attrs, @rest) = @_;
491c8ff9 122
7b461f8a 123 foreach my $key (keys %{$attrs||{}}) {
491c8ff9 124 if (
125 $self->has_column($key)
126 &&
127 exists $self->column_info($key)->{_filter_info}
128 ) {
129 $self->set_filtered_column($key, delete $attrs->{$key});
7c6fa77f 130
131 # FIXME update() reaches directly into the object-hash
132 # and we may *not* have a filtered value there - thus
133 # the void-ctx filter-trigger
134 $self->get_column($key) unless exists $self->{_column_data}{$key};
7b461f8a 135 }
136 }
491c8ff9 137
7b461f8a 138 return $self->next::method($attrs, @rest);
139}
140
7b461f8a 141sub new {
142 my ($class, $attrs, @rest) = @_;
9b2c0de6 143 my $source = $attrs->{-result_source}
d9ea6d6d 144 or $class->throw_exception('Sourceless rows are not supported with DBIx::Class::FilterColumn');
145
9b2c0de6 146 my $obj = $class->next::method($attrs, @rest);
7b461f8a 147 foreach my $key (keys %{$attrs||{}}) {
9b2c0de6 148 if ($obj->has_column($key) &&
149 exists $obj->column_info($key)->{_filter_info} ) {
491c8ff9 150 $obj->set_filtered_column($key, $attrs->{$key});
7b461f8a 151 }
152 }
491c8ff9 153
7b461f8a 154 return $obj;
155}
156
51bec050 1571;
22d9e05a 158
9b2c0de6 159=head1 NAME
22d9e05a 160
9b2c0de6 161DBIx::Class::FilterColumn - Automatically convert column data
162
163=head1 SYNOPSIS
164
f10a2e9a 165In your Schema or DB class add "FilterColumn" to the top of the component list.
166
167 __PACKAGE__->load_components(qw( FilterColumn ... ));
168
169Set up filters for the columns you want to convert.
170
9b2c0de6 171 __PACKAGE__->filter_column( money => {
172 filter_to_storage => 'to_pennies',
173 filter_from_storage => 'from_pennies',
174 });
22d9e05a 175
176 sub to_pennies { $_[1] * 100 }
9b2c0de6 177
22d9e05a 178 sub from_pennies { $_[1] / 100 }
179
180 1;
181
f10a2e9a 182
9b2c0de6 183=head1 DESCRIPTION
22d9e05a 184
9b2c0de6 185This component is meant to be a more powerful, but less DWIM-y,
186L<DBIx::Class::InflateColumn>. One of the major issues with said component is
187that it B<only> works with references. Generally speaking anything that can
188be done with L<DBIx::Class::InflateColumn> can be done with this component.
22d9e05a 189
9b2c0de6 190=head1 METHODS
22d9e05a 191
9b2c0de6 192=head2 filter_column
22d9e05a 193
9b2c0de6 194 __PACKAGE__->filter_column( colname => {
eec182b6 195 filter_from_storage => 'method'|\&coderef,
196 filter_to_storage => 'method'|\&coderef,
9b2c0de6 197 })
22d9e05a 198
eec182b6 199This is the method that you need to call to set up a filtered column. It takes
200exactly two arguments; the first being the column name the second being a hash
201reference with C<filter_from_storage> and C<filter_to_storage> set to either
202a method name or a code reference. In either case the filter is invoked as:
203
204 $row_obj->$filter_specification ($value_to_filter)
205
206with C<$filter_specification> being chosen depending on whether the
207C<$value_to_filter> is being retrieved from or written to permanent
208storage.
209
210If a specific directional filter is not specified, the original value will be
211passed to/from storage unfiltered.
22d9e05a 212
9b2c0de6 213=head2 get_filtered_column
22d9e05a 214
9b2c0de6 215 $obj->get_filtered_column('colname')
216
217Returns the filtered value of the column
218
219=head2 set_filtered_column
220
221 $obj->set_filtered_column(colname => 'new_value')
222
223Sets the filtered value of the column
eec182b6 224
225=head1 EXAMPLE OF USE
226
227Some databases have restrictions on values that can be passed to
228boolean columns, and problems can be caused by passing value that
229perl considers to be false (such as C<undef>).
230
231One solution to this is to ensure that the boolean values are set
232to something that the database can handle - such as numeric zero
233and one, using code like this:-
234
235 __PACKAGE__->filter_column(
236 my_boolean_column => {
237 filter_to_storage => sub { $_[1] ? 1 : 0 },
238 }
239 );
240
241In this case the C<filter_from_storage> is not required, as just
242passing the database value through to perl does the right thing.