Multiple HashRefInflator improvements:
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultClass / HashRefInflator.pm
1 package DBIx::Class::ResultClass::HashRefInflator;
2
3 use strict;
4 use warnings;
5
6 our %inflator_cache;
7 our $inflate_data;
8
9 =head1 NAME
10
11 DBIx::Class::ResultClass::HashRefInflator
12
13 =head1 SYNOPSIS
14
15  my $rs = $schema->resultset('CD');
16
17  $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
18
19 =head1 DESCRIPTION
20
21 DBIx::Class is not built for speed: it's built for convenience and
22 ease of use. But sometimes you just need to get the data, and skip the
23 fancy objects. That is what this class provides.
24
25 There are two ways of using this class.
26
27 =over
28
29 =item *
30
31 Specify C<< $rs->result_class >> on a specific resultset to affect only that
32 resultset (and any chained off of it); or
33
34 =item *
35
36 Specify C<< __PACKAGE__->result_class >> on your source object to force all
37 uses of that result source to be inflated to hash-refs - this approach is not
38 recommended.
39
40 =back
41
42 =head1 AUTOMATICALLY INFLATING COLUMN VALUES
43
44 So you want to skip the DBIx::Class object creation part, but you still want 
45 all your data to be inflated according to the rules you defined in your table
46 classes. Setting the global variable 
47 C<$DBIx::Class::ResultClass::HashRefInflator::inflate_data> to a true value
48 will instruct L<mk_hash> to interrogate the processed columns and apply any
49 inflation methods declared via L<DBIx::Class::InflateColumn/inflate_column>.
50
51 For increased speed the inflation method lookups are cached in 
52 C<%DBIx::Class::ResultClass::HashRefInflator::inflator_cache>. Make sure to 
53 reset this hash if you modify column inflators at run time.
54
55 =head1 METHODS
56
57 =head2 inflate_result
58
59 Inflates the result and prefetched data into a hash-ref using L<mk_hash>.
60
61 =cut
62
63 sub inflate_result {
64     my ($self, $source, $me, $prefetch) = @_;
65
66     my $hashref = mk_hash($me, $prefetch);
67     inflate_hash ($source->schema, $source->result_class, $hashref) if $inflate_data;
68     return $hashref;
69 }
70
71 =head2 mk_hash
72
73 This does all the work of inflating the (pre)fetched data.
74
75 =cut
76
77 ##############
78 # NOTE
79 #
80 # Generally people use this to gain as much speed as possible. If a new mk_hash is
81 # implemented, it should be benchmarked using the maint/benchmark_hashrefinflator.pl
82 # script (in addition to passing all tests of course :). Additional instructions are 
83 # provided in the script itself.
84 #
85
86 sub mk_hash { 
87     if (ref $_[0] eq 'ARRAY') {     # multi relationship
88         return [ map { mk_hash (@$_) || () } (@_) ];
89     }
90     else {
91         my $hash = {
92             # the main hash could be an undef if we are processing a skipped-over join
93             $_[0] ? %{$_[0]} : (),
94
95             # the second arg is a hash of arrays for each prefetched relation
96             map
97                 { $_ => mk_hash( @{$_[1]->{$_}} ) }
98                 ( $_[1] ? (keys %{$_[1]}) : () )
99         };
100
101         # if there is at least one defined column consider the resultset real
102         # (and not an emtpy has_many rel containing one empty hashref)
103         for (values %$hash) {
104             return $hash if defined $_;
105         }
106
107         return undef;
108     }
109 }
110
111 =head2 inflate_hash
112
113 This walks through a hashref produced by L<mk_hash> and inflates any data 
114 for which there is a registered inflator in the C<column_info>
115
116 =cut
117
118 sub inflate_hash {
119     my ($schema, $rc, $data) = @_;
120
121     foreach my $column (keys %{$data}) {
122
123         if (ref $data->{$column} eq 'HASH') {
124             inflate_hash ($schema, $schema->source ($rc)->related_class ($column), $data->{$column});
125         } 
126         elsif (ref $data->{$column} eq 'ARRAY') {
127             foreach my $rel (@{$data->{$column}}) {
128                 inflate_hash ($schema, $schema->source ($rc)->related_class ($column), $rel);
129             }
130         }
131         else {
132             # "null is null is null"
133             next if not defined $data->{$column};
134
135             # cache the inflator coderef
136             unless (exists $inflator_cache{$rc}{$column}) {
137                 $inflator_cache{$rc}{$column} = exists $schema->source ($rc)->_relationships->{$column}
138                     ? undef     # currently no way to inflate a column sharing a name with a rel 
139                     : $rc->column_info($column)->{_inflate_info}{inflate}
140                 ;
141             }
142
143             if ($inflator_cache{$rc}{$column}) {
144                 $data->{$column} = $inflator_cache{$rc}{$column}->($data->{$column});
145             }
146         }
147     }
148 }
149
150 =head1 CAVEAT
151
152 This will not work for relationships that have been prefetched. Consider the
153 following:
154
155  my $artist = $artitsts_rs->search({}, {prefetch => 'cds' })->first;
156
157  my $cds = $artist->cds;
158  $cds->result_class('DBIx::Class::ResultClass::HashRefInflator');
159  my $first = $cds->first; 
160
161 C<$first> will B<not> be a hashref, it will be a normal CD row since 
162 HashRefInflator only affects resultsets at inflation time, and prefetch causes
163 relations to be inflated when the master C<$artist> row is inflated.
164
165 =cut
166
167 1;