File Coverage

blib/lib/DBIx/Class/Wrapper/Factory.pm
Criterion Covered Total %
statement 119 123 96.7
branch 27 36 75.0
condition 7 10 70.0
subroutine 22 23 95.6
pod 20 20 100.0
total 195 212 91.9


line stmt bran cond sub pod time code
1             package DBIx::Class::Wrapper::Factory;
2             $DBIx::Class::Wrapper::Factory::VERSION = '0.009';
3 4     4   26004 use Moose;
  4         9  
  4         25  
4             extends qw/DBIx::Class::Wrapper::FactoryBase/;
5              
6             =head1 NAME
7              
8             DBIx::Class::Wrapper::Factory - A factory class that decorates a L<DBIx::Class::ResultSet>.
9              
10             =head1 SYNOPSIS
11              
12             A model implementing the role DBIx::Class::Wrapper will automatically instantiate
13             subclasses of this for any underlying DBIx::Class ResultSet.
14              
15             To implement your own factory containing your business code for the underlying
16             DBIC resulsets, you need to subclass this.
17              
18             See L<DBIx::Class::Wrapper> for a simple synopsis overview.
19              
20             =head1 PROPERTIES
21              
22             =head2 dbic_rs
23              
24             The original L<DBIx::Class::ResultSet>. Mandatory.
25              
26             =head2 bm
27              
28             The business model consuming the role L<DBIx::Class::Wrapper>. Mandatory.
29              
30             See L<DBIx::Class::Wrapper> for more details.
31              
32             =cut
33              
34             has 'dbic_rs' => ( is => 'ro' , isa => 'DBIx::Class::ResultSet', required => 1 , lazy_build => 1);
35             has 'bm' => ( is => 'ro' , does => 'DBIx::Class::Wrapper' , required => 1 , weak_ref => 1 );
36             has 'name' => ( is => 'ro' , isa => 'Str' , required => 1 );
37              
38             sub _build_dbic_rs{
39 20     20   54 my ($self) = @_;
40 20         82 return $self->build_dbic_rs();
41             }
42              
43             =head2 build_dbic_rs
44              
45             Builds the dbic ResultSet to be wrapped by this factory.
46              
47             Defaults to the DBIx::Class Resultset with the same name
48             as this factory.
49              
50             You can override this in your business specific factories to build
51             specific resultsets:
52              
53             package My::Model::Factory::SomeName;
54              
55             use Moose; extends qw/DBIx::Class::Wrapper::Factory/ ;
56              
57             sub build_dbic_rs{
58             my ($self) = @_;
59             return $self->bm->dbic_schema->resultset('SomeOtherName');
60              
61             # Or with some restriction:
62              
63             return $self->bm->dbic_schema->resultset('SomeOtherName')
64             ->search({ bla => ... });
65             }
66              
67              
68             =cut
69              
70             sub build_dbic_rs{
71 10     10 1 25 my ($self) = @_;
72 10         21 my $resultset = eval{ return $self->bm->dbic_schema->resultset($self->name); };
  10         279  
73 10 100       4471 if( my $err = $@ ){
74 2         97 confess("Cannot build resultset for $self NAME=".$self->name().' :'.$err);
75             }
76 8         236 return $resultset;
77             }
78              
79              
80             =head2 new_result
81              
82             Instantiate a new NOT INSERTED IN DB row and wrap it using
83             the wrap method.
84              
85             See L<DBIx::Class::ResultSet/new_result>
86              
87             =cut
88              
89             sub new_result{
90 1     1 1 3 my ($self, $args) = @_;
91 1         34 return $self->wrap($self->dbic_rs->new_result($args));
92             }
93              
94             =head2 create
95              
96             Creates a new object in the DBIC Schema and return it wrapped
97             using the wrapper method.
98              
99             See L<DBIx::Class::ResultSet/create>
100              
101             =cut
102              
103             sub create{
104 2204     2204 1 40786 my ($self , $args) = @_;
105 2204         82343 return $self->wrap($self->dbic_rs->create($args));
106             }
107              
108             =head2 find
109              
110             Finds an object in the DBIC schema and returns it wrapped
111             using the wrapper method.
112              
113             See L<DBIx::Class::ResultSet/find>
114              
115             =cut
116              
117             sub find{
118 9     9 1 7212 my ($self , @rest) = @_;
119 9         254 my $original = $self->dbic_rs->find(@rest);
120 9 100       23789 return $original ? $self->wrap($original) : undef;
121             }
122              
123             =head2 first
124              
125             Equivalent to DBIC Resultset 'first' method.
126              
127             See <DBIx::Class::ResultSet/first>
128              
129             =cut
130              
131             sub first{
132 2     2 1 8 my ($self) = @_;
133 2         68 my $original = $self->dbic_rs->first();
134 2 50       11019 return $original ? $self->wrap($original) : undef;
135             }
136              
137             =head2 single
138              
139             Equivalent to DBIx::Class::ResultSet::single. It's a bit more efficient than C<first()>.
140              
141             =cut
142              
143             sub single {
144 1     1 1 3 my ($self) = @_;
145 1         34 my $original = $self->dbic_rs->single();
146 1 50       1019 return $original ? $self->wrap($original) : undef;
147             }
148              
149             =head2 update_or_create
150              
151             Wraps around the original DBIC update_or_create method.
152              
153             See L<DBIx::Class::ResultSet/update_or_create>
154              
155             =cut
156              
157             sub update_or_create {
158 2     2 1 6782 my ($self, $args) = @_;
159 2         81 my $original = $self->dbic_rs->update_or_create($args);
160 2 50       9663 return $original ? $self->wrap($original) : undef;
161             }
162              
163             =head2 find_or_create
164              
165             Wraps around the original DBIC find_or_create method.
166              
167             See L<DBIx::Class::ResultSet/find_or_create>
168              
169             =cut
170              
171             sub find_or_create{
172 4     4 1 1689 my ($self , $args) = @_;
173 4         137 my $original = $self->dbic_rs->find_or_create($args);
174 4 50       19442 return $original ? $self->wrap($original) : undef;
175             }
176              
177             =head2 find_or_new
178              
179             Wraps around the original DBIC find_or_new method.
180              
181             See L<DBIx::Class::ResultSet/find_or_new>
182              
183             =cut
184              
185             sub find_or_new {
186 2     2 1 2174 my ($self, @args) = @_;
187 2         71 return $self->wrap( $self->dbic_rs->find_or_new( @args ) );
188             }
189              
190              
191             =head2 pager
192              
193             Shortcut to underlying dbic_rs pager.
194              
195             See L<DBIx::Class::ResultSet/pager>.
196              
197             =cut
198              
199             sub pager{
200 8     8 1 4294 my ($self) = @_;
201 8         233 return $self->dbic_rs->pager();
202             }
203              
204             =head2 delete
205              
206             Shortcut to L<DBIx::Class::ResultSet/delete>
207              
208             =cut
209              
210             sub delete{
211 2     2 1 7 my ($self , @rest) = @_;
212 2         55 return $self->dbic_rs->delete(@rest);
213             }
214              
215             =head2 get_column
216              
217             Shortcut to the get_column of the decorated dbic_rs
218              
219             See L<DBIx::Class::ResultSet/get_column>
220              
221             =cut
222              
223             sub get_column{
224 2     2 1 8 my ($self, @rest) = @_;
225 2         53 return $self->dbic_rs->get_column(@rest);
226             }
227              
228             =head2 search_rs
229              
230             Alias for search
231              
232             =cut
233              
234             sub search_rs{
235 2     2 1 288 goto &search;
236             }
237              
238             =head2 search
239              
240             Search objects in the DBIC Schema and returns a new instance
241             of this factory.
242              
243             Note that unlike DBIx::Class::ResultSet, this search method
244             will not return an Array of all results in an array context.
245              
246             =cut
247              
248             sub search{
249 91     91 1 633702 my ($self , @rest) = @_;
250 91         255 my $class = ref($self);
251 91         3169 return $class->new({ dbic_rs => $self->dbic_rs->search_rs(@rest),
252             bm => $self->bm(),
253             name => $self->name()
254             });
255             }
256              
257              
258             =head2 wrap
259              
260             Wraps an L<DBIx::Class::Row> in a business object. By default, it returns the
261             Row itself.
262              
263             Override that in your subclasses of factories if you need to wrap some business code
264             around the L<DBIx::Class::Row>:
265              
266             sub wrap{
267             my ($self, $o) = @_;
268              
269             return My::Model::O::SomeObject->new({ o => $o , ... });
270             }
271              
272             =cut
273              
274             sub wrap{
275 10426     10426 1 3431506 my ($self , $o) = @_;
276 10426         27800 return $o;
277             }
278              
279              
280             =head2 all
281              
282             Similar to DBIC Resultset all.
283              
284             Usage:
285              
286             my @objs = $this->all();
287              
288             =cut
289              
290             sub all{
291 2     2 1 239 my ($self) = @_;
292 2         7 my $search = $self->search();
293 2         2893 my @res = ();
294 2         10 while( my $next = $search->next() ){
295 4         2610 push @res , $next;
296             }
297 2         88 return @res;
298             }
299              
300             =head2 loop_through
301              
302             Loop through all the elements of this factory
303             whilst paging and execute the given code
304             with the current retrieved object.
305              
306             WARNINGS:
307              
308             Make sure your resultset is ordered as
309             it wouldn't make much sense to page through an unordered resultset.
310              
311             In case other things are concurrently adding to this resultset, it is possible
312             that the code you give will be called with the same objects twice.
313              
314             If it's not the problem and if the rate at which objects are added is
315             not too fast compared to the processing you are doing in the code, it
316             should be just fine.
317              
318             In other cases, you probably want to wrap this in a transaction to have
319             a frozen view of the resultset.
320              
321             Usage:
322              
323             $this->loop_through(sub{ my $o = shift ; do something with o });
324             $this->loop_through(sub{...} , { limit => 1000 }); # Do only 1000 calls to sub.
325             $this->loop_through(sub{...} , { rows => 20 }); # Go by pages of 20 rows
326              
327             =cut
328              
329             sub loop_through{
330 6     6 1 9535 my ($self, $code , $opts ) = @_;
331              
332 6 100       32 unless( defined $opts ){
333 2         5 $opts = {};
334             }
335              
336 6         19 my $limit = $opts->{limit};
337 6 100       33 my $rows = defined $opts->{rows} ? $opts->{rows} : 10;
338              
339 6 50       16 my $attrs = { %{$self->dbic_rs->{attrs} || {} } };
  6         178  
340 6 50       28 unless( $attrs->{order_by} ){
341 0         0 warn(q|
342              
343             Missing order_by attribute. Order will be undefined in |.__PACKAGE__.q| loop_through.
344              
345             |);
346              
347             }
348              
349             # init
350 6         17 my $page = 1;
351 6         33 my $search = $self->search(undef , { page => $page , rows => $rows });
352 6         8704 my $last_page = $search->pager->last_page();
353              
354 6         27845 my $ncalls = 0;
355             # loop though all pages.
356             PAGELOOP:
357 6         28 while( $page <= $last_page ){
358             # Loop through this page
359 62         12768 while( my $o = $search->next() ){
360 2402         7273 $code->($o);
361 2402         188426 $ncalls++;
362 2402 100 100     11697 if( $limit && ( $ncalls >= $limit ) ){
363 2         68 last PAGELOOP;
364             }
365             }
366             # Done with this page.
367             # Go to the next one.
368 60         147 $page++;
369 60         369 $search = $self->search(undef, { page => $page , rows => $rows });
370             }
371             }
372              
373              
374             =head2 fast_loop_through
375              
376             Loops through all the objects of this factory
377             in a Seeking fashion. If the primary key of the underlying
378             resultset is orderable and indexed, this should run
379             in linear time of the number of rows on the resultset.
380              
381             Usage:
382              
383             $this->fast_loop_through(sub{my ($o) = @_; ... } );
384              
385             $this->fast_loop_through(sub{ .. } , { rows => 100 , limit => 1000 });
386              
387             Options:
388              
389             rows: Fetch this amount of rows at each query. Default to 100
390              
391             limit: Return after looping through this amount of rows.
392              
393             B<Important>
394              
395             =over
396              
397             You do not need to order the set, as this will order it by ascending primary key.
398              
399             This means aggregation functions (such as group_by) will not work.
400              
401             Incidentally, it means that if other processes are writing to this resultset,
402             this method will play catch up on the resultset, so if the writing rate is higher
403             than the reading rate, this might take a while to return.
404              
405             If you want to avoid this, set the option 'order' to 'desc'.
406              
407             =back
408              
409             Returns the number of rows looped through.
410              
411             Prerequisites:
412              
413             Must have:
414              
415             - The underlying L<DBIx::Class::ResultSource> has a primary key
416              
417             - Each component of the primary key supports the operators '>' and '<'
418              
419             - It is possible to order all the rows by this primary key alone.
420              
421             Should have:
422              
423             - This primary key is indexed and offers fast comparison access.
424              
425             Inspired by http://use-the-index-luke.com/sql/partial-results/fetch-next-page
426              
427             =cut
428              
429             sub fast_loop_through{
430 7     7 1 13946 my ($self , $code, $opts) = @_;
431              
432 7 50   0   36 unless( defined $code ){ $code = sub{}; }
  0         0  
433 7 100       29 unless( defined $opts ){ $opts = {}; }
  3         9  
434              
435 7   100     68 my $order = $opts->{order} || 'asc';
436 7   50     54 my $rows = $opts->{rows} || 100;
437 7         21 my $limit = $opts->{limit};
438              
439             # Gather the required info about the resultset
440 7         244 my $rs = $self->dbic_rs();
441              
442             # What is this source alias?
443 7         49 my $me = $rs->current_source_alias();
444              
445             # The source
446 7         105 my $source = $rs->result_source();
447 7         48 my @primary_columns = $source->primary_columns();
448 7 50       77 unless( @primary_columns ){
449 0         0 confess("Result Source ".$source->source_name()." does not have a primary key");
450             }
451              
452 7         23 my $order_by = [ map{ +{ '-'.$order => $me.'.'.$_ } } @primary_columns ];
  13         79  
453              
454 7         21 my $n_rows = 0;
455              
456 7         17 my $last_row;
457 7         17 do{
458 67         20119 my $resultset = $self->dbic_rs->search_rs(undef , { order_by => $order_by , rows => $rows });
459 67 100       28190 if( $last_row ){
460             # We have a last row.
461             # The idea here is to use the primary key to get the rows above
462             # this last row.
463              
464             # The primary key of the first queried row should be greater than
465             # the last row's one.
466             # Logically it should be: ( queried PK components ) > ( last row PK components )
467             # If the primary key is A , B , C , then the where clause should contain (for order desc):
468             # A > a || ( A = a && B > b || ( B = b && C > c ) )
469              
470 60         324 my $top_or = { -or => [] };
471 60         205 my $cur_or = $top_or;
472              
473 60 100       381 my $cmp_op = $order eq 'asc' ? '>' : '<';
474              
475 60         232 my $key_i = 0;
476 60         404 for(; $key_i < @primary_columns - 1 ; $key_i++ ){
477              
478 58         176 my $column = $primary_columns[$key_i];
479              
480 58         184 my $nested_or = { -or => [] };
481              
482 58         130 push @{ $cur_or->{-or} } , { $me.'.'.$column => { $cmp_op => $last_row->get_column($column) }};
  58         322  
483 58         665 push @{ $cur_or->{-or} } , { -and => [ { $me.'.'.$column => { '=' => $last_row->get_column($column) } },
  58         315  
484             $nested_or
485             ]
486             };
487 58         784 $cur_or = $nested_or;
488             }
489              
490 60         211 my $last_column = $primary_columns[$key_i];
491 60         166 push @{ $cur_or->{-or} } , { $me.'.'.$last_column => { $cmp_op => $last_row->get_column($last_column) } };
  60         480  
492              
493 60         866 $resultset = $resultset->search_rs($top_or);
494             } # End of above last row seeking
495              
496 67         16187 $last_row = undef;
497              
498 67         1080 while( my $o = $resultset->next() ){
499 5812         1049703 $last_row = $o;
500 5812         112216 my $wrapped = $self->wrap($o);
501 5812         17700 &$code($wrapped);
502 5812         344602 $n_rows++;
503              
504 5812 50 33     26391 if( $limit && ( $n_rows == $limit ) ){
505 0         0 return $n_rows;
506             }
507             }
508             }while( $last_row );
509              
510 7         37454 return $n_rows;
511             }
512              
513             =head2 next
514              
515             Returns next Business Object from this current DBIx::Resultset.
516              
517             See L<DBIx::Class::ResultSet/next>
518              
519             =cut
520              
521             sub next{
522 2478     2478 1 60579 my ($self) = @_;
523 2478         80259 my $next_o = $self->dbic_rs->next();
524 2478 100       439717 return undef unless $next_o;
525 2412         6877 return $self->wrap($next_o);
526             }
527              
528             =head2 count
529              
530             Returns the number of objects in this ResultSet.
531              
532             See L<DBIx::Class::ResultSet/count>
533              
534             =cut
535              
536             sub count{
537 21     21 1 33554 my ($self) = @_;
538 21         619 return $self->dbic_rs->count();
539             }
540              
541              
542             __PACKAGE__->meta->make_immutable();
543             1;