Commit | Line | Data |
638c1533 |
1 | package DBIx::Class::ParameterizedJoinHack; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | use base qw(DBIx::Class); |
6 | |
750d6b29 |
7 | our $VERSION = '0.002001'; # 0.2.1 |
638c1533 |
8 | $VERSION = eval $VERSION; |
9 | |
10 | our $STORE = '_parameterized_join_hack_meta_info'; |
11 | |
12 | __PACKAGE__->mk_group_accessors(inherited => $STORE); |
13 | |
14 | sub parameterized_has_many { |
15 | my ($class, $rel, $f_source, $cond, $attrs) = @_; |
93b74da6 |
16 | |
17 | die "Missing relation name for parameterized_has_many" |
18 | unless defined $rel; |
19 | die "Missing foreign source" |
20 | unless defined $f_source; |
21 | |
638c1533 |
22 | { |
23 | my $cond_ref = ref($cond); |
93b74da6 |
24 | $cond_ref = 'non-reference value' |
25 | unless $cond_ref; |
26 | die "Condition needs to be [ \\\@args, \&code ], not ${cond_ref}" |
638c1533 |
27 | unless $cond_ref eq 'ARRAY'; |
28 | } |
29 | my ($args, $code) = @$cond; |
93b74da6 |
30 | |
31 | { |
32 | my $arg_ref = ref($cond->[0]); |
33 | $arg_ref = 'non-reference value' |
34 | unless $arg_ref; |
35 | die "Arguments must be declared as array ref of names, not ${arg_ref}" |
36 | unless $arg_ref eq 'ARRAY'; |
37 | my $code_ref = ref($cond->[1]); |
38 | $code_ref = 'non-reference value' |
39 | unless $code_ref; |
40 | die "Condition builder must be declared as code ref, not ${code_ref}" |
41 | unless $code_ref eq 'CODE'; |
42 | } |
43 | |
638c1533 |
44 | my $store = $class->$STORE({ |
45 | %{$class->$STORE||{}}, |
46 | $rel => { params => {}, args => $args }, |
ddc15dd9 |
47 | })->{$rel}; |
93b74da6 |
48 | |
638c1533 |
49 | my $wrapped_code = sub { |
ddc15dd9 |
50 | my $params = $store->{params}; |
638c1533 |
51 | my @missing = grep !exists $params->{$_}, @$args; |
52 | die "Attempted to use parameterized rel ${rel} for ${class} without" |
53 | ." passing parameters ".join(', ', @missing) if @missing; |
54 | local *_ = $params; |
55 | &$code; |
56 | }; |
93b74da6 |
57 | |
638c1533 |
58 | $class->has_many($rel, $f_source, $wrapped_code, $attrs); |
59 | return; # no, you are not going to accidentally rely on a return value |
60 | } |
61 | |
62 | 1; |
63 | |
64 | =head1 NAME |
65 | |
66 | DBIx::Class::ParameterizedJoinHack - Parameterized Relationship Joins |
67 | |
68 | =head1 SYNOPSIS |
69 | |
70 | # |
71 | # The Result class we want to allow to join with a dynamic |
72 | # condition. |
73 | # |
74 | package MySchema::Result::Person; |
75 | use base qw(DBIx::Class::Core); |
76 | |
77 | __PACKAGE__->load_components(qw(ParameterizedJoinHack)); |
78 | __PACKAGE__->table('person'); |
79 | __PACKAGE__->add_columns( |
80 | id => { |
81 | data_type => 'integer', |
82 | is_nullable => 0, |
83 | is_auto_increment => 1, |
84 | }, |
85 | name => { |
86 | data_type => 'text', |
87 | is_nullable => 0, |
88 | } |
89 | ); |
90 | |
91 | ... |
92 | |
93 | __PACKAGE__->parameterized_has_many( |
94 | priority_tasks => 'MySchema::Result::Task', |
95 | [['min_priority'] => sub { |
96 | my $args = shift; |
97 | return +{ |
98 | "$args->{foreign_alias}.owner_id" => { |
99 | -ident => "$args->{self_alias}.id", |
100 | }, |
101 | "$args->{foreign_alias}.priority" => { |
102 | '>=' => $_{min_priority}, |
103 | }, |
104 | }; |
105 | }], |
106 | ); |
107 | |
108 | 1; |
109 | |
110 | # |
111 | # The ResultSet class belonging to your Result |
112 | # |
113 | package MySchema::ResultSet::Person; |
114 | use base qw(DBIx::Class::ResultSet); |
115 | |
116 | __PACKAGE__->load_components(qw(ResultSet::ParameterizedJoinHack)); |
117 | |
118 | 1; |
119 | |
120 | # |
121 | # A Result class to join against. |
122 | # |
123 | package MySchema::Result::Task; |
124 | use base qw(DBIx::Class::Core); |
125 | |
126 | __PACKAGE__->table('task'); |
127 | __PACKAGE__->add_columns( |
128 | id => { |
129 | data_type => 'integer', |
130 | is_nullable => 0, |
131 | is_auto_increment => 1, |
132 | }, |
133 | owner_id => { |
134 | data_type => 'integer', |
135 | is_nullable => 0, |
136 | }, |
137 | priority => { |
138 | data_type => 'integer', |
139 | is_nullable => 0, |
140 | }, |
141 | ); |
142 | |
143 | ... |
144 | |
145 | 1; |
146 | |
147 | # |
148 | # Using the parameterized join. |
149 | # |
150 | my @urgent = MySchema |
151 | ->connect(...) |
152 | ->resultset('Person') |
153 | ->with_parameterized_join( |
154 | priority_tasks => { |
155 | min_priority => 300, |
156 | }, |
157 | ) |
158 | ->all; |
159 | |
5d049dcb |
160 | =head1 WARNING |
161 | |
162 | This module uses L<DBIx::Class> internals and may break at any time. |
163 | |
638c1533 |
164 | =head1 DESCRIPTION |
165 | |
166 | This L<DBIx::Class> component allows to declare dynamically parameterized |
167 | has-many relationships. |
168 | |
169 | Add the component to your Result class as usual: |
170 | |
171 | __PACKAGE__->load_components(qw( ParameterizedJoinHack )); |
172 | |
5d049dcb |
173 | See L</parameterized_has_many> for details on declaring relations. |
638c1533 |
174 | |
175 | See L<DBIx::Class::ResultSet::ParameterizedJoinHack> for ResultSet usage. |
176 | |
5d049dcb |
177 | B<Note:> Currently only L</parameterized_has_many> is implemented, since |
178 | it is the most requested use-case. However, adding support for other |
179 | relationship types is possible if a use-case is found. |
180 | |
638c1533 |
181 | =head1 METHODS |
182 | |
183 | =head2 parameterized_has_many |
184 | |
185 | __PACKAGE__->parameterized_has_many( |
186 | $relation_name, |
187 | $foreign_source, |
188 | [\@join_arg_names, \&join_builder], |
189 | $attrs, |
190 | ); |
191 | |
192 | The C<$relation_name>, C<$foreign_source>, and C<$attrs> are passed |
193 | through to C<has_many> as usual. The third argument is an array reference |
194 | containing an (array reference) list of argument names and a code |
195 | reference used to build the join conditions. |
196 | |
197 | The code reference will be called with the same arguments as if it had |
198 | been passed to C<has_many> directly, but the global C<%_> hash will |
199 | contain the named arguments for the join. |
200 | |
201 | See the L</SYNOPSIS> for an example of a definition. |
202 | |
203 | =head1 SPONSORS |
204 | |
205 | Development of this module was sponsored by |
206 | |
207 | =over |
208 | |
209 | =item * Ctrl O L<http://ctrlo.com> |
210 | |
211 | =back |
212 | |
213 | =head1 AUTHOR |
214 | |
215 | Matt S. Trout <mst@shadowcat.co.uk> |
216 | |
217 | =head1 CONTRIBUTORS |
218 | |
0dabc6b5 |
219 | Robert Sedlacek <r.sedlacek@shadowcat.co.uk> |
638c1533 |
220 | |
221 | =head1 COPYRIGHT |
222 | |
223 | Copyright (c) 2015 the DBIx::Class::ParameterizedJoinHack L</AUTHOR> and L</CONTRIBUTORS> |
224 | as listed above. |
225 | |
226 | =head1 LICENSE |
227 | |
228 | This library is free software and may be distributed under the same terms |
229 | as perl itself. |