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