File Coverage

blib/lib/DBIx/Class/Helper/Row/JoinTable.pm
Criterion Covered Total %
statement 76 80 95.0
branch 5 8 62.5
condition 13 21 61.9
subroutine 16 16 100.0
pod 7 7 100.0
total 117 132 88.6


line stmt bran cond sub pod time code
1             package DBIx::Class::Helper::Row::JoinTable;
2             $DBIx::Class::Helper::Row::JoinTable::VERSION = '2.034002';
3             # ABSTRACT: Easily set up join tables with DBIx::Class
4              
5 55     55   4821791 use strict;
  55         242  
  55         1425  
6 55     55   253 use warnings;
  55         90  
  55         1277  
7              
8 55     55   263 use parent 'DBIx::Class::Row';
  55         102  
  55         295  
9              
10 55     55   13325 use DBIx::Class::Helpers::Util 'get_namespace_parts';
  55         123  
  55         413  
11 55     55   38890 use Lingua::EN::Inflect ();
  55         1318737  
  55         3170  
12 55     55   565 use DBIx::Class::Candy::Exports;
  55         112  
  55         690  
13              
14             export_methods [qw(
15             join_table
16             generate_primary_key
17             generate_has_manys
18             generate_many_to_manys
19             generate_relationships
20             set_table
21             add_join_columns
22             )];
23              
24             my $decamelize = sub {
25             my $s = shift;
26             $s =~ s{([^a-zA-Z]?)([A-Z]*)([A-Z])([a-z]?)}{
27             my $fc = pos($s)==0;
28             my ($p0,$p1,$p2,$p3) = ($1,lc$2,lc$3,$4);
29             my $t = $p0 || $fc ? $p0 : '_';
30             $t .= $p3 ? $p1 ? "${p1}_$p2$p3" : "$p2$p3" : "$p1$p2";
31             $t;
32             }ge;
33             $s;
34             };
35              
36             sub _pluralize {
37 220     220   1683 my $self = shift;
38 220 50       576 my $original = shift or return;
39 220         2312 return join q{_}, split /\s+/,
40             Lingua::EN::Inflect::PL(join q{ }, split /_/, $original);
41             }
42              
43             sub _defaults {
44 440     440   920 my ($self, $params) = @_;
45              
46 440   66     2875 $params->{namespace} ||= [ get_namespace_parts($self) ]->[0];
47 440   66     1307 $params->{left_method} ||= $decamelize->($params->{left_class});
48 440   33     1037 $params->{right_method} ||= $decamelize->($params->{right_class});
49 440   66     1059 $params->{self_method} ||= $decamelize->($self);
50 440   66     1227 $params->{left_method_plural} ||= $self->_pluralize($params->{left_method});
51 440   66     131825 $params->{right_method_plural} ||= $self->_pluralize($params->{right_method});
52 440   66     11248 $params->{self_method_plural} ||= $self->_pluralize($params->{self_method});
53              
54 440         109580 return $params;
55             }
56              
57             sub join_table {
58 110     110 1 823154 my ($self, $params) = @_;
59              
60 110         632 $self->set_table($params);
61 110         53089 $self->add_join_columns($params);
62 110         36008 $self->generate_relationships($params);
63 110         47854 $self->generate_primary_key($params);
64             }
65              
66             sub generate_primary_key {
67 110     110 1 337 my ($self, $params) = @_;
68              
69 110         381 $self->_defaults($params);
70 110         3130 $self->set_primary_key("$params->{left_method}_id", "$params->{right_method}_id");
71             }
72              
73             sub generate_has_manys {
74 55     55 1 5977 my ($self, $params) = @_;
75              
76 55         360 $params = $self->_defaults($params);
77             "$params->{namespace}::$params->{left_class}"->has_many(
78             $params->{self_method} =>
79 55         767 $self,
80             "$params->{left_method}_id"
81             );
82              
83             "$params->{namespace}::$params->{right_class}"->has_many(
84             $params->{self_method} =>
85 55         27727 $self,
86             "$params->{right_method}_id"
87             );
88             }
89              
90             sub generate_many_to_manys {
91 55     55 1 25043 my ($self, $params) = @_;
92 55         311 $params = $self->_defaults($params);
93              
94             "$params->{namespace}::$params->{left_class}"->many_to_many(
95             $params->{right_method_plural} =>
96             $params->{self_method},
97             $params->{right_method}
98 55         756 );
99              
100             "$params->{namespace}::$params->{right_class}"->many_to_many(
101             $params->{left_method_plural} =>
102             $params->{self_method},
103             $params->{left_method}
104 55         8417 );
105             }
106              
107             sub generate_relationships {
108 110     110 1 345 my ($self, $params) = @_;
109              
110 110         518 $params = $self->_defaults($params);
111             $self->belongs_to(
112             $params->{left_method} =>
113 110         1335 "$params->{namespace}::$params->{left_class}",
114             "$params->{left_method}_id"
115             );
116             $self->belongs_to(
117             $params->{right_method} =>
118 110         69286 "$params->{namespace}::$params->{right_class}",
119             "$params->{right_method}_id"
120             );
121             }
122              
123             sub set_table {
124 110     110 1 327 my ($self, $params) = @_;
125              
126 110         1543 $self->table("$params->{left_class}_$params->{right_class}");
127             }
128              
129             sub _add_join_column {
130 220     220   537 my ($self, $params) = @_;
131              
132 220         420 my $class = $params->{class};
133 220         486 my $method = $params->{method};
134              
135 220         700 my $default = {
136             data_type => 'integer',
137             is_nullable => 0,
138             is_numeric => 1,
139             };
140              
141 220         1428 $self->ensure_class_loaded($class);
142 220         129263 my @datas = qw{is_nullable extra data_type size is_numeric};
143              
144             my @class_column_info = (
145             map {
146 220         6863 my $info = $class->column_info($_);
  220         16459  
147 220         13986 my $result = {};
148 220         412 my $defined = undef;
149 220         501 for (@datas) {
150 1100 100       2223 if (defined $info->{$_}) {
151 330         477 $defined = 1;
152 330         733 $result->{$_} = $info->{$_};
153             }
154             }
155 220 50       508 $result = $default unless $defined;
156 220         663 $result;
157             } $class->primary_columns
158             );
159              
160 220 50       637 if (@class_column_info == 1) {
161 220         2288 $self->add_columns(
162             "${method}_id" => $class_column_info[0],
163             );
164             } else {
165 0         0 my $i = 0;
166 0         0 for (@class_column_info) {
167 0         0 $i++;
168 0         0 $self->add_columns(
169             "${method}_${i}_id" => $_
170             );
171             }
172             }
173             }
174              
175             sub add_join_columns {
176 110     110 1 313 my ($self, $params) = @_;
177              
178 110         1597 $params = $self->_defaults($params);
179              
180 110         398 my $l_class = "$params->{namespace}::$params->{left_class}";
181 110         312 my $r_class = "$params->{namespace}::$params->{right_class}";
182              
183             $self->_add_join_column({
184             class => $l_class,
185             method => $params->{left_method}
186 110         2161 });
187              
188             $self->_add_join_column({
189             class => $r_class,
190             method => $params->{right_method}
191 110         38515 });
192             }
193              
194             1;
195              
196             __END__
197              
198             =pod
199              
200             =head1 NAME
201              
202             DBIx::Class::Helper::Row::JoinTable - Easily set up join tables with DBIx::Class
203              
204             =head1 SYNOPSIS
205              
206             package MyApp::Schema::Result::Foo_Bar;
207              
208             __PACKAGE__->load_components(qw{Helper::Row::JoinTable Core});
209              
210             __PACKAGE__->join_table({
211             left_class => 'Foo',
212             left_method => 'foo',
213             right_class => 'Bar',
214             right_method => 'bar',
215             });
216              
217             # the above is the same as:
218              
219             __PACKAGE__->table('Foo_Bar');
220             __PACKAGE__->add_columns(
221             foo_id => {
222             data_type => 'integer',
223             is_nullable => 0,
224             is_numeric => 1,
225             },
226             bar_id => {
227             data_type => 'integer',
228             is_nullable => 0,
229             is_numeric => 1,
230             },
231             );
232              
233             $self->set_primary_key(qw{foo_id bar_id});
234              
235             __PACKAGE__->belongs_to( foo => 'MyApp::Schema::Result::Foo' 'foo_id');
236             __PACKAGE__->belongs_to( bar => 'MyApp::Schema::Result::Bar' 'bar_id');
237              
238             or with L<DBIx::Class::Candy>:
239              
240             package MyApp::Schema::Result::Foo_Bar;
241              
242             use DBIx::Class::Candy -components => ['Helper::Row::JoinTable'];
243              
244             join_table {
245             left_class => 'Foo',
246             left_method => 'foo',
247             right_class => 'Bar',
248             right_method => 'bar',
249             };
250              
251             =head1 METHODS
252              
253             All the methods take a configuration hashref that looks like the following:
254              
255             {
256             left_class => 'Foo',
257             left_method => 'foo', # see NOTE
258             left_method_plural => 'foos', # see NOTE, not required, used for
259             # many_to_many rel name in right_class
260             # which is not generated by default
261             right_class => 'Bar',
262             right_method => 'bar', # see NOTE
263             right_method_plural => 'bars', # see NOTE, not required, used for
264             # many_to_many rel name in left_class
265             # which is not generated by default
266             namespace => 'MyApp', # default is guessed via *::Foo
267             self_method => 'foobars', # not required, used for setting the name of the
268             # join table's relationship in a has_many
269             # which is not generated by default
270             }
271              
272             =head2 join_table
273              
274             This is the method that you probably want. It will set your table, add
275             columns, set the primary key, and set up the relationships.
276              
277             =head2 add_join_columns
278              
279             Adds two non-nullable integer fields named C<"${left_method}_id"> and
280             C<"${right_method}_id"> respectively.
281              
282             =head2 generate_has_manys
283              
284             Installs methods into C<left_class> and C<right_class> to get to the join table.
285             The methods will be named what's passed into the configuration hashref as
286             C<self_method>.
287              
288             =head2 generate_many_to_manys
289              
290             Installs many_to_many methods into C<left_class> and C<right_class>. The
291             methods will be named what's passed into the configuration hashref as
292             C<left_method_plural> for the C<right_class> and C<right_method_plural> for the
293             C<left_class>.
294              
295             =head2 generate_primary_key
296              
297             Sets C<"${left_method}_id"> and C<"${right_method}_id"> to be the primary key.
298              
299             =head2 generate_relationships
300              
301             This adds relationships to C<"${namespace}::Schema::Result::$left_class"> and
302             C<"${namespace}::Schema::Result::$left_class"> respectively.
303              
304             =head2 set_table
305              
306             This method sets the table to "${left_class}_${right_class}".
307              
308             =head1 CANDY EXPORTS
309              
310             If used in conjunction with L<DBIx::Class::Candy> this component will export:
311              
312             =over
313              
314             =item join_table
315              
316             =item generate_primary_key
317              
318             =item generate_has_manys
319              
320             =item generate_many_to_manys
321              
322             =item generate_relationships
323              
324             =item set_table
325              
326             =item add_join_columns
327              
328             =back
329              
330             =head2 NOTE
331              
332             This module uses (an internal fork of) L<String::CamelCase> to default the
333             method names and uses L<Lingua::EN::Inflect> for pluralization.
334              
335             =head1 CHANGES BETWEEN RELEASES
336              
337             =head2 Changes since 0.*
338              
339             Originally this module would use
340              
341             data_type => 'integer',
342             is_nullable => 0,
343             is_numeric => 1,
344              
345             for all joining columns. It now infers C<data_type>, C<is_nullable>,
346             C<is_numeric>, and C<extra> from the foreign tables.
347              
348             =head1 AUTHOR
349              
350             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
351              
352             =head1 COPYRIGHT AND LICENSE
353              
354             This software is copyright (c) 2019 by Arthur Axel "fREW" Schmidt.
355              
356             This is free software; you can redistribute it and/or modify it under
357             the same terms as the Perl 5 programming language system itself.
358              
359             =cut