File Coverage

blib/lib/DBIx/Class/ResultSet/RecursiveUpdate.pm
Criterion Covered Total %
statement 397 432 91.9
branch 158 198 79.8
condition 97 139 69.7
subroutine 33 36 91.6
pod 1 1 100.0
total 686 806 85.1


line stmt bran cond sub pod time code
1 10     10   1942895 use strict;
  10         86  
  10         454  
2 10     10   65 use warnings;
  10         20  
  10         544  
3              
4             package DBIx::Class::ResultSet::RecursiveUpdate;
5             $DBIx::Class::ResultSet::RecursiveUpdate::VERSION = '0.42';
6             # ABSTRACT: like update_or_create - but recursive
7              
8 10     10   73 use base qw(DBIx::Class::ResultSet);
  10         52  
  10         7311  
9              
10             sub recursive_update {
11 75     75 1 7481665 my ( $self, $updates, $attrs ) = @_;
12              
13 75         467 my $fixed_fields;
14             my $unknown_params_ok;
15 75         0 my $m2m_force_set_rel;
16              
17             # 0.21+ api
18 75 100 100     662 if ( defined $attrs && ref $attrs eq 'HASH' ) {
    100 66        
19 7         23 $fixed_fields = $attrs->{fixed_fields};
20 7         20 $unknown_params_ok = $attrs->{unknown_params_ok};
21 7         20 $m2m_force_set_rel = $attrs->{m2m_force_set_rel};
22             }
23              
24             # pre 0.21 api
25             elsif ( defined $attrs && ref $attrs eq 'ARRAY' ) {
26 1         3 $fixed_fields = $attrs;
27             }
28              
29 75         360 return DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update(
30             resultset => $self,
31             updates => $updates,
32             fixed_fields => $fixed_fields,
33             unknown_params_ok => $unknown_params_ok,
34             m2m_force_set_rel => $m2m_force_set_rel,
35             );
36             }
37              
38             package DBIx::Class::ResultSet::RecursiveUpdate::Functions;
39             $DBIx::Class::ResultSet::RecursiveUpdate::Functions::VERSION = '0.42';
40 10     10   359986 use Carp::Clan qw/^DBIx::Class|^HTML::FormHandler|^Try::Tiny/;
  10         18666  
  10         79  
41 10     10   1369 use Scalar::Util qw( blessed );
  10         27  
  10         606  
42 10     10   5506 use List::MoreUtils qw/ any all none /;
  10         120481  
  10         74  
43 10     10   12870 use Try::Tiny;
  10         31  
  10         614  
44 10     10   4794 use Data::Dumper::Concise;
  10         73422  
  10         794  
45              
46 10     10   86 use constant DEBUG => 0;
  10         33  
  10         21715  
47              
48             sub recursive_update {
49 277     277   1482440 my %params = @_;
50             my ( $self, $updates, $fixed_fields, $object, $resolved, $if_not_submitted,
51             $unknown_params_ok, $m2m_force_set_rel )
52             = @params{
53 277         1306 qw/resultset updates fixed_fields object resolved if_not_submitted unknown_params_ok m2m_force_set_rel/
54             };
55 277   100     1194 $resolved ||= {};
56 277         2106 $ENV{DBIC_NULLABLE_KEY_NOWARN} = 1;
57              
58             local $SIG{__WARN__} = sub {
59 0     0   0 my $level = 0;
60 0         0 my $i = 0;
61 0         0 while (1) {
62 0 0       0 my $sub = (caller($i++))[3] or last;
63 0 0       0 $level++ if $sub =~ /(?:recursive_update|_update_relation)$/;
64             }
65 0         0 warn map { s/^/" "x$level/egmr } @_;
  0         0  
  0         0  
66 277         579 } if DEBUG;
67              
68 277         869 my $source = $self->result_source;
69              
70 277 50       797 croak "first parameter needs to be defined"
71             unless defined $updates;
72              
73 277 50       880 croak "first parameter needs to be a hashref"
74             unless ref($updates) eq 'HASH';
75              
76 277 50 66     894 croak 'fixed fields needs to be an arrayref'
77             if defined $fixed_fields && ref $fixed_fields ne 'ARRAY';
78              
79 277         471 DEBUG and warn "recursive_update: " . $source->name . "\n";
80 277 100       743 DEBUG and warn "object passed, skipping find" .
81             (defined $object->id
82             ? " (id " . $object->id . ")\n"
83             : "\n")
84             if defined $object;
85              
86             # always warn about additional parameters if storage debugging is enabled
87 277 100       1055 $unknown_params_ok = 0
88             if $source->storage->debug;
89              
90 277 50 33     14218 if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
91 0         0 return $updates;
92             }
93              
94 277         1013 my @pks = $source->primary_columns;
95 277         2106 my %pk_kvs;
96 277         681 for my $colname (@pks) {
97 369 100 66     1514 if (exists $updates->{$colname} && defined $updates->{$colname}) {
98 177         499 $pk_kvs{$colname} = $updates->{$colname};
99 177         438 next;
100             }
101             $pk_kvs{$colname} = $resolved->{$colname}
102 192 100 66     1365 if exists $resolved->{$colname} && defined $resolved->{$colname};
103             }
104             # support the special case where a method on the related row
105             # populates one or more primary key columns and we don't have
106             # all primary key values already
107             # see DBSchema::Result::DVD relationship keysbymethod
108 277         475 DEBUG and warn "pk columns so far: " . join (', ',
109             sort keys %pk_kvs) . "\n";
110             my @non_pk_columns = grep {
111 277         1307 my $colname = $_;
  493         1103  
112 493     357   3103 none { $colname eq $_ } keys %pk_kvs
  357         1718  
113             }
114             sort keys %$updates;
115 277         592 DEBUG and warn "non-pk columns: " . join (', ',
116             @non_pk_columns) . "\n";
117 277 100 66     1267 if ( scalar keys %pk_kvs != scalar @pks && @non_pk_columns) {
118 101         206 DEBUG and warn "not all primary keys available, trying " .
119             "object creation\n";
120             # new_result throws exception if non column values are passed
121             # because we want to also support e.g. a BUILDARGS method that
122             # populates primary key columns from an additional value
123             # filter out all relationships
124             my @non_rel_columns = grep {
125 101   100     308 !is_m2m( $self, $_ )
  206         3003  
126             && !$source->has_relationship($_)
127             }
128             sort keys %$updates;
129             my %non_rel_updates = map {
130 101         2843 $_ => $updates->{$_}
  124         477  
131             } @non_rel_columns;
132             # transform columns specified by their accessor name
133 101         324 my %columns_by_accessor = _get_columns_by_accessor($self);
134 101         588 for my $accessor_name (sort keys %columns_by_accessor) {
135 404         784 my $colname = $columns_by_accessor{$accessor_name}->{name};
136 404 100 100     1066 if ($accessor_name ne $colname
137             && exists $non_rel_updates{$accessor_name}) {
138 12         22 DEBUG and warn "renaming column accessor " .
139             "'$accessor_name' to column name '$colname'\n";
140             $non_rel_updates{$colname} = delete
141 12         44 $non_rel_updates{$accessor_name};
142             }
143             }
144 101         249 DEBUG and warn "using all non-rel updates for object " .
145             "construction: " . Dumper(\%non_rel_updates);
146             # the object creation might fail because of non-column and
147             # non-constructor handled parameters which shouldn't break RU
148             try {
149 101     101   6829 my $row = $self->new_result(\%non_rel_updates);
150 98         11523 for my $colname (@pks) {
151             next
152 122 100       457 if exists $pk_kvs{$colname};
153              
154 100 100 66     3083 if ($row->can($colname)
155             && defined $row->$colname) {
156 6         132 DEBUG and warn "missing pk column $colname exists " .
157             "and defined on object\n";
158 6         112 $pk_kvs{$colname} = $row->$colname;
159             }
160             else {
161 94         3535 DEBUG and warn "missing pk column $colname doesn't "
162             . "exist or isn't defined on object, aborting\n";
163 94         416 last;
164             }
165             }
166             }
167             catch {
168 3     3   6778 DEBUG and warn "object construction failed, ignoring:
169             $_\n";
170 101         999 };
171             }
172              
173             # check if row can be found in resultset cache
174 277 100 100     5010 if ( !defined $object && scalar keys %pk_kvs == scalar @pks ) {
175 106         482 my $cached_rows = $self->get_cache;
176 106 100       642 if (defined $cached_rows) {
177 4         15 DEBUG and warn "find in cache\n";
178 4         27 $object = _get_matching_row(\%pk_kvs, $cached_rows)
179             }
180             }
181              
182 277         1229 $updates = { %$updates, %$resolved };
183              
184 277         893 my %fixed_fields = map { $_ => 1 } @$fixed_fields;
  2         10  
