Release commit for 0.002001
[dbsrgits/DBIx-Class-ParameterizedJoinHack.git] / lib / DBIx / Class / ParameterizedJoinHack.pm
1 package DBIx::Class::ParameterizedJoinHack;
2
3 use strict;
4 use warnings;
5 use base qw(DBIx::Class);
6
7 our $VERSION = '0.002001'; # 0.2.1
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) = @_;
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
22   {
23     my $cond_ref = ref($cond);
24     $cond_ref = 'non-reference value'
25       unless $cond_ref;
26     die "Condition needs to be [ \\\@args, \&code ], not ${cond_ref}"
27       unless $cond_ref eq 'ARRAY';
28   }
29   my ($args, $code) = @$cond;
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
44   my $store = $class->$STORE({
45     %{$class->$STORE||{}},
46     $rel => { params => {}, args => $args },
47   })->{$rel};
48
49   my $wrapped_code = sub {
50     my $params = $store->{params};
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   };
57
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
160 =head1 WARNING
161
162 This module uses L<DBIx::Class> internals and may break at any time.
163
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
173 See L</parameterized_has_many> for details on declaring relations.
174
175 See L<DBIx::Class::ResultSet::ParameterizedJoinHack> for ResultSet usage.
176
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
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
219  Robert Sedlacek <r.sedlacek@shadowcat.co.uk>
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.