6a00d25da32003b090b53639975d1979fa0c7e4e
[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.000001
39
40 =cut
41
42 our $VERSION = '1.000001';
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   my @rows;
146   foreach my $row ($rs->all) {
147     # THIS BLOCK IS DEPRECATED
148     if (my $info = $self->row_info_for(id => $self->_mk_id(row => $row))) {
149       $row = { %{$row}, %{$info} };
150     }
151
152     foreach my $modifier (@{$rs->_hash_modifiers}) {
153       my $row_hash = $modifier->($row);
154       if (ref $row_hash ne 'HASH') {
155         die 'modifier subref (added via build_metadata) did not return hashref';
156       }
157
158       # simple merge for now, potentially needs to be more complex
159       $row->{$_} = $row_hash->{$_} for keys %{$row_hash};
160     }
161
162     foreach my $params (@{$rs->_key_modifiers}) {
163       my $modifier = $params->{modifier};
164       my $key = $params->{key};
165
166       if (my $val = $modifier->($row)) {
167         $row->{$key} = $val;
168       }
169     }
170     push(@rows, $row);
171   }
172
173   return ($self->was_row) ? $rows[0] : \@rows;
174 }
175
176 =head2 _with_meta_key
177
178 =over 4
179
180 =item Arguments: key_name => subref($row_hash)
181
182 =item Return Value: ResultSet
183
184 =back
185
186  $self->_with_meta_key( substr => sub ($row) { 
187    return substr(shift->{name}, 0, 3);
188  });
189
190 This method allows you populate a certain key for each row hash at  L</display> time.
191
192 =cut
193
194 method _with_meta_key ($key, $modifier) {
195   my $rs = $self->search({});
196   unless ($key) {
197     die 'build_metadata called without key';
198   }
199
200   unless ($modifier && (ref $modifier eq 'CODE')) {
201     die 'build_metadata called without modifier param';
202   }
203
204   push( @{$rs->_key_modifiers}, { key => $key, modifier => $modifier });
205   return $rs;
206 }
207
208 =head2 _with_meta_hash
209
210 =over 4
211
212 =item Arguments: subref($row_hash)
213
214 =item Return Value: ResultSet
215
216 =back
217
218  $self->_with_meta_hash( sub ($row) { 
219    my $row = shift;
220    my $return_hash = { substr => substr($row->{name}, 0, 3), substr2 => substr($row->{name}, 0, 4) };
221    return $return_hash;
222  });
223
224 Use this method when you want to populate multiple keys of the hash at the same time. If you just want to 
225 populate one key, use L</_with_meta_key>.
226
227 =cut
228
229 method _with_meta_hash ($modifier) {
230   my $rs = $self->search({});
231   unless ($modifier && (ref $modifier eq 'CODE')) {
232     die 'build_metadata called without modifier param';
233   }
234
235   push( @{$rs->_hash_modifiers}, $modifier );
236   return $rs;
237 }
238
239 =head2 add_row_info (DEPRECATED)
240
241 =over 4
242
243 =item Arguments: row => DBIx::Class::Row object, info => HashRef to attach to the row
244
245 =item Return Value: ResultSet
246
247 =back
248
249  $rs = $rs->add_row_info(row => $row, info => { dates => [qw/mon weds fri/] } );
250
251 DEPRECATED - this method is quite slow as it requires that you iterate through 
252 the resultset each time you want to add metadata. Replaced by L</build_metadata>.
253
254 =cut
255
256 method add_row_info (%opts) {
257   my ($row, $id, $info) = map { $opts{$_} } qw/row id info/;
258
259   warn 'DEPRECATED - add_row_info is deprecated in favour of build_metadata';
260   if ($row) {
261     $id = $self->_mk_id(row => { $row->get_columns });
262   }
263
264   unless ($row || $self->find($id)) {
265     die 'invalid id passed to add_row_info';
266   }
267
268   if (my $existing = $self->_row_info->{$id}) {
269     $info = { %{$existing}, %{$info} };
270   }
271
272   $self->_row_info->{$id} = $info;  
273 }
274
275 # DEPRECATED
276 method row_info_for (%opts) {
277   my $id = $opts{id};
278   return $self->_row_info->{$id};
279 }
280
281 # DEPRECATED
282 method _mk_id (%opts) {
283   my $row = $opts{row};
284   return join('-', map { $row->{$_} } @{$self->id_cols});
285 }
286
287 =head1 AUTHOR
288
289   Luke Saunders <luke.saunders@gmail.com>
290
291 =head1 THANKS
292
293 As usual, thanks to Matt S Trout for the sanity check.
294
295 =head1 LICENSE
296
297   This library is free software under the same license as perl itself
298
299 =cut
300
301 1;