Inflate/Deflate on all refs but scalars, with tests and all!
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / InflateColumn.pm
CommitLineData
0e5c2582 1package DBIx::Class::InflateColumn;
2
3use strict;
4use warnings;
ba026511 5use Scalar::Util qw/blessed/;
aa562407 6
75a23b3e 7use base qw/DBIx::Class::Row/;
0e5c2582 8
75d07914 9=head1 NAME
bcae85db 10
e81a6241 11DBIx::Class::InflateColumn - Automatically create references from column data
bcae85db 12
13=head1 SYNOPSIS
14
15 # In your table classes
16 __PACKAGE__->inflate_column('column_name', {
17 inflate => sub { ... },
18 deflate => sub { ... },
19 });
20
21=head1 DESCRIPTION
22
e81a6241 23This component translates column data into references, i.e. "inflating"
24the column data. It also "deflates" references into an appropriate format
bcae85db 25for the database.
26
27It can be used, for example, to automatically convert to and from
75d07914 28L<DateTime> objects for your date and time fields.
bcae85db 29
e81a6241 30It will accept arrayrefs, hashrefs and blessed references (objects),
31but not scalarrefs. Scalar references are passed through to the
32database to deal with, to allow such settings as C< \'year + 1'> and
33C< \'DEFAULT' > to work.
34
bcae85db 35=head1 METHODS
36
37=head2 inflate_column
38
75d07914 39Instruct L<DBIx::Class> to inflate the given column.
bcae85db 40
41In addition to the column name, you must provide C<inflate> and
42C<deflate> methods. The C<inflate> method is called when you access
43the field, while the C<deflate> method is called when the field needs
44to used by the database.
45
46For example, if you have a table C<events> with a timestamp field
47named C<insert_time>, you could inflate the column in the
48corresponding table class using something like:
49
50 __PACKAGE__->inflate_column('insert_time', {
51 inflate => sub { DateTime::Format::Pg->parse_datetime(shift); },
52 deflate => sub { DateTime::Format::Pg->format_datetime(shift); },
53 });
54
55(Replace L<DateTime::Format::Pg> with the appropriate module for your
56database, or consider L<DateTime::Format::DBI>.)
57
06fc5fc9 58The coderefs you set for inflate and deflate are called with two parameters,
59the first is the value of the column to be inflated/deflated, the second is the
60row object itself. Thus you can call C<< ->result_source->schema->storage->dbh >> on
61it, to feed to L<DateTime::Format::DBI>.
62
bcae85db 63In this example, calls to an event's C<insert_time> accessor return a
64L<DateTime> object. This L<DateTime> object is later "deflated" when
65used in the database layer.
66
67=cut
68
0e5c2582 69sub inflate_column {
70 my ($self, $col, $attrs) = @_;
bc0c9800 71 $self->throw_exception("No such column $col to inflate")
72 unless $self->has_column($col);
73 $self->throw_exception("inflate_column needs attr hashref")
74 unless ref $attrs eq 'HASH';
103647d5 75 $self->column_info($col)->{_inflate_info} = $attrs;
0e5c2582 76 $self->mk_group_accessors('inflated_column' => $col);
77 return 1;
78}
79
4a07648a 80sub _inflated_column {
0e5c2582 81 my ($self, $col, $value) = @_;
9f300b1b 82 return $value unless defined $value; # NULL is NULL is NULL
bc0c9800 83 my $info = $self->column_info($col)
84 or $self->throw_exception("No column info for $col");
103647d5 85 return $value unless exists $info->{_inflate_info};
86 my $inflate = $info->{_inflate_info}{inflate};
701da8c4 87 $self->throw_exception("No inflator for $col") unless defined $inflate;
0e5c2582 88 return $inflate->($value, $self);
89}
90
89279e9d 91sub _deflated_column {
92 my ($self, $col, $value) = @_;
e81a6241 93# return $value unless ref $value && blessed($value); # If it's not an object, don't touch it
94 ## Leave scalar refs (ala SQL::Abstract literal SQL), untouched, deflate all other refs
95 return $value unless (ref $value && ref($value) ne 'SCALAR');
89279e9d 96 my $info = $self->column_info($col) or
97 $self->throw_exception("No column info for $col");
98 return $value unless exists $info->{_inflate_info};
99 my $deflate = $info->{_inflate_info}{deflate};
100 $self->throw_exception("No deflator for $col") unless defined $deflate;
101 return $deflate->($value, $self);
0e5c2582 102}
103
7eb4ecc8 104=head2 get_inflated_column
105
106 my $val = $obj->get_inflated_column($col);
107
108Fetch a column value in its inflated state. This is directly
109analogous to L<DBIx::Class::Row/get_column> in that it only fetches a
110column already retreived from the database, and then inflates it.
111Throws an exception if the column requested is not an inflated column.
112
113=cut
114
0e5c2582 115sub get_inflated_column {
116 my ($self, $col) = @_;
bc0c9800 117 $self->throw_exception("$col is not an inflated column")
118 unless exists $self->column_info($col)->{_inflate_info};
0e5c2582 119 return $self->{_inflated_column}{$col}
120 if exists $self->{_inflated_column}{$col};
0e5c2582 121 return $self->{_inflated_column}{$col} =
4a07648a 122 $self->_inflated_column($col, $self->get_column($col));
0e5c2582 123}
124
7eb4ecc8 125=head2 set_inflated_column
126
127 my $copy = $obj->set_inflated_column($col => $val);
128
129Sets a column value from an inflated value. This is directly
130analogous to L<DBIx::Class::Row/set_column>.
131
132=cut
133
0e5c2582 134sub set_inflated_column {
180c7679 135 my ($self, $col, $obj) = @_;
9f471067 136 $self->set_column($col, $self->_deflated_column($col, $obj));
137 if (blessed $obj) {
138 $self->{_inflated_column}{$col} = $obj;
139 } else {
140 delete $self->{_inflated_column}{$col};
141 }
142 return $obj;
0e5c2582 143}
144
7eb4ecc8 145=head2 store_inflated_column
146
147 my $copy = $obj->store_inflated_column($col => $val);
148
149Sets a column value from an inflated value without marking the column
47c56124 150as dirty. This is directly analogous to L<DBIx::Class::Row/store_column>.
7eb4ecc8 151
152=cut
153
0e5c2582 154sub store_inflated_column {
180c7679 155 my ($self, $col, $obj) = @_;
9f471067 156 unless (blessed $obj) {
157 delete $self->{_inflated_column}{$col};
c5cf11f1 158 $self->store_column($col => $obj);
159 return $obj;
9f471067 160 }
25594f03 161 delete $self->{_column_data}{$col};
89279e9d 162 return $self->{_inflated_column}{$col} = $obj;
180c7679 163}
4a07648a 164
47c56124 165=head2 get_column
166
167Gets a column value in the same way as L<DBIx::Class::Row/get_column>. If there
168is an inflated value stored that has not yet been deflated, it is deflated
169when the method is invoked.
170
171=cut
172
180c7679 173sub get_column {
174 my ($self, $col) = @_;
89279e9d 175 if (exists $self->{_inflated_column}{$col}
176 && !exists $self->{_column_data}{$col}) {
177 $self->store_column($col, $self->_deflated_column($col, $self->{_inflated_column}{$col}));
178 }
180c7679 179 return $self->next::method($col);
180}
4a07648a 181
3fbd7eac 182=head2 get_columns
183
184Returns the get_column info for all columns as a hash,
185just like L<DBIx::Class::Row/get_columns>. Handles inflation just
186like L</get_column>.
187
188=cut
189
180c7679 190sub get_columns {
191 my $self = shift;
192 if (exists $self->{_inflated_column}) {
89279e9d 193 foreach my $col (keys %{$self->{_inflated_column}}) {
425f2ea9 194 $self->store_column($col, $self->_deflated_column($col, $self->{_inflated_column}{$col}))
89279e9d 195 unless exists $self->{_column_data}{$col};
196 }
180c7679 197 }
198 return $self->next::method;
199}
200
3fbd7eac 201=head2 has_column_loaded
202
203Like L<DBIx::Class::Row/has_column_loaded>, but also returns true if there
204is an inflated value stored.
205
206=cut
207
180c7679 208sub has_column_loaded {
209 my ($self, $col) = @_;
210 return 1 if exists $self->{_inflated_column}{$col};
211 return $self->next::method($col);
0e5c2582 212}
213
7eb4ecc8 214=head2 update
215
216Updates a row in the same way as L<DBIx::Class::Row/update>, handling
217inflation and deflation of columns appropriately.
218
219=cut
220
9fcda149 221sub update {
222 my ($class, $attrs, @rest) = @_;
180c7679 223 foreach my $key (keys %{$attrs||{}}) {
096f4212 224 if (ref $attrs->{$key} && $class->has_column($key)
9fcda149 225 && exists $class->column_info($key)->{_inflate_info}) {
180c7679 226 $class->set_inflated_column($key, delete $attrs->{$key});
9fcda149 227 }
228 }
229 return $class->next::method($attrs, @rest);
230}
231
7eb4ecc8 232=head2 new
233
234Creates a row in the same way as L<DBIx::Class::Row/new>, handling
235inflation and deflation of columns appropriately.
236
237=cut
238
0e5c2582 239sub new {
240 my ($class, $attrs, @rest) = @_;
180c7679 241 my $inflated;
242 foreach my $key (keys %{$attrs||{}}) {
243 $inflated->{$key} = delete $attrs->{$key}
096f4212 244 if ref $attrs->{$key} && $class->has_column($key)
245 && exists $class->column_info($key)->{_inflate_info};
0e5c2582 246 }
180c7679 247 my $obj = $class->next::method($attrs, @rest);
248 $obj->{_inflated_column} = $inflated if $inflated;
249 return $obj;
0e5c2582 250}
251
bcae85db 252=head1 SEE ALSO
253
254=over 4
255
256=item L<DBIx::Class::Core> - This component is loaded as part of the
257 "core" L<DBIx::Class> components; generally there is no need to
258 load it directly
259
260=back
261
262=head1 AUTHOR
263
264Matt S. Trout <mst@shadowcatsystems.co.uk>
265
266=head1 CONTRIBUTORS
267
268Daniel Westermann-Clark <danieltwc@cpan.org> (documentation)
269
e81a6241 270Jess Robinson <cpan@desert-island.demon.co.uk>
271
bcae85db 272=head1 LICENSE
273
274You may distribute this code under the same terms as Perl itself.
275
276=cut
277
0e5c2582 2781;