More reshuffling of the m2m helper code - no functional changes intended
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Relationship / ManyToMany.pm
1 package # hide from PAUSE
2     DBIx::Class::Relationship::ManyToMany;
3
4 use strict;
5 use warnings;
6
7 use DBIx::Class::Carp;
8 use Sub::Name 'subname';
9 use Scalar::Util 'blessed';
10 use DBIx::Class::_Util 'fail_on_internal_wantarray';
11 use namespace::clean;
12
13 our %_pod_inherit_config =
14   (
15    class_map => { 'DBIx::Class::Relationship::ManyToMany' => 'DBIx::Class::Relationship' }
16   );
17
18 sub many_to_many {
19   my ($class, $meth, $rel, $f_rel, $rel_attrs) = @_;
20
21   $class->throw_exception(
22     "missing relation in many-to-many"
23   ) unless $rel;
24
25   $class->throw_exception(
26     "missing foreign relation in many-to-many"
27   ) unless $f_rel;
28
29   {
30     no strict 'refs';
31     no warnings 'redefine';
32
33     my $add_meth = "add_to_${meth}";
34     my $remove_meth = "remove_from_${meth}";
35     my $set_meth = "set_${meth}";
36     my $rs_meth = "${meth}_rs";
37
38     for ($add_meth, $remove_meth, $set_meth, $rs_meth) {
39       if ( $class->can ($_) ) {
40         carp (<<"EOW") unless $ENV{DBIC_OVERWRITE_HELPER_METHODS_OK};
41
42 ***************************************************************************
43 The many-to-many relationship '$meth' is trying to create a utility method
44 called $_.
45 This will completely overwrite one such already existing method on class
46 $class.
47
48 You almost certainly want to rename your method or the many-to-many
49 relationship, as the functionality of the original method will not be
50 accessible anymore.
51
52 To disable this warning set to a true value the environment variable
53 DBIC_OVERWRITE_HELPER_METHODS_OK
54
55 ***************************************************************************
56 EOW
57       }
58     }
59
60     $rel_attrs->{alias} ||= $f_rel;
61
62
63     my $rs_meth_name = join '::', $class, $rs_meth;
64     *$rs_meth_name = subname $rs_meth_name, sub {
65
66       # this little horror is there replicating a deprecation from
67       # within search_rs() itself
68       shift->search_related_rs($rel)
69             ->search_related_rs(
70               $f_rel,
71               undef,
72               ( @_ > 1 and ref $_[-1] eq 'HASH' )
73                 ? { %$rel_attrs, %{ pop @_ } }
74                 : $rel_attrs
75             )->search_rs(@_)
76       ;
77
78     };
79
80
81     my $meth_name = join '::', $class, $meth;
82     *$meth_name = subname $meth_name, sub {
83
84       DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = fail_on_internal_wantarray;
85
86       my $rs = shift->$rs_meth( @_ );
87
88       wantarray ? $rs->all : $rs;
89
90     };
91
92
93     my $add_meth_name = join '::', $class, $add_meth;
94     *$add_meth_name = subname $add_meth_name, sub {
95
96       ( @_ >= 2 and @_ <= 3 ) or $_[0]->throw_exception(
97         "'$add_meth' expects an object or hashref to link to, and an optional hashref of link data"
98       );
99
100       $_[0]->throw_exception(
101         "The optional link data supplied to '$add_meth' is not a hashref (it was previously ignored)"
102       ) if $_[2] and ref $_[2] ne 'HASH';
103
104       my( $self, $far_obj ) = @_;
105
106       my $guard;
107
108       # the API needs is always expected to return the far object, possibly
109       # creating it in the process
110       if( not defined blessed $far_obj ) {
111
112         $guard = $self->result_source->schema->storage->txn_scope_guard;
113
114         # reify the hash into an actual object
115         $far_obj = $self->result_source
116                          ->related_source( $rel )
117                           ->related_source( $f_rel )
118                            ->resultset
119                             ->search_rs( undef, $rel_attrs )
120                              ->find_or_create( $far_obj );
121       }
122
123       my $link = $self->new_related(
124         $rel,
125         $_[2] || {},
126       );
127
128       $link->set_from_related( $f_rel, $far_obj );
129
130       $link->insert();
131
132       $guard->commit if $guard;
133
134       $far_obj;
135     };
136
137
138     my $set_meth_name = join '::', $class, $set_meth;
139     *$set_meth_name = subname $set_meth_name, sub {
140
141       my $self = shift;
142
143       my $set_to = ( ref $_[0] eq 'ARRAY' )
144         ? ( shift @_ )
145         : do {
146           carp_unique(
147             "Calling '$set_meth' with a list of items to link to is deprecated, use an arrayref instead"
148           );
149
150           # gobble up everything from @_ into a new arrayref
151           [ splice @_ ]
152         }
153       ;
154
155       # make sure folks are not invoking a bizarre mix of deprecated and curent syntax
156       $self->throw_exception(
157         "'$set_meth' expects an arrayref of objects or hashrefs to link to, and an optional hashref of link data"
158       ) if (
159         @_ > 1
160           or
161         ( @_ and ref $_[0] ne 'HASH' )
162       );
163
164       my $guard;
165
166       # there will only be a single delete() op, unless we have what to set to
167       $guard = $self->result_source->schema->storage->txn_scope_guard
168         if @$set_to;
169
170       # if there is a where clause in the attributes, ensure we only delete
171       # rows that are within the where restriction
172       $self->search_related(
173         $rel,
174         ( $rel_attrs->{where}
175           ? ( $rel_attrs->{where}, { join => $f_rel } )
176           : ()
177         )
178       )->delete;
179
180       # add in the set rel objects
181       $self->$add_meth(
182         $_,
183         @_, # at this point @_ is either empty or contains a lone link-data hash
184       ) for @$set_to;
185
186       $guard->commit if $guard;
187     };
188
189
190     my $remove_meth_name = join '::', $class, $remove_meth;
191     *$remove_meth_name = subname $remove_meth_name, sub {
192
193       $_[0]->throw_exception("'$remove_meth' expects an object")
194         unless defined blessed $_[1];
195
196       $_[0]->search_related_rs( $rel )
197             ->search_rs( $_[1]->ident_condition( $f_rel ), { join => $f_rel } )
198              ->delete;
199     };
200
201   }
202 }
203
204 1;