File Coverage

blib/lib/DBIx/Class/Helper/ResultSet/CorrelateRelationship.pm
Criterion Covered Total %
statement 10 10 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod 1 1 100.0
total 15 15 100.0


line stmt bran cond sub pod time code
1             package DBIx::Class::Helper::ResultSet::CorrelateRelationship;
2             $DBIx::Class::Helper::ResultSet::CorrelateRelationship::VERSION = '2.036000';
3             # ABSTRACT: Easily correlate your ResultSets
4              
5 56     56   96400 use strict;
  56         143  
  56         1589  
6 56     56   288 use warnings;
  56         1910  
  56         2381  
7              
8             use DBIx::Class::Helper::ResultSet::Util
9 56     56   24988 correlate => { -as => 'corr' };
  56         150  
  56         526  
10              
11 2     2 1 93 sub correlate { corr(@_) }
12              
13             1;
14              
15             __END__
16              
17             =pod
18              
19             =head1 NAME
20              
21             DBIx::Class::Helper::ResultSet::CorrelateRelationship - Easily correlate your ResultSets
22              
23             =head1 SYNOPSIS
24              
25             package MyApp::Schema::ResultSet::Author;
26              
27             use parent 'DBIx::Class::ResultSet';
28              
29             __PACKAGE__->load_components(qw(Helper::ResultSet::CorrelateRelationship));
30              
31             sub with_book_count {
32             my $self = shift;
33              
34             $self->search(undef, {
35             '+columns' => {
36             book_count => $self->correlate('books')->count_rs->as_query
37             }
38             });
39             }
40              
41             1;
42              
43             And then elsewhere, like in a controller:
44              
45             my $rows = $schema->resultset('Author')->with_book_count->all;
46              
47             =head1 DESCRIPTION
48              
49             Correlated queries are one of the coolest things I've learned about for SQL
50             since my initial learning of SQL. Unfortunately they are somewhat confusing.
51             L<DBIx::Class> has supported doing them for a long time, but generally people
52             don't think of them because they are so rare. I won't go through all the
53             details of how they work and cool things you can do with them, but here are a
54             couple high level things you can use them for to save you time or effort.
55              
56             If you want to select a list of authors and counts of books for each author,
57             you B<could> use C<group_by> and something like C<COUNT(book.id)>, but then
58             you'd need to make your select list match your C<group_by> and it would just
59             be a hassle forever after that. The L</SYNOPSIS> is a perfect example of how
60             to implement this.
61              
62             If you want to select a list of authors and two separate kinds of counts of
63             books for each author, as far as I know, you B<must> use a correlated subquery
64             in L<DBIx::Class>. Here is an example of how you might do that:
65              
66             package MyApp::Schema::ResultSet::Author;
67              
68             use parent 'DBIx::Class::ResultSet';
69              
70             __PACKAGE__->load_components(qw(Helper::ResultSet::CorrelateRelationship));
71              
72             sub with_good_book_count {
73             my $self = shift;
74              
75             $self->search(undef, {
76             '+columns' => {
77             good_book_count => $self->correlate('books')->good->count_rs->as_query
78             }
79             });
80             }
81              
82             sub with_bad_book_count {
83             my $self = shift;
84              
85             $self->search(undef, {
86             '+columns' => {
87             bad_book_count => $self->correlate('books')->bad->count_rs->as_query
88             }
89             });
90             }
91              
92             1;
93              
94             And then elsewhere, like in a controller:
95              
96             my $rows = $schema->resultset('Author')
97             ->with_bad_book_count
98             ->with_good_book_count
99             ->all;
100              
101             This assumes that the Book resultset has C<good> and C<bad> methods.
102              
103             See L<DBIx::Class::Helper::ResultSet/NOTE> for a nice way to apply it to
104             your entire schema.
105              
106             =head1 METHODS
107              
108             =head2 correlate
109              
110             $rs->correlate($relationship_name)
111              
112             Correlate takes a single argument, a relationship for the invocant, and returns
113             a resultset that can be used in the selector list.
114              
115             =head1 EXAMPLES
116              
117             =head2 counting CD's and Tracks of Artists
118              
119             If you had an Artist ResultSet and you wanted to count the tracks and CD's per
120             Artist, here is a recipe that will work:
121              
122             sub with_track_count {
123             my $self = shift;
124              
125             $self->search(undef, {
126             '+columns' => {
127             track_count => $self->correlate('cds')
128             ->related_resultset('tracks')
129             ->count_rs
130             ->as_query
131             }
132             });
133             }
134              
135             sub with_cd_count {
136             my $self = shift;
137              
138             $self->search(undef, {
139             '+columns' => {
140             cd_count => $self->correlate('cds')
141             ->count_rs
142             ->as_query
143             }
144             });
145             }
146              
147             # elsewhere
148              
149             my @artists = $artists->with_cd_count->with_track_count->all;
150              
151             Note that the following will B<not> work:
152              
153             sub BUSTED_with_track_count {
154             my $self = shift;
155              
156             $self->search(undef, {
157             '+columns' => {
158             track_count => $self->related_resultset('cds')
159             ->correlate('tracks')
160             ->count_rs
161             ->as_query
162             }
163             });
164             }
165              
166             The above is broken because C<correlate> returns a fresh resultset that will
167             only work as a subquery to the ResultSet it was chained off of. The upshot
168             of that is that the above C<tracks> relationship is on the C<cds> ResultSet,
169             whereas the query is for the Artist ResultSet, so the correlation will be
170             "broken" by effectively "joining" to columns that are not in the current scope.
171              
172             For the same reason, the following will also not work:
173              
174             sub BUSTED2_with_track_count {
175             my $self = shift;
176              
177             $self->search(undef, {
178             '+columns' => {
179             track_count => $self->correlate('cds')
180             ->correlate('tracks')
181             ->count_rs
182             ->as_query
183             }
184             });
185             }
186              
187             =head1 SEE ALSO
188              
189             =over
190              
191             =item * L<Introducing DBIx::Class::Helper::ResultSet::CorrelateRelationship|https://blog.afoolishmanifesto.com/posts/introducing-dbix-class-helper-resultset-correlaterelationship/>
192              
193             =item * L<Set-based DBIx::Class Advent Article|http://www.perladvent.org/2012/2012-12-21.html>
194              
195             =back
196              
197             =head1 AUTHOR
198              
199             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
200              
201             =head1 COPYRIGHT AND LICENSE
202              
203             This software is copyright (c) 2020 by Arthur Axel "fREW" Schmidt.
204              
205             This is free software; you can redistribute it and/or modify it under
206             the same terms as the Perl 5 programming language system itself.
207              
208             =cut