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.035000';
3             # ABSTRACT: Easily set up join tables with DBIx::Class
4              
5 56     56   7006651 use strict;
  56         265  
  56         1761  
6 56     56   306 use warnings;
  56         146  
  56         1623  
7              
8 56     56   324 use parent 'DBIx::Class::Row';
  56         134  
  56         379  
9              
10 56     56   18867 use DBIx::Class::Helpers::Util 'get_namespace_parts';
  56         138  
  56         1738  
11 56     56   51807 use Lingua::EN::Inflect ();
  56         1676225  
  56         3207  
12 56     56   570 use DBIx::Class::Candy::Exports;
  56         129  
  56         2149  
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 224     224   600 my $self = shift;
38 224 50       643 my $original = shift or return;
39 224         1359 return join q{_}, split /\s+/,
40             Lingua::EN::Inflect::PL(join q{ }, split /_/, $original);
41             }
42              
43             sub _defaults {
44 448     448   2578 my ($self, $params) = @_;
45              
46 448   66     2034 $params->{namespace} ||= [ get_namespace_parts($self) ]->[0];
47 448   66     1659 $params->{left_method} ||= $decamelize->($params->{left_class});
48 448   33     1277 $params->{right_method} ||= $decamelize->($params->{right_class});
49 448   66     1339 $params->{self_method} ||= $decamelize->($self);
50 448   66     1556 $params->{left_method_plural} ||= $self->_pluralize($params->{left_method});
51 448   66     143616 $params->{right_method_plural} ||= $self->_pluralize($params->{right_method});
52 448   66     15196 $params->{self_method_plural} ||= $self->_pluralize($params->{self_method});
53              
54 448         160760 return $params;
55             }
56              
57             sub join_table {
58 112     112 1 983740 my ($self, $params) = @_;
59              
60 112         723 $self->set_table($params);
61 112         63696 $self->add_join_columns($params);
62 112         45538 $self->generate_relationships($params);
63 112         59965 $self->generate_primary_key($params);
64             }
65              
66             sub generate_primary_key {
67 112     112 1 362 my ($self, $params) = @_;
68              
69 112         471 $self->_defaults($params);
70 112         5206 $self->set_primary_key("$params->{left_method}_id", "$params->{right_method}_id");
71             }
72              
73             sub generate_has_manys {
74 56     56 1 7425 my ($self, $params) = @_;
75              
76 56         481 $params = $self->_defaults($params);
77             "$params->{namespace}::$params->{left_class}"->has_many(
78             $params->{self_method} =>
79 56         948 $self,
80             "$params->{left_method}_id"
81             );
82              
83             "$params->{namespace}::$params->{right_class}"->has_many(
84             $params->{self_method} =>
85 56         33168 $self,
86             "$params->{right_method}_id"
87             );
88             }
89              
90             sub generate_many_to_manys {
91 56     56 1 31854 my ($self, $params) = @_;
92 56         344 $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 56         1107 );
99              
100             "$params->{namespace}::$params->{right_class}"->many_to_many(
101             $params->{left_method_plural} =>
102             $params->{self_method},
103             $params->{left_method}
104 56         9222 );
105             }
106              
107             sub generate_relationships {
108 112     112 1 371 my ($self, $params) = @_;
109              
110 112         507 $params = $self->_defaults($params);
111             $self->belongs_to(
112             $params->{left_method} =>
113 112         1437 "$params->{namespace}::$params->{left_class}",
114             "$params->{left_method}_id"
115             );
116             $self->belongs_to(
117             $params->{right_method} =>
118 112         85034 "$params->{namespace}::$params->{right_class}",
119             "$params->{right_method}_id"
120             );
121             }
122              
123             sub set_table {
124 112     112 1 366 my ($self, $params) = @_;
125              
126 112         1609 $self->table("$params->{left_class}_$params->{right_class}");
127             }
128              
129             sub _add_join_column {
130 224     224   684 my ($self, $params) = @_;
131              
132 224         500 my $class = $params->{class};
133 224         523 my $method = $params->{method};
134              
135 224         788 my $default = {
136             data_type => 'integer',
137             is_nullable => 0,
138             is_numeric => 1,
139             };
140              
141 224         1716 $self->ensure_class_loaded($class);
142 224         203652 my @datas = qw{is_nullable extra data_type size is_numeric};
143              
144             my @class_column_info = (
145             map {
146 224         7660 my $info = $class->column_info($_);
  224         21332  
147 224         19105 my $result = {};
148 224         451 my $defined = undef;
149 224         579 for (@datas) {
150 1120 100       2708 if (defined $info->{$_}) {
151 336         569 $defined = 1;
152 336         876 $result->{$_} = $info->{$_};
153             }
154             }
155 224 50       620 $result = $default unless $defined;
156 224         758 $result;
157             } $class->primary_columns
158             );
159              
160 224 50       750 if (@class_column_info == 1) {
161 224         2668 $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 112     112 1 331 my ($self, $params) = @_;
177              
178 112         531 $params = $self->_defaults($params);
179              
180 112         451 my $l_class = "$params->{namespace}::$params->{left_class}";
181 112         363 my $r_class = "$params->{namespace}::$params->{right_class}";
182              
183             $self->_add_join_column({
184             class => $l_class,
185             method => $params->{left_method}
186 112         2320 });
187              
188             $self->_add_join_column({
189             class => $r_class,
190             method => $params->{right_method}
191 112         46285 });
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) 2020 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