185              
186             # add the resolved columns to the updates hashref
187 277         844 my %all_pks = ( %pk_kvs, %fixed_fields );
188              
189 277 100 100     1117 if ( !defined $object && scalar keys %all_pks == scalar @pks) {
190 102         202 DEBUG and warn "find by pk\n";
191 102         586 $object = $self->find( \%all_pks, { key => 'primary' } );
192             }
193              
194 277 100       299269 unless (defined $object) {
195 67         140 DEBUG and warn "create new row\n";
196 67         313 $object = $self->new_result( {} );
197             }
198              
199             # direct column accessors
200 277         7032 my %columns;
201              
202             # relations that that should be done before the row is inserted into the
203             # database like belongs_to
204             my %pre_updates;
205              
206             # relations that that should be done after the row is inserted into the
207             # database like has_many, might_have and has_one
208 277         0 my %post_updates;
209 277         0 my %other_methods;
210 277         0 my %m2m_accessors;
211 277         842 my %columns_by_accessor = _get_columns_by_accessor($self);
212              
213             # this section determines to what each key/value pair maps to,
214             # column or relationship
215 277         1397 for my $name ( sort keys %$updates ) {
216             DEBUG and warn "updating $name to "
217 615         1515 . ($updates->{$name} // '[undef]') . "\n";
218             # columns
219 615 100 100     2314 if ( exists $columns_by_accessor{$name} &&
      100        
220             !( $source->has_relationship($name) && ref( $updates->{$name} ) ) ) {
221 443         3534 $columns{$name} = $updates->{$name};
222 443         1015 next;
223             }
224              
225             # relationships
226 172 100       907 if ( $source->has_relationship($name) ) {
227 130 100       879 if ( _master_relation_cond( $self, $name ) ) {
228 82         224 $pre_updates{$name} = $updates->{$name};
229 82         222 next;
230             }
231             else {
232 48         182 $post_updates{$name} = $updates->{$name};
233 48         183 next;
234             }
235             }
236              
237             # many-to-many helper accessors
238 42 100       360 if ( is_m2m( $self, $name ) ) {
239 37         1051 DEBUG and warn "is m2m\n";
240             # Transform m2m data into recursive has_many data
241             # if IntrospectableM2M is in use.
242             #
243             # This removes the overhead related to deleting and
244             # re-adding all relationships.
245 37 100 100     878 if ( !$m2m_force_set_rel && $source->result_class->can('_m2m_metadata') ) {
246 27         1376 my $meta = $source->result_class->_m2m_metadata->{$name};
247 27         1417 my $bridge_rel = $meta->{relation};
248 27         88 my $foreign_rel = $meta->{foreign_relation};
249              
250             $post_updates{$bridge_rel} = [
251             map {
252 70         244 { $foreign_rel => $_ }
253 27         82 } @{ $updates->{$name} }
  27         86  
254             ];
255             DEBUG and warn "m2m '$name' transformed to:\n$bridge_rel => " .
256 27         72 Dumper($post_updates{$bridge_rel}) . "\n";
257             }
258             # Fall back to set_$rel if IntrospectableM2M
259             # is not available. (removing and re-adding all relationships)
260             else {
261 10         215 $m2m_accessors{$name} = $updates->{$name};
262             }
263              
264 37         129 next;
265             }
266              
267             # accessors
268 5 100 66     131 if ( $object->can($name) && not $source->has_relationship($name) ) {
269 2         17 $other_methods{$name} = $updates->{$name};
270 2         6 next;
271             }
272              
273             # unknown
274              
275             # don't throw a warning instead of an exception to give users
276             # time to adapt to the new API
277             carp(
278 3 100       26 "No such column, relationship, many-to-many helper accessor or " .
279             "generic accessor '$name' on '" . $source->name . "'"
280             ) unless $unknown_params_ok;
281              
282             }
283              
284             # first update columns and other accessors
285             # so that later related records can be found
286 277         1083 for my $name ( sort keys %columns ) {
287 431         30286 $object->$name( $columns{$name} );
288             }
289 271         28192 for my $name ( sort keys %other_methods ) {
290 2         13 $object->$name( $other_methods{$name} );
291             }
292 271         1124 for my $name ( sort keys %pre_updates ) {
293 79         490 _update_relation( $self, $name, $pre_updates{$name}, $object, $if_not_submitted, 0 );
294             }
295              
296             # $self->_delete_empty_auto_increment($object);
297             # don't allow insert to recurse to related objects
298             # do the recursion ourselves
299             # $object->{_rel_in_storage} = 1;
300             # Update if %other_methods because of possible custom update method
301 267         2131 my $in_storage = $object->in_storage;
302              
303             # preserve related resultsets as DBIx::Class::Row->update clears them
304             # yes, this directly accesses a row attribute, but no API exists and in
305             # the hope to get the recursive_update feature into core DBIx::Class this
306             # is the easiest solution
307 267         625 my $related_resultsets = $object->{related_resultsets};
308              
309 267         446 DEBUG and warn "before update_or_insert\n";
310 267 100 66     1493 $object->update_or_insert if ( $object->is_changed || keys %other_methods );
311 267         1164704 DEBUG and warn "after update_or_insert\n";
312              
313             # restore related resultsets
314 267         761 $object->{related_resultsets} = $related_resultsets;
315              
316             # updating many_to_many
317 267         924 for my $name ( sort keys %m2m_accessors ) {
318 10         20 DEBUG and warn "updating m2m $name\n";
319 10         29 my $value = $m2m_accessors{$name};
320              
321             # TODO: only first pk col is used
322 10         43 my ($pk) = _get_pk_for_related( $self, $name );
323 10         93 my @rows;
324 10         51 my $rel_source = $object->$name->result_source;
325 10         32272 my @updates;
326 10 50 33     258 if ( defined $value && ref $value eq 'ARRAY' ) {
    0 0        
    0          
327 10         29 @updates = @{$value};
  10         38  
328             }
329             elsif ( defined $value && !ref $value ) {
330 0         0 @updates = ($value);
331             }
332             elsif ( defined $value ) {
333 0         0 carp "value of many-to-many rel '$name' must be an arrayref or scalar: $value";
334             }
335 10         36 for my $elem (@updates) {
336 23 50 33     32072 if ( blessed($elem) && $elem->isa('DBIx::Class::Row') ) {
    100          
337 0         0 push @rows, $elem;
338             }
339             elsif ( ref $elem eq 'HASH' ) {
340 5         24 push @rows,
341             recursive_update(
342             resultset => $rel_source->resultset,
343             updates => $elem
344             );
345             }
346             else {
347 18         107 push @rows, $rel_source->resultset->find( { $pk => $elem } );
348             }
349             }
350 10         24488 my $set_meth = 'set_' . $name;
351 10         75 $object->$set_meth( \@rows );
352             }
353 267         354936 for my $name ( sort keys %post_updates ) {
354 67         546 _update_relation( $self, $name, $post_updates{$name}, $object, $if_not_submitted, $in_storage );
355             }
356 262         3284 delete $ENV{DBIC_NULLABLE_KEY_NOWARN};
357 262         3145 return $object;
358             }
359              
360             # returns DBIx::Class::ResultSource::column_info as a hash indexed by column accessor || name
361             sub _get_columns_by_accessor {
362 400     400   1863 my $self = shift;
363 400         1096 my $source = $self->result_source;
364 400         832 my %columns;
365 400         1418 for my $name ( $source->columns ) {
366 1548         7420 my $info = $source->column_info($name);
367 1548   66     21444 $columns{ $info->{accessor} || $name } = { %$info, name => $name };
368             }
369 400         2058 return %columns;
370             }
371              
372             sub _get_matching_row {
373 100     100   278 my ($kvs, $rows) = @_;
374              
375             return
376 100 50       298 unless defined $rows;
377              
378 100 50       327 croak 'key/value need to be a hashref'
379             unless ref $kvs eq 'HASH';
380              
381 100 50       299 croak 'key/value needs to have at least one pair'
382             if keys %$kvs == 0;
383              
384 100 50       313 croak 'rows need to be an arrayref'
385             unless ref $rows eq 'ARRAY';
386              
387 100 50       283 unless ($rows) {
388 0         0 DEBUG and warn "skipping because no rows passed\n";
389 0         0 return;
390             }
391              
392 100         211 my $matching_row;
393              
394             my @matching_rows;
395 100         264 for my $row (@$rows) {
396 10     10   107 no warnings 'uninitialized';
  10         35  
  10         18967  
397             push @matching_rows, $row
398 444     444   2514 if all { $kvs->{$_} eq $row->get_column($_) }
399 239 100       2311 grep { !ref $kvs->{$_} }
  447         1339  
400             sort keys %$kvs;
401             }
402 100 50       939 DEBUG and warn "multiple matching rows: " . scalar @matching_rows . "\n"
403             if @matching_rows > 1;
404 100 100       298 $matching_row = $matching_rows[0]
405             if scalar @matching_rows == 1;
406             DEBUG and warn "matching row found for: " . Dumper($kvs) . " in " .
407 100 100       263 Dumper([map { { $_->get_columns } } @$rows]) . "\n"
408             if defined $matching_row;
409             DEBUG and warn "matching row not found for: " . Dumper($kvs) . " in " .
410 100 100       256 Dumper([map { { $_->get_columns } } @$rows]) . "\n"
411             unless defined $matching_row;
412              
413 100         242 return $matching_row;
414             }
415              
416             # Arguments: $rs, $name, $updates, $row, $if_not_submitted, $row_existed
417             sub _update_relation {
418 146     146   482 my ( $self, $name, $updates, $object, $if_not_submitted, $row_existed ) = @_;
419              
420             # this should never happen because we're checking the paramters passed to
421             # recursive_update, but just to be sure...
422 146 50       3934 $object->throw_exception("No such relationship '$name'")
423             unless $object->has_relationship($name);
424              
425 146         14980 DEBUG and warn "_update_relation: $name\n";
426              
427 146         581 my $info = $object->result_source->relationship_info($name);
428 146         1554 my $attrs = $info->{attrs};
429              
430             # get a related resultset without a condition
431 146         718 my $related_source = $self->related_resultset($name)->result_source;
432 146         141887 my $related_resultset = $related_source->resultset;
433 146 50       46908 $self->throw_exception("result_source must support _resolve_condition")
434             unless $self->result_source->can('_resolve_condition');
435 146         785 my $resolved = $self->result_source->_resolve_condition( $info->{cond}, $name, $object, $name );
436              
437 146 100 66     45127 $resolved = {}
438             if defined $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION &&
439             $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION == $resolved;
440              
441             # This is a hack. I'm not sure that this will handle most
442             # custom code conditions yet. This needs tests.
443 146         371 my @rel_cols;
444 146 50       597 if ( ref $info->{cond} eq 'CODE' ) {
445 0         0 my $new_resolved;
446             # remove 'me.' from keys in returned hashref
447 0         0 while ( my ( $key, $value ) = each %$resolved ) {
448 0         0 $key =~ s/^me\.//;
449 0         0 $new_resolved->{$key} = $value;
450 0         0 push @rel_cols, $key;
451             }
452 0         0 $resolved = $new_resolved;
453             }
454             else {
455 146         321 @rel_cols = sort keys %{ $info->{cond} };
  146         607  
456 146         445 map { s/^foreign\.// } @rel_cols;
  146         805  
457             }
458              
459             # find out if all related columns are nullable
460 146         362 my $all_fks_nullable = 1;
461 146         357 for my $rel_col (@rel_cols) {
462             $all_fks_nullable = 0
463 146 100       752 unless $related_resultset->result_source->column_info($rel_col)->{is_nullable};
464             }
465              
466 146 100       2882 $if_not_submitted = $all_fks_nullable ? 'set_to_null' : 'delete'
    100          
467             unless defined $if_not_submitted;
468              
469             # the only valid datatype for a has_many rels is an arrayref
470 146 100 66     752 if ( $attrs->{accessor} eq 'multi' ) {
    50          
471 59         127 DEBUG and warn "has_many: $name\n";
472              
473             # handle undef like empty arrayref
474 59 100       211 $updates = []
475             unless defined $updates;
476 59 50       263 $self->throw_exception("data for has_many relationship '$name' must be an arrayref")
477             unless ref $updates eq 'ARRAY';
478              
479 59         152 my @updated_objs;
480             my @related_rows;
481             # newly created rows can't have related rows
482 59 100       225 if ($row_existed) {
483 38         887 @related_rows = $object->$name;
484 38         126558 DEBUG and warn "got related rows: " . scalar @related_rows . "\n";
485             }
486 59         234 my $related_result_source = $related_resultset->result_source;
487 59         276 my @pks = $related_result_source->primary_columns;
488              
489 59         548 for my $sub_updates ( @{$updates} ) {
  59         191  
490 116         228 DEBUG and warn "updating related row: " . Dumper($sub_updates)
491             . "\n";
492 116         255 my %pk_kvs;
493             # detect the special case where the primary key of a currently not
494             # related row is passed in the updates hash
495             # let the resolved column values fill any missing primary key
496             # columns but not overwrite them
497 116         319 for my $colname (@pks) {
498 204 100 66     824 if (exists $sub_updates->{$colname}
499             && defined $sub_updates->{$colname}) {
500             # $sub_updates->{$colname} might be a hashref if a
501             # relationship is named the same as a foreign key column
502 69 100       292 if (ref $sub_updates->{$colname} eq 'HASH') {
503 29 50       116 if ($related_source->has_relationship($colname)) {
504 29         238 my $rel_info = $related_source
505             ->relationship_info($colname);
506 29         133 my @rel_cols = sort keys %{ $rel_info->{cond} };
  29         127  
507 29         91 map { s/^foreign\.// } @rel_cols;
  29         184  
508 29 50       107 $self->throw_exception("passing a hashref for " .
509             "a multi-column relationship named the " .
510             "same as a column ('$colname') is not " .
511             "implemented")
512             if scalar @rel_cols != 1;
513 29         66 DEBUG and warn "using '$rel_cols[0]' in hashref " .
514             "for primary key column '$colname'\n";
515             $pk_kvs{$colname} = $sub_updates->{$colname}
516 29         150 ->{$rel_cols[0]};
517             }
518             else {
519 0         0 $self->throw_exception(
520             "data for $colname is a hashref but no " .
521             "relationship with that name exists");
522             }
523             }
524             else {
525 40         115 $pk_kvs{$colname} = $sub_updates->{$colname};
526             }
527 69         208 next;
528             }
529             $pk_kvs{$colname} = $resolved->{$colname}
530             if exists $resolved->{$colname}
531 135 100 66     718 && defined $resolved->{$colname};
532             }
533 116         257 my $related_object;
534              
535             my @missing_pks = grep {
536 116         275 my $pkcolname = $_;
  204         420  
537 204     240   1196 none { $_ eq $pkcolname } keys %pk_kvs;
  240         1024  
538             } @pks;
539              
540             # support the special case where a method on the related row
541             # populates one or more primary key columns and we don't have
542             # all primary key values already
543             # see DBSchema::Result::DVD relationship keysbymethod
544 116         249 DEBUG and warn "pk columns so far: " . join (', ',
545             sort keys %pk_kvs) . "\n";
546 116         212 DEBUG and warn "pk columns missing: " . join (', ',
547             sort @missing_pks) . "\n";
548             my @non_pk_columns = grep {
549 116         480 my $colname = $_;
  143         309  
550 143     135   632 none { $colname eq $_ } keys %pk_kvs
  135         581  
551             }
552             sort keys %$sub_updates;
553 116         270 DEBUG and warn "non-pk columns: " . join (', ',
554             @non_pk_columns) . "\n";
555 116 100 66     543 if ( scalar keys %pk_kvs != scalar @pks && @non_pk_columns) {
556 51         98 DEBUG and warn "not all primary keys available, trying " .
557             "relationships\n";
558             my @rel_names = grep {
559 51         193 $related_result_source->has_relationship($_)
  71         321  
560             } sort keys %$sub_updates;
561              
562 51         353 for my $relname (@rel_names) {
563 34         98 my $rel_info = $related_result_source
564             ->relationship_info($relname);
565             next
566             unless $rel_info->{attrs}->{accessor} eq 'single'
567 34 100 100     284 && $rel_info->{attrs}->{is_foreign_key_constraint};
568              
569 31         67 for my $f_key ( sort keys %{$rel_info->{cond}} ) {
  31         103  
570 31         70 my $col = $rel_info->{cond}->{$f_key};
571 31         162 $col =~ s/^self\.//;
572 31         107 $f_key =~ s/^foreign\.//;
573              
574             next
575 31 100   31   158 unless any { $col eq $_ } @missing_pks;
  31         117  
576              
577 30         83 DEBUG and warn "populating primary key column " .
578             "'$col' from relationship '$relname'\n";
579             # rel holds a scalar value
580 30 100 66     52 if (keys %{$rel_info->{cond}} == 1 && !ref $sub_updates->{$relname}) {
  30 50       213  
581 10         29 $pk_kvs{$col} = delete $sub_updates->{$relname};
582             # optimization to not update the related row
583             # when only the primary key of it is given
584 10         41 $sub_updates->{$col} = $pk_kvs{$col};
585             }
586             # rel holds a hashref
587             elsif (ref $sub_updates->{$relname} eq 'HASH') {
588             $pk_kvs{$col} =
589 20         94 $sub_updates->{$relname}->{$f_key};
590             }
591             else {
592 0         0 $self->throw_exception("passing anything else " .
593             "but a scalar or a hashref is not " .
594             "implemented");
595             }
596             }
597             }
598             }
599              
600 116 100 66     467 if ( scalar keys %pk_kvs != scalar @pks && @non_pk_columns) {
601 21         43 DEBUG and warn "not all primary keys available, trying " .
602             "object creation\n";
603             # new_result throws exception if non column values are passed
604             # because we want to also support e.g. a BUILDARGS method that
605             # populates primary key columns from an additional value
606             # filter out all relationships
607             my @non_rel_columns = grep {
608 21   100     68 !is_m2m( $related_resultset, $_ )
  41         606  
609             && !$related_result_source->has_relationship($_)
610             }
611             sort keys %$sub_updates;
612             my %non_rel_updates = map {
613 21         610 $_ => $sub_updates->{$_}
  35         146  
614             } @non_rel_columns;
615             # transform columns specified by their accessor name
616 21         85 my %columns_by_accessor = _get_columns_by_accessor($related_resultset);
617 21         139 for my $accessor_name (sort keys %columns_by_accessor) {
618 107         207 my $colname = $columns_by_accessor{$accessor_name}->{name};
619 107 100 100     319 if ($accessor_name ne $colname
620             && exists $non_rel_updates{$accessor_name}) {
621 10         15 DEBUG and warn "renaming column accessor " .
622             "'$accessor_name' to column name '$colname'\n";
623             $non_rel_updates{$colname} = delete
624 10         29 $non_rel_updates{$accessor_name};
625             }
626             }
627 21         133 DEBUG and warn "using all non-rel updates for object " .
628             "construction: " . Dumper(\%non_rel_updates);
629             # the object creation might fail because of non-column and
630             # non-constructor handled parameters which shouldn't break RU
631             try {
632 21     21   1591 my $related_row = $related_resultset
633             ->new_result(\%non_rel_updates);
634 21         2664 for my $colname (@pks) {
635             next
636 23 100       106 if exists $pk_kvs{$colname};
637              
638 22 100 66     761 if ($related_row->can($colname)
639             && defined $related_row->$colname) {
640 2         30 DEBUG and warn "missing pk column $colname exists " .
641             "and defined on object\n";
642 2         38 $pk_kvs{$colname} = $related_row->$colname;
643             }
644             else {
645 20         669 DEBUG and warn "missing pk column $colname doesn't "
646             . "exist or isn't defined on object, aborting\n";
647 20         98 last;
648             }
649             }
650             }
651             catch {
652 0     0   0 DEBUG and warn "object construction failed, ignoring:
653             $_\n";
654 21         235 };
655             }
656              
657             # The only reasons to let recursive_update search for an existing
658             # row (= not passing a new result to it) is relinking of existing
659             # rows.
660             # Relinking is only possible if all primary key column values are
661             # known and only required if at least one of the foreign row
662             # columns, which are part of the relationship, differ between
663             # current and target ones.
664             # There are two different cases:
665             # The foreign row columns are part of the foreign primary key.
666             # An example is the dvdtags relationship of Dvd.
667             # Or one or more non primary key form the relationship.
668             # An example is the owned_dvds relationship of User.
669 116         1151 my $relink = 0;
670              
671 116 100       399 if ( scalar keys %pk_kvs == scalar @pks ) {
672 96         172 DEBUG and warn "all primary keys available, " .
673             "searching for row in currently related rows\n";
674             # the lookup can fail if the primary key of a currently not
675             # related row is passed in the updates hash
676 96         330 $related_object = _get_matching_row(\%pk_kvs, \@related_rows);
677             # %pk_kvs contains the scalar value instead of a hashref
678             # when a column and relationship are named the same so
679             # overwrite the hashref in $sub_updates with that
680             # don't include %$resolved as well as that contains target data
681 96         534 my %current_data = (%$sub_updates, %pk_kvs);
682 96         224 DEBUG and warn "current data: " . Dumper(\%current_data);
683 96         174 DEBUG and warn "target data: " . Dumper($resolved);
684              
685 10     10   94 no warnings 'uninitialized';
  10         29  
  10         18096  
686              
687             # If the row can't be found by _get_matching_row it is
688             # currently not linked or doesn't even exist. In this case we
689             # must execute a sql select to find it.
690             $relink = 1
691             if (not defined $related_object)
692 38     38   231 && (any { $resolved->{$_} ne $current_data{$_} }
693 96 100 100     608 keys %$resolved);
694             }
695 116         301 DEBUG and warn "relink: $relink\n";
696              
697             # pass an empty object if no related row found and it's not the
698             # special case where the primary key of a currently not related
699             # row is passed in the updates hash to prevent the find by pk in
700             # recursive_update to happen
701 116 100 100     451 if ((not defined $related_object) && (not $relink)) {
702 56         106 DEBUG and warn "passing empty row to prevent find by pk\n";
703 56         254 $related_object = $related_resultset->new_result({});
704             }
705              
706 116         5533 my $sub_object = recursive_update(
707             resultset => $related_resultset,
708             updates => $sub_updates,
709             resolved => $resolved,
710             # pass prefetched object if found
711             object => $related_object,
712             );
713              
714 111         540 push @updated_objs, $sub_object;
715             }
716              
717             # determine if a removal query is required
718             my @remove_rows = grep {
719 54         204 my $existing_row = $_;
  102         12264  
720 159     159   12625 none { $existing_row->ID eq $_->ID } @updated_objs
721 102         586 } @related_rows;
722 54         5767 DEBUG and warn "rows for removal: " . join(', ', map { $_->ID }
723             @remove_rows) . "\n";
724              
725 54 100       441 if (scalar @remove_rows) {
726 21         680 my $rs_rel_delist = $object->$name;
727              
728             # foreign table has a single pk column
729 21 100       5635 if (scalar @pks == 1) {
730 6         17 DEBUG and warn "delete in not_in\n";
731 6         31 $rs_rel_delist = $rs_rel_delist->search_rs(
732             {
733             $self->current_source_alias . "." .
734             $pks[0] => { -not_in => [ map ( $_->id, @updated_objs ) ] }
735             }
736             );
737             }
738              
739             # foreign table has multiple pk columns
740             else {
741 15         41 my @cond;
742 15         54 for my $obj (@updated_objs) {
743 21         49 my %cond_for_obj;
744 21         53 for my $col (@pks) {
745 42         368 $cond_for_obj{ $self->current_source_alias . ".$col" } =
746             $obj->get_column($col);
747              
748             }
749 21         263 push @cond, \%cond_for_obj;
750             }
751              
752             # only limit resultset if there are related rows left
753 15 100       72 if (scalar @cond) {
754 11         59 $rs_rel_delist = $rs_rel_delist->search_rs({ -not => [ @cond ] });
755             }
756             }
757              
758 21 100       9461 if ($if_not_submitted eq 'delete') {
    50          
759 19         129 $rs_rel_delist->delete;
760             }
761             elsif ($if_not_submitted eq 'set_to_null') {
762 2         8 my %update = map {$_ => undef} @rel_cols;
  2         9  
763 2         11 $rs_rel_delist->update(\%update);
764             }
765             }
766             }
767             elsif ( $attrs->{accessor} eq 'single' ||
768             $attrs->{accessor} eq 'filter' ) {
769             DEBUG and warn "has_one, might_have, belongs_to (" .
770 87         178 $attrs->{accessor} . "): $name\n";
771              
772 87         186 my $sub_object;
773 87 100       255 if ( ref $updates ) {
774 86         168 my $existing_row = 0;
775 86         299 my @pks = $related_resultset->result_source->primary_columns;
776 86 100   86   1037 if ( all { exists $updates->{$_} && defined $updates->{$_} } @pks ) {
  86 100       487  
777 37         97 $existing_row = 1;
778             }
779 86         302 DEBUG and warn $existing_row ? "existing row\n" : "new row\n";
780             # newly created rows can't have related rows
781 86         167 my $related_row;
782 86 100       230 if ($row_existed) {
783 2         32 $related_row = $object->$name;
784 2         8992 DEBUG and warn "got related row\n";
785             }
786 86 100 66     701 if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
    100 100        
787 10         32 $sub_object = $updates;
788             }
789             elsif ( $attrs->{accessor} eq 'single' && defined $related_row )
790             {
791 1 50       29 $sub_object = recursive_update(
792             resultset => $related_resultset,
793             updates => $updates,
794             $existing_row ? () : (object => $object->$name),
795             );
796             }
797             else {
798 75 100       378 $sub_object = recursive_update(
799             resultset => $related_resultset,
800             updates => $updates,
801             $existing_row ? () : (resolved => $resolved),
802             );
803             }
804             }
805             else {
806             $sub_object = $related_resultset->find($updates)
807             unless (
808             !$updates &&
809             ( exists $attrs->{join_type} &&
810 1 50 33     10 $attrs->{join_type} eq 'LEFT' )
      33        
811             );
812             }
813 83   100     459 my $join_type = $attrs->{join_type} || '';
814             # unmarked 'LEFT' join for belongs_to
815             my $might_belong_to =
816             ( $attrs->{accessor} eq 'single' || $attrs->{accessor} eq 'filter' ) &&
817 83   66     612 $attrs->{is_foreign_key_constraint};
818             # adding check for custom condition that's a coderef
819             # this 'set_from_related' should probably not be called in lots of other
820             # situations too, but until that's worked out, kludge it
821 83 50 33     1191 if ( ( $sub_object || $updates || $might_belong_to || $join_type eq 'LEFT' ) &&
      33        
822             ref $info->{cond} ne 'CODE' ) {
823 83         6803 $object->$name($sub_object);
824             }
825             }
826             else {
827             $self->throw_exception(
828             "recursive_update doesn't now how to handle relationship '$name' with accessor " .
829 0         0 $info->{attrs}{accessor} );
830             }
831              
832 137         257289 DEBUG and warn "_update_relation end\n";
833             }
834              
835             sub is_m2m {
836 392     392   1076 my ( $self, $relation ) = @_;
837 392         1372 my $rclass = $self->result_class;
838              
839             # DBIx::Class::IntrospectableM2M
840 392 100       5858 if ( $rclass->can('_m2m_metadata') ) {
841 70         1993 return $rclass->_m2m_metadata->{$relation};
842             }
843 322         1152 my $object = $self->new_result( {} );
844 322 100 100     26367 if ( $object->can($relation) and
      100        
845             !$self->result_source->has_relationship($relation) and
846             $object->can( 'set_' . $relation ) ) {
847 12         187 return 1;
848             }
849 310         4407 return;
850             }
851              
852             sub get_m2m_source {
853 10     10   28 my ( $self, $relation ) = @_;
854 10         35 my $rclass = $self->result_class;
855              
856             # DBIx::Class::IntrospectableM2M
857 10 100       118 if ( $rclass->can('_m2m_metadata') ) {
858             return $self->result_source->related_source(
859             $rclass->_m2m_metadata->{$relation}{relation} )
860 4         97 ->related_source( $rclass->_m2m_metadata->{$relation}{foreign_relation} );
861             }
862 6         36 my $object = $self->new_result( {} );
863 6         417 my $r = $object->$relation;
864 6         24155 return $r->result_source;
865             }
866              
867             sub _delete_empty_auto_increment {
868 0     0   0 my ( $self, $object ) = @_;
869 0         0 for my $col ( sort keys %{ $object->{_column_data} } ) {
  0         0  
870 0 0 0     0 if (
      0        
871             $object->result_source->column_info($col)->{is_auto_increment} and
872             ( !defined $object->{_column_data}{$col} or
873             $object->{_column_data}{$col} eq '' )
874             ) {
875 0         0 delete $object->{_column_data}{$col};
876             }
877             }
878             }
879              
880             sub _get_pk_for_related {
881 103     103   278 my ( $self, $relation ) = @_;
882 103         204 my $source;
883 103 100       333 if ( $self->result_source->has_relationship($relation) ) {
884 93         579 $source = $self->result_source->related_source($relation);
885             }
886              
887             # many to many case
888 103 100       13877 if ( is_m2m( $self, $relation ) ) {
889 10         209 $source = get_m2m_source( $self, $relation );
890             }
891 103         3313 return $source->primary_columns;
892             }
893              
894             # This function determines whether a relationship should be done before or
895             # after the row is inserted into the database
896             # relationships before: belongs_to
897             # relationships after: has_many, might_have and has_one
898             # true means before, false after
899             sub _master_relation_cond {
900 132     132   1374315 my ( $self, $name ) = @_;
901              
902 132         395 my $source = $self->result_source;
903 132         397 my $info = $source->relationship_info($name);
904              
905             # has_many rels are always after
906             return 0
907 132 100       908 if $info->{attrs}->{accessor} eq 'multi';
908              
909 93         321 my @foreign_ids = _get_pk_for_related( $self, $name );
910              
911 93         845 my $cond = $info->{cond};
912              
913             sub _inner {
914 93     93   278 my ( $source, $cond, @foreign_ids ) = @_;
915              
916 93         340 for my $f_key ( sort keys %$cond ) {
917 93         236 my $col = $cond->{$f_key};
918             # might_have is not master
919 93         458 $col =~ s/^self\.//;
920 93         323 $f_key =~ s/^foreign\.//;
921 93 100       299 if ( $source->column_info($col)->{is_auto_increment} ) {
922 9         139 return 0;
923             }
924 84 50   84   1403 if ( any { $_ eq $f_key } @foreign_ids ) {
  84         291  
925 84         477 return 1;
926             }
927             }
928 0           return 0;
929             }
930              
931 93 50       331 if ( ref $cond eq 'HASH' ) {
    0          
    0          
932 93         294 return _inner( $source, $cond, @foreign_ids );
933             }
934              
935             # arrayref of hashrefs
936             elsif ( ref $cond eq 'ARRAY' ) {
937 0         0 for my $new_cond (@$cond) {
938 0         0 return _inner( $source, $new_cond, @foreign_ids );
939             }
940             }
941              
942             # we have a custom join condition, so update afterward
943             elsif ( ref $cond eq 'CODE' ) {
944 0         0 return 0;
945             }
946              
947             else {
948 0         0 $source->throw_exception( "unhandled relation condition " . ref($cond) );
949             }
950 0         0 return;
951             }
952              
953             1;
954              
955             __END__
956              
957             =pod
958              
959             =encoding UTF-8
960              
961             =head1 NAME
962              
963             DBIx::Class::ResultSet::RecursiveUpdate - like update_or_create - but recursive
964              
965             =head1 VERSION
966              
967             version 0.42
968              
969             =head1 SYNOPSIS
970              
971             # The functional interface:
972              
973             my $schema = MyDB::Schema->connect();
974             my $new_item = DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update(
975             resultset => $schema->resultset('User'),
976             updates => {
977             id => 1,
978             owned_dvds => [
979             {
980             title => "One Flew Over the Cuckoo's Nest"
981             }
982             ]
983             },
984             unknown_params_ok => 1,
985             );
986              
987              
988             # As ResultSet subclass:
989              
990             __PACKAGE__->load_namespaces( default_resultset_class => '+DBIx::Class::ResultSet::RecursiveUpdate' );
991              
992             # in the Schema file (see t/lib/DBSchema.pm). Or appropriate 'use base' in the ResultSet classes.
993              
994             my $user = $schema->resultset('User')->recursive_update({
995             id => 1,
996             owned_dvds => [
997             {
998             title => "One Flew Over the Cuckoo's Nest"
999             }
1000             ]
1001             }, {
1002             unknown_params_ok => 1,
1003             });
1004              
1005             # You'll get a warning if you pass non-result specific data to
1006             # recursive_update. See L</"Additional data in the updates hashref">
1007             # for more information how to prevent this.
1008              
1009             =head1 DESCRIPTION
1010              
1011             You can feed the ->create method of DBIx::Class with a recursive datastructure
1012             and have the related records created. Unfortunately you cannot do a similar
1013             thing with update_or_create. This module tries to fill that void until
1014             L<DBIx::Class> has an api itself.
1015              
1016             The functional interface can be used without modifications of the model,
1017             for example by form processors like L<HTML::FormHandler::Model::DBIC>.
1018              
1019             It is a base class for L<DBIx::Class::ResultSet>s providing the method
1020             recursive_update which works just like update_or_create but can recursively
1021             update or create result objects composed of multiple rows. All rows need to be
1022             identified by primary keys so you need to provide them in the update structure
1023             (unless they can be deduced from the parent row. For example a related row of
1024             a belongs_to relationship). If any of the primary key columns are missing,
1025             a new row will be created, with the expectation that the missing columns will
1026             be filled by it (as in the case of auto_increment primary keys).
1027              
1028             If the resultset itself stores an assignment for the primary key,
1029             like in the case of:
1030              
1031             my $restricted_rs = $user_rs->search( { id => 1 } );
1032              
1033             you need to inform recursive_update about the additional predicate with the fixed_fields attribute:
1034              
1035             my $user = $restricted_rs->recursive_update( {
1036             owned_dvds => [
1037             {
1038             title => 'One Flew Over the Cuckoo's Nest'
1039             }
1040             ]
1041             },
1042             {
1043             fixed_fields => [ 'id' ],
1044             }
1045             );
1046              
1047             For a many_to_many (pseudo) relation you can supply a list of primary keys
1048             from the other table and it will link the record at hand to those and
1049             only those records identified by them. This is convenient for handling web
1050             forms with check boxes (or a select field with multiple choice) that lets you
1051             update such (pseudo) relations.
1052              
1053             For a description how to set up base classes for ResultSets see
1054             L<DBIx::Class::Schema/load_namespaces>.
1055              
1056             =head2 Additional data in the updates hashref
1057              
1058             If you pass additional data to recursive_update which doesn't match a column
1059             name, column accessor, relationship or many-to-many helper accessor, it will
1060             throw a warning by default. To disable this behaviour you can set the
1061             unknown_params_ok attribute to a true value.
1062              
1063             The warning thrown is:
1064             "No such column, relationship, many-to-many helper accessor or generic accessor '$key'"
1065              
1066             When used by L<HTML::FormHandler::Model::DBIC> this can happen if you have
1067             additional form fields that aren't relevant to the database but don't have the
1068             noupdate attribute set to a true value.
1069              
1070             NOTE: in a future version this behaviour will change and throw an exception
1071             instead of a warning!
1072              
1073             =head1 DESIGN CHOICES
1074              
1075             Columns and relationships which are excluded from the updates hashref aren't
1076             touched at all.
1077              
1078             =head2 Treatment of belongs_to relations
1079              
1080             In case the relationship is included but undefined in the updates hashref,
1081             all columns forming the relationship will be set to null.
1082             If not all of them are nullable, DBIx::Class will throw an error.
1083              
1084             Updating the relationship:
1085              
1086             my $dvd = $dvd_rs->recursive_update( {
1087             id => 1,
1088             owner => $user->id,
1089             });
1090              
1091             Clearing the relationship (only works if cols are nullable!):
1092              
1093             my $dvd = $dvd_rs->recursive_update( {
1094             id => 1,
1095             owner => undef,
1096             });
1097              
1098             Updating a relationship including its (full) primary key:
1099              
1100             my $dvd = $dvd_rs->recursive_update( {
1101             id => 1,
1102             owner => {
1103             id => 2,
1104             name => "George",
1105             },
1106             });
1107              
1108             =head2 Treatment of might_have relationships
1109              
1110             In case the relationship is included but undefined in the updates hashref,
1111             all columns forming the relationship will be set to null.
1112              
1113             Updating the relationship:
1114              
1115             my $user = $user_rs->recursive_update( {
1116             id => 1,
1117             address => {
1118             street => "101 Main Street",
1119             city => "Podunk",
1120             state => "New York",
1121             }
1122             });
1123              
1124             Clearing the relationship:
1125              
1126             my $user = $user_rs->recursive_update( {
1127             id => 1,
1128             address => undef,
1129             });
1130              
1131             =head2 Treatment of has_many relations
1132              
1133             If a relationship key is included in the data structure with a value of undef
1134             or an empty array, all existing related rows will be deleted, or their foreign
1135             key columns will be set to null.
1136              
1137             The exact behaviour depends on the nullability of the foreign key columns and
1138             the value of the "if_not_submitted" parameter. The parameter defaults to
1139             undefined which neither nullifies nor deletes.
1140              
1141             When the array contains elements they are updated if they exist, created when
1142             not and deleted if not included.
1143              
1144             =head3 All foreign table columns are nullable
1145              
1146             In this case recursive_update defaults to nullifying the foreign columns.
1147              
1148             =head3 Not all foreign table columns are nullable
1149              
1150             In this case recursive_update deletes the foreign rows.
1151              
1152             Updating the relationship:
1153              
1154             Passing ids:
1155              
1156             my $user = $user_rs->recursive_update( {
1157             id => 1,
1158             owned_dvds => [1, 2],
1159             });
1160              
1161             Passing hashrefs:
1162              
1163             my $user = $user_rs->recursive_update( {
1164             id => 1,
1165             owned_dvds => [
1166             {
1167             name => 'temp name 1',
1168             },
1169             {
1170             name => 'temp name 2',
1171             },
1172             ],
1173             });
1174              
1175             Passing objects:
1176              
1177             my $user = $user_rs->recursive_update( {
1178             id => 1,
1179             owned_dvds => [ $dvd1, $dvd2 ],
1180             });
1181              
1182             You can even mix them:
1183              
1184             my $user = $user_rs->recursive_update( {
1185             id => 1,
1186             owned_dvds => [ 1, { id => 2 } ],
1187             });
1188              
1189             Clearing the relationship:
1190              
1191             my $user = $user_rs->recursive_update( {
1192             id => 1,
1193             owned_dvds => undef,
1194             });
1195              
1196             This is the same as passing an empty array:
1197              
1198             my $user = $user_rs->recursive_update( {
1199             id => 1,
1200             owned_dvds => [],
1201             });
1202              
1203             =head2 Treatment of many-to-many pseudo relations
1204              
1205             If a many-to-many accessor key is included in the data structure with a value
1206             of undef or an empty array, all existing related rows are unlinked.
1207              
1208             When the array contains elements they are updated if they exist, created when
1209             not and deleted if not included.
1210              
1211             RecursiveUpdate defaults to
1212             calling 'set_$rel' to update many-to-many relationships.
1213             See L<DBIx::Class::Relationship/many_to_many> for details.
1214             set_$rel effectively removes and re-adds all relationship data,
1215             even if the set of related items did not change at all.
1216              
1217             If L<DBIx::Class::IntrospectableM2M> is in use, RecursiveUpdate will
1218             look up the corresponding has_many relationship and use this to recursively
1219             update the many-to-many relationship.
1220              
1221             While both mechanisms have the same final result, deleting and re-adding
1222             all relationship data can have unwanted consequences if triggers or
1223             method modifiers are defined or logging modules like L<DBIx::Class::AuditLog>
1224             are in use.
1225              
1226             The traditional "set_$rel" behaviour can be forced by passing
1227             "m2m_force_set_rel => 1" to recursive_update.
1228              
1229             See L</is_m2m> for many-to-many pseudo relationship detection.
1230              
1231             Updating the relationship:
1232              
1233             Passing ids:
1234              
1235             my $dvd = $dvd_rs->recursive_update( {
1236             id => 1,
1237             tags => [1, 2],
1238             });
1239              
1240             Passing hashrefs:
1241              
1242             my $dvd = $dvd_rs->recursive_update( {
1243             id => 1,
1244             tags => [
1245             {
1246             id => 1,
1247             file => 'file0'
1248             },
1249             {
1250             id => 2,
1251             file => 'file1',
1252             },
1253             ],
1254             });
1255              
1256             Passing objects:
1257              
1258             my $dvd = $dvd_rs->recursive_update( {
1259             id => 1,
1260             tags => [ $tag1, $tag2 ],
1261             });
1262              
1263             You can even mix them:
1264              
1265             my $dvd = $dvd_rs->recursive_update( {
1266             id => 1,
1267             tags => [ 2, { id => 3 } ],
1268             });
1269              
1270             Clearing the relationship:
1271              
1272             my $dvd = $dvd_rs->recursive_update( {
1273             id => 1,
1274             tags => undef,
1275             });
1276              
1277             This is the same as passing an empty array:
1278              
1279             my $dvd = $dvd_rs->recursive_update( {
1280             id => 1,
1281             tags => [],
1282             });
1283              
1284             Make sure that set_$rel used to update many-to-many relationships
1285             even if IntrospectableM2M is loaded:
1286              
1287             my $dvd = $dvd_rs->recursive_update( {
1288             id => 1,
1289             tags => [1, 2],
1290             },
1291             { m2m_force_set_rel => 1 },
1292             );
1293              
1294             =head1 INTERFACE
1295              
1296             =head1 METHODS
1297              
1298             =head2 recursive_update
1299              
1300             The method that does the work here.
1301              
1302             =head2 is_m2m
1303              
1304             =over 4
1305              
1306             =item Arguments: $name
1307              
1308             =item Return Value: true, if $name is a many to many pseudo-relationship
1309              
1310             =back
1311              
1312             The function gets the information about m2m relations from
1313             L<DBIx::Class::IntrospectableM2M>. If it isn't loaded in the ResultSource
1314             class, the code relies on the fact:
1315              
1316             if($object->can($name) and
1317             !$object->result_source->has_relationship($name) and
1318             $object->can( 'set_' . $name )
1319             )
1320              
1321             to identify a many to many pseudo relationship. In a similar ugly way the
1322             ResultSource of that many to many pseudo relationship is detected.
1323              
1324             So if you need many to many pseudo relationship support, it's strongly
1325             recommended to load L<DBIx::Class::IntrospectableM2M> in your ResultSource
1326             class!
1327              
1328             =head2 get_m2m_source
1329              
1330             =over 4
1331              
1332             =item Arguments: $name
1333              
1334             =item Return Value: $result_source
1335              
1336             =back
1337              
1338             =head1 CONFIGURATION AND ENVIRONMENT
1339              
1340             DBIx::Class::RecursiveUpdate requires no configuration files or environment variables.
1341              
1342             =head1 DEPENDENCIES
1343              
1344             DBIx::Class
1345              
1346             optional but recommended:
1347             DBIx::Class::IntrospectableM2M
1348              
1349             =head1 INCOMPATIBILITIES
1350              
1351             None reported.
1352              
1353             =head1 BUGS AND LIMITATIONS
1354              
1355             The list of reported bugs can be viewed at L<http://rt.cpan.org/Public/Dist/Display.html?Name=DBIx-Class-ResultSet-RecursiveUpdate>.
1356              
1357             Please report any bugs or feature requests to
1358             C<bug-DBIx-Class-ResultSet-RecursiveUpdate@rt.cpan.org>, or through the web interface at
1359             L<http://rt.cpan.org>.
1360              
1361             =head1 AUTHORS
1362              
1363             =over 4
1364              
1365             =item *
1366              
1367             Zbigniew Lukasiak <zby@cpan.org>
1368              
1369             =item *
1370              
1371             John Napiorkowski <jjnapiork@cpan.org>
1372              
1373             =item *
1374              
1375             Alexander Hartmaier <abraxxa@cpan.org>
1376              
1377             =item *
1378              
1379             Gerda Shank <gshank@cpan.org>
1380              
1381             =back
1382              
1383             =head1 COPYRIGHT AND LICENSE
1384              
1385             This software is copyright (c) 2020 by Zbigniew Lukasiak, John Napiorkowski, Alexander Hartmaier.
1386              
1387             This is free software; you can redistribute it and/or modify it under
1388             the same terms as the Perl 5 programming language system itself.
1389              
1390             =cut