cfd5960ce166d4d7d0c376658b589dd13aad7d13
[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.000001'; # 0.0.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     my $cond_ref = ref($cond);
18     die "Condition needs to be [ \\\@args, \$code ], not ${cond_ref}"
19       unless $cond_ref eq 'ARRAY';
20   }
21   my ($args, $code) = @$cond;
22   my $store = $class->$STORE({
23     %{$class->$STORE||{}},
24     $rel => { params => {}, args => $args },
25   })->{$rel};
26   my $wrapped_code = sub {
27     my $params = $store->{params};
28     my @missing = grep !exists $params->{$_}, @$args;
29     die "Attempted to use parameterized rel ${rel} for ${class} without"
30         ." passing parameters ".join(', ', @missing) if @missing;
31     local *_ = $params;
32     &$code;
33   };
34   $class->has_many($rel, $f_source, $wrapped_code, $attrs);
35   return; # no, you are not going to accidentally rely on a return value
36 }
37
38 1;
39
40 =head1 NAME
41
42 DBIx::Class::ParameterizedJoinHack - Parameterized Relationship Joins
43
44 =head1 SYNOPSIS
45
46     #
47     #   The Result class we want to allow to join with a dynamic
48     #   condition.
49     #
50     package MySchema::Result::Person;
51     use base qw(DBIx::Class::Core);
52
53     __PACKAGE__->load_components(qw(ParameterizedJoinHack));
54     __PACKAGE__->table('person');
55     __PACKAGE__->add_columns(
56         id => {
57             data_type => 'integer',
58             is_nullable => 0,
59             is_auto_increment => 1,
60         },
61         name => {
62             data_type => 'text',
63             is_nullable => 0,
64         }
65     );
66
67     ...
68
69     __PACKAGE__->parameterized_has_many(
70         priority_tasks => 'MySchema::Result::Task',
71         [['min_priority'] => sub {
72             my $args = shift;
73             return +{
74                 "$args->{foreign_alias}.owner_id" => {
75                     -ident => "$args->{self_alias}.id",
76                 },
77                 "$args->{foreign_alias}.priority" => {
78                     '>=' => $_{min_priority},
79                 },
80             };
81         }],
82     );
83
84     1;
85
86     #
87     #   The ResultSet class belonging to your Result
88     #
89     package MySchema::ResultSet::Person;
90     use base qw(DBIx::Class::ResultSet);
91
92     __PACKAGE__->load_components(qw(ResultSet::ParameterizedJoinHack));
93
94     1;
95
96     #
97     #   A Result class to join against.
98     #
99     package MySchema::Result::Task;
100     use base qw(DBIx::Class::Core);
101     
102     __PACKAGE__->table('task');
103     __PACKAGE__->add_columns(
104         id => {
105             data_type => 'integer',
106             is_nullable => 0,
107             is_auto_increment => 1,
108         },
109         owner_id => {
110             data_type => 'integer',
111             is_nullable => 0,
112         },
113         priority => {
114             data_type => 'integer',
115             is_nullable => 0,
116         },
117     );
118
119     ...
120
121     1;
122
123     #
124     #   Using the parameterized join.
125     #
126     my @urgent = MySchema
127         ->connect(...)
128         ->resultset('Person')
129         ->with_parameterized_join(
130             priority_tasks => {
131                 min_priority => 300,
132             },
133         )
134         ->all;
135
136 =head1 DESCRIPTION
137
138 This L<DBIx::Class> component allows to declare dynamically parameterized
139 has-many relationships.
140
141 Add the component to your Result class as usual:
142
143     __PACKAGE__->load_components(qw( ParameterizedJoinHack ));
144
145 See L<parameterized_has_many> for details on declaring relations.
146
147 See L<DBIx::Class::ResultSet::ParameterizedJoinHack> for ResultSet usage.
148
149 =head1 METHODS
150
151 =head2 parameterized_has_many
152
153     __PACKAGE__->parameterized_has_many(
154         $relation_name,
155         $foreign_source,
156         [\@join_arg_names, \&join_builder],
157         $attrs,
158     );
159
160 The C<$relation_name>, C<$foreign_source>, and C<$attrs> are passed
161 through to C<has_many> as usual. The third argument is an array reference
162 containing an (array reference) list of argument names and a code
163 reference used to build the join conditions.
164
165 The code reference will be called with the same arguments as if it had
166 been passed to C<has_many> directly, but the global C<%_> hash will
167 contain the named arguments for the join.
168
169 See the L</SYNOPSIS> for an example of a definition.
170
171 =head1 SPONSORS
172
173 Development of this module was sponsored by
174
175 =over
176
177 =item * Ctrl O L<http://ctrlo.com>
178
179 =back
180
181 =head1 AUTHOR
182
183  Matt S. Trout <mst@shadowcat.co.uk>
184
185 =head1 CONTRIBUTORS
186
187 None yet.
188
189 =head1 COPYRIGHT
190
191 Copyright (c) 2015 the DBIx::Class::ParameterizedJoinHack L</AUTHOR> and L</CONTRIBUTORS>
192 as listed above.
193
194 =head1 LICENSE
195
196 This library is free software and may be distributed under the same terms
197 as perl itself.