01a98538d053ea69f779d294ea44702da517c898
[dbsrgits/DBIx-Class-ResultSet-WithMetaData.git] / lib / DBIx / Class / ResultSet / WithMetaData.pm
1 package DBIx::Class::ResultSet::WithMetaData;
2
3 use strict;
4 use warnings;
5
6 use Data::Alias;
7 use Moose;
8 use Method::Signatures::Simple;
9 extends 'DBIx::Class::ResultSet';
10
11 has '_row_info' => (
12   is => 'rw',
13   isa => 'HashRef'
14 );
15
16 has 'was_row' => (
17   is => 'rw',
18   isa => 'Int'
19 );
20
21 has 'id_cols' => (
22   is => 'rw',
23   isa => 'ArrayRef',
24 );
25
26 has '_hash_modifiers' => (
27   is => 'rw',
28   isa => 'ArrayRef',
29 );
30
31 has '_key_modifiers' => (
32   is => 'rw',
33   isa => 'ArrayRef',
34 );
35
36 =head1 VERSION
37
38 Version 1.000002
39
40 =cut
41
42 our $VERSION = '1.000002';
43
44 =head1 NAME
45
46 DBIx::Class::ResultSet::WithMetaData
47
48 =head1 SYNOPSIS
49
50   package MyApp::Schema::ResultSet::ObjectType;
51
52   use Moose;
53   use MooseX::Method::Signatures;
54   extends 'DBIx::Class::ResultSet::WithMetaData;
55
56   method with_substr () {
57     return $self->_with_meta_key( 
58       substr => sub {
59         return substr(shift->{name}, 0, 3);
60       }
61     );
62   }
63
64   ...
65
66
67   # then somewhere else
68
69   my $object_type_arrayref = $object_type_rs->with_substr->display();
70
71   # [{
72   #    'artistid' => '1',
73   #    'name' => 'Caterwauler McCrae',
74   #    'substr' => 'Cat'
75   #  },
76   #  {
77   #    'artistid' => '2',
78   #    'name' => 'Random Boy Band',
79   #    'substr' => 'Ran'
80   #  },
81   #  {
82   #    'artistid' => '3',
83   #    'name' => 'We Are Goth',
84   #    'substr' => 'We '
85   #  }]
86
87 =head1 DESCRIPTION
88
89 Attach metadata to rows by chaining ResultSet methods together. When the ResultSet is
90 flattened to an ArrayRef the metadata is merged with the row hashes to give
91 a combined 'hash-plus-other-stuff' representation.
92
93 =head1 METHODS
94
95 =cut
96
97 sub new {
98   my $self = shift;
99
100   my $new = $self->next::method(@_);
101   foreach my $key (qw/_row_info was_row id_cols _key_modifiers _hash_modifiers/) {
102     alias $new->{$key} = $new->{attrs}{$key};
103   }
104
105   unless ($new->_row_info) {
106     $new->_row_info({});
107   }
108
109   unless ($new->_key_modifiers) {
110     $new->_key_modifiers([]);
111   }
112   unless ($new->_hash_modifiers) {
113     $new->_hash_modifiers([]);
114   }
115
116   unless ($new->id_cols && scalar(@{$new->id_cols})) {
117     $new->id_cols([sort $new->result_source->primary_columns]);
118   }
119
120   return $new;
121 }
122
123 =head2 display
124
125 =over 4
126
127 =item Arguments: none
128
129 =item Return Value: ArrayRef
130
131 =back
132
133  $arrayref_of_row_hashrefs = $rs->display();
134
135 This method uses L<DBIx::Class::ResultClass::HashRefInflator> to convert all
136 rows in the ResultSet to HashRefs. Then the subrefs that were added via 
137 L</_with_meta_key> or L</_with_meta_hash> are run for each row and the
138 resulting data merged with them.
139
140 =cut
141
142 method display () {
143   my $rs = $self->search({});
144 #  $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
145   $rs->result_class('DBIx::Class::WithMetaData::Inflator');
146   my @rows;
147   foreach my $row_rep ($rs->all) {
148     my ($row, $row_obj) = @{$row_rep};
149     # THIS BLOCK IS DEPRECATED
150     if (my $info = $self->row_info_for(id => $self->_mk_id(row => $row))) {
151       $row = { %{$row}, %{$info} };
152     }
153
154     foreach my $modifier (@{$rs->_hash_modifiers}) {
155       my $row_hash = $modifier->($row);
156       if (ref $row_hash ne 'HASH') {
157         die 'modifier subref (added via build_metadata) did not return hashref';
158       }
159
160       # simple merge for now, potentially needs to be more complex
161       $row->{$_} = $row_hash->{$_} for keys %{$row_hash};
162     }
163
164     foreach my $params (@{$rs->_key_modifiers}) {
165       my $modifier = $params->{modifier};
166       my $key = $params->{key};
167
168       if (my $val = $modifier->($row)) {
169         $row->{$key} = $val;
170       }
171     }
172     push(@rows, $row);
173   }
174
175   return ($self->was_row) ? $rows[0] : \@rows;
176 }
177
178 =head2 _with_meta_key
179
180 =over 4
181
182 =item Arguments: key_name => subref($row_hash)
183
184 =item Return Value: ResultSet
185
186 =back
187
188  $self->_with_meta_key( substr => sub ($row) { 
189    return substr(shift->{name}, 0, 3);
190  });
191
192 This method allows you populate a certain key for each row hash at  L</display> time.
193
194 =cut
195
196 method _with_meta_key ($key, $modifier) {
197   my $rs = $self->search({});
198   unless ($key) {
199     die 'build_metadata called without key';
200   }
201
202   unless ($modifier && (ref $modifier eq 'CODE')) {
203     die 'build_metadata called without modifier param';
204   }
205
206   push( @{$rs->_key_modifiers}, { key => $key, modifier => $modifier });
207   return $rs;
208 }
209
210 =head2 _with_meta_hash
211
212 =over 4
213
214 =item Arguments: subref($row_hash)
215
216 =item Return Value: ResultSet
217
218 =back
219
220  $self->_with_meta_hash( sub ($row) { 
221    my $row = shift;
222    my $return_hash = { substr => substr($row->{name}, 0, 3), substr2 => substr($row->{name}, 0, 4) };
223    return $return_hash;
224  });
225
226 Use this method when you want to populate multiple keys of the hash at the same time. If you just want to 
227 populate one key, use L</_with_meta_key>.
228
229 =cut
230
231 method _with_meta_hash ($modifier) {
232   my $rs = $self->search({});
233   unless ($modifier && (ref $modifier eq 'CODE')) {
234     die 'build_metadata called without modifier param';
235   }
236
237   push( @{$rs->_hash_modifiers}, $modifier );
238   return $rs;
239 }
240
241 =head2 add_row_info (DEPRECATED)
242
243 =over 4
244
245 =item Arguments: row => DBIx::Class::Row object, info => HashRef to attach to the row
246
247 =item Return Value: ResultSet
248
249 =back
250
251  $rs = $rs->add_row_info(row => $row, info => { dates => [qw/mon weds fri/] } );
252
253 DEPRECATED - this method is quite slow as it requires that you iterate through 
254 the resultset each time you want to add metadata. Replaced by L</build_metadata>.
255
256 =cut
257
258 method add_row_info (%opts) {
259   my ($row, $id, $info) = map { $opts{$_} } qw/row id info/;
260
261   warn 'DEPRECATED - add_row_info is deprecated in favour of build_metadata';
262   if ($row) {
263     $id = $self->_mk_id(row => { $row->get_columns });
264   }
265
266   unless ($row || $self->find($id)) {
267     die 'invalid id passed to add_row_info';
268   }
269
270   if (my $existing = $self->_row_info->{$id}) {
271     $info = { %{$existing}, %{$info} };
272   }
273
274   $self->_row_info->{$id} = $info;  
275 }
276
277 # DEPRECATED
278 method row_info_for (%opts) {
279   my $id = $opts{id};
280   return $self->_row_info->{$id};
281 }
282
283 # DEPRECATED
284 method _mk_id (%opts) {
285   my $row = $opts{row};
286   return join('-', map { $row->{$_} } @{$self->id_cols});
287 }
288
289 =head1 AUTHOR
290
291   Luke Saunders <luke.saunders@gmail.com>
292
293 =head1 THANKS
294
295 As usual, thanks to Matt S Trout for the sanity check.
296
297 =head1 LICENSE
298
299   This library is free software under the same license as perl itself
300
301 =cut
302
303 1;