Commit | Line | Data |
b0930c1e |
1 | package DBIx::Class::ResultClass::HashRefInflator; |
2 | |
83304545 |
3 | use strict; |
4 | use warnings; |
5 | |
137c657c |
6 | =head1 NAME |
7 | |
8 | DBIx::Class::ResultClass::HashRefInflator |
9 | |
10 | =head1 SYNOPSIS |
11 | |
a5b29361 |
12 | use DBIx::Class::ResultClass::HashRefInflator; |
13 | |
137c657c |
14 | my $rs = $schema->resultset('CD'); |
15 | |
16 | $rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); |
a5b29361 |
17 | or |
18 | $rs->result_class(DBIx::Class::ResultClass::HashRefInflator->new (%args)); |
19 | |
20 | while (my $hashref = $rs->next) { |
21 | ... |
22 | } |
137c657c |
23 | |
24 | =head1 DESCRIPTION |
25 | |
a5b29361 |
26 | DBIx::Class is faster than older ORMs like Class::DBI but it still isn't |
27 | designed primarily for speed. Sometimes you need to quickly retrieve the data |
28 | from a massive resultset, while skipping the creation of fancy row objects. |
29 | Specifying this class as a C<result_class> for a resultset will change C<< $rs->next >> |
30 | to return a plain data hash-ref (or a list of such hash-refs if C<< $rs->all >> is used). |
137c657c |
31 | |
a5b29361 |
32 | There are two ways of using this class: |
137c657c |
33 | |
34 | =over |
35 | |
36 | =item * |
37 | |
a5b29361 |
38 | Supply an instance of DBIx::Class::ResultClass::HashRefInflator to |
39 | C<< $rs->result_class >>. See L</ARGUMENTS> for a list of valid |
40 | arguments to new(). |
137c657c |
41 | |
42 | =item * |
43 | |
a5b29361 |
44 | Another way is to simply supply the class name as a string to |
45 | C<< $rs->result_class >>. Equivalent to passing |
46 | DBIx::Class::ResultClass::HashRefInflator->new(). |
137c657c |
47 | |
48 | =back |
49 | |
a5b29361 |
50 | There are two ways of applying this class to a resultset: |
2328814a |
51 | |
a5b29361 |
52 | =over |
137c657c |
53 | |
a5b29361 |
54 | =item * |
137c657c |
55 | |
a5b29361 |
56 | Specify C<< $rs->result_class >> on a specific resultset to affect only that |
57 | resultset (and any chained off of it); or |
137c657c |
58 | |
a5b29361 |
59 | =item * |
137c657c |
60 | |
a5b29361 |
61 | Specify C<< __PACKAGE__->result_class >> on your source object to force all |
62 | uses of that result source to be inflated to hash-refs - this approach is not |
63 | recommended. |
137c657c |
64 | |
a5b29361 |
65 | =back |
137c657c |
66 | |
67 | =cut |
b0930c1e |
68 | |
2328814a |
69 | ############## |
70 | # NOTE |
71 | # |
a5b29361 |
72 | # Generally people use this to gain as much speed as possible. If a new &mk_hash is |
2328814a |
73 | # implemented, it should be benchmarked using the maint/benchmark_hashrefinflator.pl |
a5b29361 |
74 | # script (in addition to passing all tests of course :). Additional instructions are |
2328814a |
75 | # provided in the script itself. |
76 | # |
77 | |
a5b29361 |
78 | # This coderef is a simple recursive function |
79 | # Arguments: ($me, $prefetch) from inflate_result() below |
80 | my $mk_hash; |
81 | $mk_hash = sub { |
2328814a |
82 | if (ref $_[0] eq 'ARRAY') { # multi relationship |
a5b29361 |
83 | return [ map { $mk_hash->(@$_) || () } (@_) ]; |
2328814a |
84 | } |
85 | else { |
86 | my $hash = { |
87 | # the main hash could be an undef if we are processing a skipped-over join |
88 | $_[0] ? %{$_[0]} : (), |
89 | |
90 | # the second arg is a hash of arrays for each prefetched relation |
91 | map |
a5b29361 |
92 | { $_ => $mk_hash->( @{$_[1]->{$_}} ) } |
2328814a |
93 | ( $_[1] ? (keys %{$_[1]}) : () ) |
94 | }; |
95 | |
96 | # if there is at least one defined column consider the resultset real |
97 | # (and not an emtpy has_many rel containing one empty hashref) |
98 | for (values %$hash) { |
99 | return $hash if defined $_; |
100 | } |
b0930c1e |
101 | |
2328814a |
102 | return undef; |
103 | } |
a5b29361 |
104 | }; |
105 | |
106 | # This is the inflator |
107 | my $inflate_hash; |
108 | $inflate_hash = sub { |
109 | my ($hri_instance, $schema, $rc, $data) = @_; |
110 | |
111 | foreach my $column (keys %{$data}) { |
112 | |
113 | if (ref $data->{$column} eq 'HASH') { |
114 | $inflate_hash->($hri_instance, $schema, $schema->source ($rc)->related_class ($column), $data->{$column}); |
115 | } |
116 | elsif (ref $data->{$column} eq 'ARRAY') { |
117 | foreach my $rel (@{$data->{$column}}) { |
118 | $inflate_hash->($hri_instance, $schema, $schema->source ($rc)->related_class ($column), $rel); |
119 | } |
120 | } |
121 | else { |
122 | # "null is null is null" |
123 | next if not defined $data->{$column}; |
124 | |
125 | # cache the inflator coderef |
126 | unless (exists $hri_instance->{_inflator_cache}{$rc}{$column}) { |
127 | $hri_instance->{_inflator_cache}{$rc}{$column} = exists $schema->source ($rc)->_relationships->{$column} |
128 | ? undef # currently no way to inflate a column sharing a name with a rel |
129 | : $rc->column_info($column)->{_inflate_info}{inflate} |
130 | ; |
131 | } |
132 | |
133 | if ($hri_instance->{_inflator_cache}{$rc}{$column}) { |
134 | $data->{$column} = $hri_instance->{_inflator_cache}{$rc}{$column}->($data->{$column}); |
135 | } |
136 | } |
137 | } |
138 | }; |
139 | |
140 | |
141 | =head1 METHODS |
142 | |
143 | =head2 new |
144 | |
145 | $class->new( %args ); |
146 | $class->new({ %args }); |
147 | |
148 | Creates a new DBIx::Class::ResultClass::HashRefInflator object. Takes the following |
149 | arguments: |
150 | |
151 | =over |
152 | |
153 | =item inflate_columns |
154 | |
155 | Sometimes you still want all your data to be inflated to the corresponding |
156 | objects according to the rules you defined in your table classes (e.g. you |
157 | want all dates in the resulting hash to be replaced with the equivalent |
158 | DateTime objects). Supplying C<< inflate_columns => 1 >> to the constructor will |
159 | interrogate the processed columns and apply any inflation methods declared |
160 | via L<DBIx::Class::InflateColumn/inflate_column> to the contents of the |
161 | resulting hash-ref. |
162 | |
163 | =back |
164 | |
165 | =cut |
166 | |
167 | sub new { |
168 | my $self = shift; |
169 | my $args = { (ref $_[0] eq 'HASH') ? %{$_[0]} : @_ }; |
170 | return bless ($args, $self) |
171 | } |
172 | |
173 | =head2 inflate_result |
174 | |
175 | Inflates the result and prefetched data into a hash-ref (invoked by L<DBIx::Class::ResultSet>) |
176 | |
177 | =cut |
178 | |
179 | |
180 | sub inflate_result { |
181 | my ($self, $source, $me, $prefetch) = @_; |
182 | |
183 | my $hashref = $mk_hash->($me, $prefetch); |
184 | |
185 | # if $self is an instance and inflate_columns is set |
186 | if ( (ref $self) and $self->{inflate_columns} ) { |
187 | $inflate_hash->($self, $source->schema, $source->result_class, $hashref); |
188 | } |
189 | |
190 | return $hashref; |
2328814a |
191 | } |
192 | |
a5b29361 |
193 | |
194 | =head1 CAVEATS |
195 | |
196 | =over |
197 | |
198 | =item * |
419ff184 |
199 | |
200 | This will not work for relationships that have been prefetched. Consider the |
201 | following: |
202 | |
203 | my $artist = $artitsts_rs->search({}, {prefetch => 'cds' })->first; |
204 | |
205 | my $cds = $artist->cds; |
206 | $cds->result_class('DBIx::Class::ResultClass::HashRefInflator'); |
207 | my $first = $cds->first; |
208 | |
209 | C<$first> will B<not> be a hashref, it will be a normal CD row since |
210 | HashRefInflator only affects resultsets at inflation time, and prefetch causes |
211 | relations to be inflated when the master C<$artist> row is inflated. |
212 | |
a5b29361 |
213 | =item * |
214 | |
215 | When using C<inflate_columns>, the inflation method lookups are cached in the |
216 | HashRefInflator object for additional speed. If you modify column inflators at run |
217 | time, make sure to grab a new instance of this class to avoid cached surprises. |
218 | |
219 | =back |
220 | |
419ff184 |
221 | =cut |
222 | |
b0930c1e |
223 | 1; |