use strict;
use warnings;
+our %inflator_cache;
+our $inflate_data;
+
=head1 NAME
DBIx::Class::ResultClass::HashRefInflator
=back
+=head1 AUTOMATICALLY INFLATING COLUMN VALUES
+
+So you want to skip the DBIx::Class object creation part, but you still want
+all your data to be inflated according to the rules you defined in your table
+classes. Setting the global variable
+C<$DBIx::Class::ResultClass::HashRefInflator::inflate_data> to a true value
+will instruct L<mk_hash> to interrogate the processed columns and apply any
+inflation methods declared via L<DBIx::Class::InflateColumn/inflate_column>.
+
+For increased speed the inflation method lookups are cached in
+C<%DBIx::Class::ResultClass::HashRefInflator::inflator_cache>. Make sure to
+reset this hash if you modify column inflators at run time.
+
=head1 METHODS
=head2 inflate_result
sub inflate_result {
my ($self, $source, $me, $prefetch) = @_;
- return mk_hash($me, $prefetch);
+ my $hashref = mk_hash($me, $prefetch);
+ inflate_hash ($source->schema, $source->result_class, $hashref) if $inflate_data;
+ return $hashref;
}
=head2 mk_hash
=cut
-sub mk_hash {
- my ($me, $rest) = @_;
+##############
+# NOTE
+#
+# Generally people use this to gain as much speed as possible. If a new mk_hash is
+# implemented, it should be benchmarked using the maint/benchmark_hashrefinflator.pl
+# script (in addition to passing all tests of course :). Additional instructions are
+# provided in the script itself.
+#
+
+sub mk_hash {
+ if (ref $_[0] eq 'ARRAY') { # multi relationship
+ return [ map { mk_hash (@$_) || () } (@_) ];
+ }
+ else {
+ my $hash = {
+ # the main hash could be an undef if we are processing a skipped-over join
+ $_[0] ? %{$_[0]} : (),
+
+ # the second arg is a hash of arrays for each prefetched relation
+ map
+ { $_ => mk_hash( @{$_[1]->{$_}} ) }
+ ( $_[1] ? (keys %{$_[1]}) : () )
+ };
+
+ # if there is at least one defined column consider the resultset real
+ # (and not an emtpy has_many rel containing one empty hashref)
+ for (values %$hash) {
+ return $hash if defined $_;
+ }
- # $me is the hashref of cols/data from the immediate resultsource
- # $rest is a deep hashref of all the data from the prefetched
- # related sources.
+ return undef;
+ }
+}
+
+=head2 inflate_hash
+
+This walks through a hashref produced by L<mk_hash> and inflates any data
+for which there is a registered inflator in the C<column_info>
- # to avoid emtpy has_many rels contain one empty hashref
- return undef if (not keys %$me);
+=cut
+
+sub inflate_hash {
+ my ($schema, $rc, $data) = @_;
- my $def;
+ foreach my $column (keys %{$data}) {
- foreach (values %$me) {
- if (defined $_) {
- $def = 1;
- last;
+ if (ref $data->{$column} eq 'HASH') {
+ inflate_hash ($schema, $schema->source ($rc)->related_class ($column), $data->{$column});
+ }
+ elsif (ref $data->{$column} eq 'ARRAY') {
+ foreach my $rel (@{$data->{$column}}) {
+ inflate_hash ($schema, $schema->source ($rc)->related_class ($column), $rel);
+ }
+ }
+ else {
+ # "null is null is null"
+ next if not defined $data->{$column};
+
+ # cache the inflator coderef
+ unless (exists $inflator_cache{$rc}{$column}) {
+ $inflator_cache{$rc}{$column} = exists $schema->source ($rc)->_relationships->{$column}
+ ? undef # currently no way to inflate a column sharing a name with a rel
+ : $rc->column_info($column)->{_inflate_info}{inflate}
+ ;
+ }
+
+ if ($inflator_cache{$rc}{$column}) {
+ $data->{$column} = $inflator_cache{$rc}{$column}->($data->{$column});
+ }
}
}
- return undef unless $def;
-
- return { %$me,
- map {
- ( $_ =>
- ref($rest->{$_}[0]) eq 'ARRAY'
- ? [ grep defined, map mk_hash(@$_), @{$rest->{$_}} ]
- : mk_hash( @{$rest->{$_}} )
- )
- } keys %$rest
- };
}
=head1 CAVEAT