--- /dev/null
+package DBIx::Class::FilterColumn;
+use strict;
+use warnings;
+
+use base qw/DBIx::Class::Row/;
+
+sub filter_column {
+ my ($self, $col, $attrs) = @_;
+
+ $self->throw_exception("No such column $col to filter")
+ unless $self->has_column($col);
+
+ $self->throw_exception("filter_column needs attr hashref")
+ unless ref $attrs eq 'HASH';
+
+ $self->column_info($col)->{_filter_info} = $attrs;
+ my $acc = $self->column_info($col)->{accessor};
+ $self->mk_group_accessors(filtered_column => [ (defined $acc ? $acc : $col), $col]);
+ return 1;
+}
+
+sub _column_from_storage {
+ my ($self, $col, $value) = @_;
+
+ return $value unless defined $value;
+
+ my $info = $self->column_info($col)
+ or $self->throw_exception("No column info for $col");
+
+ return $value unless exists $info->{_filter_info};
+
+ my $filter = $info->{_filter_info}{filter_from_storage};
+ $self->throw_exception("No inflator for $col") unless defined $filter;
+
+ return $self->$filter($value);
+}
+
+sub _column_to_storage {
+ my ($self, $col, $value) = @_;
+
+ my $info = $self->column_info($col) or
+ $self->throw_exception("No column info for $col");
+
+ return $value unless exists $info->{_filter_info};
+
+ my $unfilter = $info->{_filter_info}{filter_to_storage};
+ $self->throw_exception("No unfilter for $col") unless defined $unfilter;
+ return $self->$unfilter($value);
+}
+
+sub get_filtered_column {
+ my ($self, $col) = @_;
+
+ $self->throw_exception("$col is not a filtered column")
+ unless exists $self->column_info($col)->{_filter_info};
+
+ return $self->{_filtered_column}{$col}
+ if exists $self->{_filtered_column}{$col};
+
+ my $val = $self->get_column($col);
+
+ return $self->{_filtered_column}{$col} = $self->_column_from_storage($col, $val);
+}
+
+sub set_filtered_column {
+ my ($self, $col, $filtered) = @_;
+
+ $self->set_column($col, $self->_column_to_storage($col, $filtered));
+
+ delete $self->{_filtered_column}{$col};
+
+ return $filtered;
+}
+
+sub update {
+ my ($self, $attrs, @rest) = @_;
+ foreach my $key (keys %{$attrs||{}}) {
+ if ($self->has_column($key) &&
+ exists $self->column_info($key)->{_filter_info}) {
+ my $val = delete $attrs->{$key};
+ $self->set_filtered_column($key, $val);
+ $attrs->{$key} = $self->_column_to_storage($key, $val)
+ }
+ }
+ return $self->next::method($attrs, @rest);
+}
+
+sub new {
+ my ($class, $attrs, @rest) = @_;
+ my $source = $attrs->{-result_source}
+ or $class->throw_exception('Sourceless rows are not supported with DBIx::Class::FilterColumn');
+
+ my $obj = $class->next::method($attrs, @rest);
+ foreach my $key (keys %{$attrs||{}}) {
+ if ($obj->has_column($key) &&
+ exists $obj->column_info($key)->{_filter_info} ) {
+ my $val = delete $attrs->{$key};
+ $obj->set_filtered_column($key, $val);
+ }
+ }
+ return $obj;
+}
+
+1;
+
+=head1 NAME
+
+DBIx::Class::FilterColumn - Automatically convert column data
+
+=head1 SYNOPSIS
+
+ # In your result classes
+ __PACKAGE__->filter_column( money => {
+ filter_to_storage => 'to_pennies',
+ filter_from_storage => 'from_pennies',
+ });
+
+ sub to_pennies { $_[1] * 100 }
+
+ sub from_pennies { $_[1] / 100 }
+
+ 1;
+
+=head1 DESCRIPTION
+
+This component is meant to be a more powerful, but less DWIM-y,
+L<DBIx::Class::InflateColumn>. One of the major issues with said component is
+that it B<only> works with references. Generally speaking anything that can
+be done with L<DBIx::Class::InflateColumn> can be done with this component.
+
+=head1 METHODS
+
+=head2 filter_column
+
+ __PACKAGE__->filter_column( colname => {
+ filter_from_storage => 'method',
+ filter_to_storage => 'method',
+ })
+
+This is the method that you need to call to set up a filtered column. It takes
+exactly two arguments; the first being the column name the second being a
+C<HashRef> with C<filter_from_storage> and C<filter_to_storage> having
+something that can be called as a method. The method will be called with
+the value of the column as the first non-C<$self> argument.
+
+=head2 get_filtered_column
+
+ $obj->get_filtered_column('colname')
+
+Returns the filtered value of the column
+
+=head2 set_filtered_column
+
+ $obj->set_filtered_column(colname => 'new_value')
+
+Sets the filtered value of the column
+
+=head2 update
+
+Just overridden to filter changed columns through this component
+
+=head2 new
+
+Just overridden to filter columns through this component
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+
+my $schema = DBICTest->init_schema();
+DBICTest::Schema::Artist->load_components('FilterColumn');
+DBICTest::Schema::Artist->filter_column(rank => {
+ filter_from_storage => sub { $_[1] * 2 },
+ filter_to_storage => sub { $_[1] / 2 },
+});
+Class::C3->reinitialize();
+
+my $artist = $schema->resultset('Artist')->create( { rank => 20 } );
+
+# this should be using the cursor directly, no inflation/processing of any sort
+my ($raw_db_rank) = $schema->resultset('Artist')
+ ->search ($artist->ident_condition)
+ ->get_column('rank')
+ ->_resultset
+ ->cursor
+ ->next;
+
+is ($raw_db_rank, 10, 'INSERT: correctly unfiltered on insertion');
+
+for my $reloaded (0, 1) {
+ my $test = $reloaded ? 'reloaded' : 'stored';
+ $artist->discard_changes if $reloaded;
+
+ is( $artist->rank , 20, "got $test filtered rank" );
+}
+
+$artist->update;
+$artist->discard_changes;
+is( $artist->rank , 20, "got filtered rank" );
+
+$artist->update ({ rank => 40 });
+($raw_db_rank) = $schema->resultset('Artist')
+ ->search ($artist->ident_condition)
+ ->get_column('rank')
+ ->_resultset
+ ->cursor
+ ->next;
+is ($raw_db_rank, 20, 'UPDATE: correctly unflitered on update');
+
+$artist->discard_changes;
+$artist->rank(40);
+ok( !$artist->is_column_changed('rank'), 'column is not dirty after setting the same value' );
+
+MC: {
+ my $cd = $schema->resultset('CD')->create({
+ artist => { rank => 20 },
+ title => 'fun time city!',
+ year => 'forevertime',
+ });
+ ($raw_db_rank) = $schema->resultset('Artist')
+ ->search ($cd->artist->ident_condition)
+ ->get_column('rank')
+ ->_resultset
+ ->cursor
+ ->next;
+
+ is $raw_db_rank, 10, 'artist rank gets correctly unfiltered w/ MC';
+ is $cd->artist->rank, 20, 'artist rank gets correctly filtered w/ MC';
+}
+
+done_testing;