]> Dogcows Code - chaz/p5-DBIx-Class-ResultSet-RecursiveUpdate/blob - lib/DBIx/Class/ResultSet/RecursiveUpdate.pm
handle DBIx::Class change for find which throws an exception since version > 0.08123
[chaz/p5-DBIx-Class-ResultSet-RecursiveUpdate] / lib / DBIx / Class / ResultSet / RecursiveUpdate.pm
1 use strict;
2 use warnings;
3
4 package DBIx::Class::ResultSet::RecursiveUpdate;
5
6 # ABSTRACT: like update_or_create - but recursive
7
8 use base qw(DBIx::Class::ResultSet);
9
10 sub recursive_update {
11 my ( $self, $updates, $attrs ) = @_;
12
13 my $fixed_fields;
14 my $unknown_params_ok;
15
16 # 0.21+ api
17 if ( defined $attrs && ref $attrs eq 'HASH' ) {
18 $fixed_fields = $attrs->{fixed_fields};
19 $unknown_params_ok = $attrs->{unknown_params_ok};
20 }
21
22 # pre 0.21 api
23 elsif ( defined $attrs && ref $attrs eq 'ARRAY' ) {
24 $fixed_fields = $attrs;
25 }
26
27 return
28 DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update(
29 resultset => $self,
30 updates => $updates,
31 fixed_fields => $fixed_fields,
32 unknown_params_ok => $unknown_params_ok,
33 );
34 }
35
36 package DBIx::Class::ResultSet::RecursiveUpdate::Functions;
37 use Carp::Clan qw/^DBIx::Class|^HTML::FormHandler|^Try::Tiny/;
38 use Scalar::Util qw( blessed );
39 use List::MoreUtils qw/ any /;
40 use Try::Tiny;
41
42 sub recursive_update {
43 my %params = @_;
44 my ( $self, $updates, $fixed_fields, $object, $resolved,
45 $if_not_submitted, $unknown_params_ok )
46 = @params{
47 qw/resultset updates fixed_fields object resolved if_not_submitted unknown_params_ok/
48 };
49 $resolved ||= {};
50
51 my $source = $self->result_source;
52
53 croak "first parameter needs to be defined"
54 unless defined $updates;
55
56 croak "first parameter needs to be a hashref"
57 unless ref($updates) eq 'HASH';
58
59 # warn 'entering: ' . $source->from();
60 croak 'fixed fields needs to be an arrayref'
61 if defined $fixed_fields && ref $fixed_fields ne 'ARRAY';
62
63 # always warn about additional parameters if storage debugging is enabled
64 $unknown_params_ok = 0
65 if $source->storage->debug;
66
67 if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
68 return $updates;
69 }
70
71 if ( !defined $object && exists $updates->{id} ) {
72
73 # warn "finding object by id " . $updates->{id} . "\n";
74 $object = $self->find( $updates->{id}, { key => 'primary' } );
75
76 # warn "object not found by id\n"
77 # unless defined $object;
78 }
79
80 my %fixed_fields = map { $_ => 1 } @$fixed_fields
81 if $fixed_fields;
82 my @missing =
83 grep { !exists $updates->{$_} && !exists $fixed_fields{$_} }
84 $source->primary_columns;
85
86 # warn "MISSING: " . join(', ', @missing) . "\n";
87 if ( !defined $object && scalar @missing == 0 ) {
88
89 # warn 'finding by: ' . Dumper( $updates ); use Data::Dumper;
90 try {
91 $object = $self->find( $updates, { key => 'primary' } );
92 };
93 }
94 $updates = { %$updates, %$resolved };
95 @missing = grep { !exists $resolved->{$_} } @missing;
96 if ( !defined $object && scalar @missing == 0 ) {
97
98 # warn 'finding by +resolved: ' . Dumper( $updates ); use Data::Dumper;
99 try {
100 $object = $self->find( $updates, { key => 'primary' } );
101 };
102 }
103
104 $object = $self->new( {} )
105 unless defined $object;
106
107 # warn Dumper( $updates ); use Data::Dumper;
108 # direct column accessors
109 my %columns;
110
111 # relations that that should be done before the row is inserted into the
112 # database like belongs_to
113 my %pre_updates;
114
115 # relations that that should be done after the row is inserted into the
116 # database like has_many, might_have and has_one
117 my %post_updates;
118 my %other_methods;
119 my %m2m_accessors;
120 my %columns_by_accessor = _get_columns_by_accessor($self);
121
122 # warn 'resolved: ' . Dumper( $resolved );
123 # warn 'updates: ' . Dumper( $updates ); use Data::Dumper;
124 # warn 'columns: ' . Dumper( \%columns_by_accessor );
125 for my $name ( keys %$updates ) {
126
127 # columns
128 if ( exists $columns_by_accessor{$name}
129 && !( $source->has_relationship($name)
130 && ref( $updates->{$name} ) ) )
131 {
132
133 #warn "$name is a column\n";
134 $columns{$name} = $updates->{$name};
135 next;
136 }
137
138 # relationships
139 if ( $source->has_relationship($name) ) {
140 if ( _master_relation_cond( $self, $name ) ) {
141
142 #warn "$name is a pre-update rel\n";
143 $pre_updates{$name} = $updates->{$name};
144 next;
145 }
146 else {
147
148 #warn "$name is a post-update rel\n";
149 $post_updates{$name} = $updates->{$name};
150 next;
151 }
152 }
153
154 # many-to-many helper accessors
155 if ( is_m2m( $self, $name ) ) {
156
157 #warn "$name is a many-to-many helper accessor\n";
158 $m2m_accessors{$name} = $updates->{$name};
159 next;
160 }
161
162 # accessors
163 if ( $object->can($name) && not $source->has_relationship($name) ) {
164
165 #warn "$name is an accessor";
166 $other_methods{$name} = $updates->{$name};
167 next;
168 }
169
170 # unknown
171
172 # don't throw a warning instead of an exception to give users
173 # time to adapt to the new API
174 carp(
175 "No such column, relationship, many-to-many helper accessor or generic accessor '$name'"
176 ) unless $unknown_params_ok;
177
178 #$self->throw_exception(
179 # "No such column, relationship, many-to-many helper accessor or generic accessor '$name'"
180 #);
181 }
182
183 # warn 'other: ' . Dumper( \%other_methods ); use Data::Dumper;
184
185 # first update columns and other accessors
186 # so that later related records can be found
187 for my $name ( keys %columns ) {
188
189 #warn "update col $name\n";
190 $object->$name( $columns{$name} );
191 }
192 for my $name ( keys %other_methods ) {
193
194 #warn "update other $name\n";
195 $object->$name( $other_methods{$name} );
196 }
197 for my $name ( keys %pre_updates ) {
198
199 #warn "pre_update $name\n";
200 _update_relation( $self, $name, $pre_updates{$name}, $object,
201 $if_not_submitted );
202 }
203
204 # $self->_delete_empty_auto_increment($object);
205 # don't allow insert to recurse to related objects
206 # do the recursion ourselves
207 # $object->{_rel_in_storage} = 1;
208 #warn "CHANGED: " . $object->is_changed . "\n";
209 #warn "IN STOR: " . $object->in_storage . "\n";
210 $object->update_or_insert if $object->is_changed;
211 $object->discard_changes;
212
213 # updating many_to_many
214 for my $name ( keys %m2m_accessors ) {
215 my $value = $m2m_accessors{$name};
216
217 #warn "update m2m $name\n";
218 # TODO: only first pk col is used
219 my ($pk) = _get_pk_for_related( $self, $name );
220 my @rows;
221 my $result_source = $object->$name->result_source;
222 my @updates;
223 if ( defined $value && ref $value eq 'ARRAY' ) {
224 @updates = @{$value};
225 }
226 elsif ( defined $value && !ref $value ) {
227 @updates = ($value);
228 }
229 elsif ( defined $value ) {
230 carp
231 "value of many-to-many rel '$name' must be an arrayref or scalar: $value";
232 }
233 for my $elem (@updates) {
234 if ( blessed($elem) && $elem->isa('DBIx::Class::Row') ) {
235 push @rows, $elem;
236 }
237 elsif ( ref $elem eq 'HASH' ) {
238 push @rows,
239 recursive_update(
240 resultset => $result_source->resultset,
241 updates => $elem
242 );
243 }
244 else {
245 push @rows,
246 $result_source->resultset->find( { $pk => $elem } );
247 }
248 }
249 my $set_meth = 'set_' . $name;
250 $object->$set_meth( \@rows );
251 }
252 for my $name ( keys %post_updates ) {
253
254 #warn "post_update $name\n";
255 _update_relation( $self, $name, $post_updates{$name}, $object,
256 $if_not_submitted );
257 }
258 return $object;
259 }
260
261 # returns DBIx::Class::ResultSource::column_info as a hash indexed by column accessor || name
262 sub _get_columns_by_accessor {
263 my $self = shift;
264 my $source = $self->result_source;
265 my %columns;
266 for my $name ( $source->columns ) {
267 my $info = $source->column_info($name);
268 $info->{name} = $name;
269 $columns{ $info->{accessor} || $name } = $info;
270 }
271 return %columns;
272 }
273
274 # Arguments: $rs, $name, $updates, $row, $if_not_submitted
275 sub _update_relation {
276 my ( $self, $name, $updates, $object, $if_not_submitted ) = @_;
277
278 # this should never happen because we're checking the paramters passed to
279 # recursive_update, but just to be sure...
280 $object->throw_exception("No such relationship '$name'")
281 unless $object->has_relationship($name);
282
283 #warn "_update_relation $name: OBJ: " . ref($object) . "\n";
284
285 my $info = $object->result_source->relationship_info($name);
286
287 # get a related resultset without a condition
288 my $related_resultset =
289 $self->related_resultset($name)->result_source->resultset;
290 my $resolved;
291 if ( $self->result_source->can('_resolve_condition') ) {
292 $resolved =
293 $self->result_source->_resolve_condition( $info->{cond}, $name,
294 $object );
295 }
296 else {
297 $self->throw_exception(
298 "result_source must support _resolve_condition");
299 }
300
301 # warn "$name resolved: " . Dumper( $resolved ); use Data::Dumper;
302 $resolved = {}
303 if defined $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
304 && $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
305 == $resolved;
306
307 #warn "RESOLVED: " . Dumper($resolved); use Data::Dumper;
308
309 my @rel_cols = keys %{ $info->{cond} };
310 map {s/^foreign\.//} @rel_cols;
311
312 #warn "REL_COLS: " . Dumper(@rel_cols); use Data::Dumper;
313 #my $rel_col_cnt = scalar @rel_cols;
314
315 # find out if all related columns are nullable
316 my $all_fks_nullable = 1;
317 for my $rel_col (@rel_cols) {
318 $all_fks_nullable = 0
319 unless $related_resultset->result_source->column_info($rel_col)
320 ->{is_nullable};
321 }
322
323 $if_not_submitted = $all_fks_nullable ? 'nullify' : 'delete'
324 unless defined $if_not_submitted;
325
326 #warn "\tNULLABLE: $all_fks_nullable ACTION: $if_not_submitted\n";
327
328 #warn "RELINFO for $name: " . Dumper($info); use Data::Dumper;
329
330 # the only valid datatype for a has_many rels is an arrayref
331 if ( $info->{attrs}{accessor} eq 'multi' ) {
332
333 # handle undef like empty arrayref
334 $updates = []
335 unless defined $updates;
336 $self->throw_exception(
337 "data for has_many relationship '$name' must be an arrayref")
338 unless ref $updates eq 'ARRAY';
339
340 my @updated_objs;
341
342 #warn "\tupdating has_many rel '$name' ($rel_col_cnt columns cols)\n";
343 for my $sub_updates ( @{$updates} ) {
344 my $sub_object = recursive_update(
345 resultset => $related_resultset,
346 updates => $sub_updates,
347 resolved => $resolved
348 );
349
350 push @updated_objs, $sub_object;
351 }
352
353 #warn "\tcreated and updated related rows\n";
354
355 my @related_pks = $related_resultset->result_source->primary_columns;
356
357 my $rs_rel_delist = $object->$name;
358
359 # foreign table has a single pk column
360 if ( scalar @related_pks == 1 ) {
361 $rs_rel_delist = $rs_rel_delist->search_rs(
362 { $related_pks[0] =>
363 { -not_in => [ map ( $_->id, @updated_objs ) ] }
364 }
365 );
366 }
367
368 # foreign table has multiple pk columns
369 else {
370 my @cond;
371 for my $obj (@updated_objs) {
372 my %cond_for_obj;
373 for my $col (@related_pks) {
374 $cond_for_obj{$col} = $obj->get_column($col);
375 }
376 push @cond, \%cond_for_obj;
377 }
378
379 # only limit resultset if there are related rows left
380 if ( scalar @cond ) {
381 $rs_rel_delist =
382 $rs_rel_delist->search_rs( { -not => [@cond] } );
383 }
384 }
385
386 #warn "\tCOND: " . Dumper(\%cond);
387 #my $rel_delist_cnt = $rs_rel_delist->count;
388 if ( $if_not_submitted eq 'delete' ) {
389
390 #warn "\tdeleting related rows: $rel_delist_cnt\n";
391 $rs_rel_delist->delete;
392 }
393 elsif ( $if_not_submitted eq 'set_to_null' ) {
394
395 #warn "\tnullifying related rows: $rel_delist_cnt\n";
396 my %update = map { $_ => undef } @rel_cols;
397 $rs_rel_delist->update( \%update );
398 }
399 }
400 elsif ($info->{attrs}{accessor} eq 'single'
401 || $info->{attrs}{accessor} eq 'filter' )
402 {
403
404 #warn "\tupdating rel '$name': $if_not_submitted\n";
405 my $sub_object;
406 if ( ref $updates ) {
407 if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
408 $sub_object = $updates;
409 }
410
411 # for might_have relationship
412 elsif ( $info->{attrs}{accessor} eq 'single'
413 && defined $object->$name )
414 {
415 $sub_object = recursive_update(
416 resultset => $related_resultset,
417 updates => $updates,
418 object => $object->$name
419 );
420 }
421 else {
422 $sub_object = recursive_update(
423 resultset => $related_resultset,
424 updates => $updates,
425 resolved => $resolved
426 );
427 }
428 }
429 else {
430 $sub_object = $related_resultset->find($updates)
431 unless (
432 !$updates
433 && ( exists $info->{attrs}{join_type}
434 && $info->{attrs}{join_type} eq 'LEFT' )
435 );
436 }
437 $object->set_from_related( $name, $sub_object )
438 unless (
439 !$sub_object
440 && !$updates
441 && ( exists $info->{attrs}{join_type}
442 && $info->{attrs}{join_type} eq 'LEFT' )
443 );
444 }
445 else {
446 $self->throw_exception(
447 "recursive_update doesn't now how to handle relationship '$name' with accessor "
448 . $info->{attrs}{accessor} );
449 }
450 }
451
452 sub is_m2m {
453 my ( $self, $relation ) = @_;
454 my $rclass = $self->result_class;
455
456 # DBIx::Class::IntrospectableM2M
457 if ( $rclass->can('_m2m_metadata') ) {
458 return $rclass->_m2m_metadata->{$relation};
459 }
460 my $object = $self->new( {} );
461 if ( $object->can($relation)
462 and !$self->result_source->has_relationship($relation)
463 and $object->can( 'set_' . $relation ) )
464 {
465 return 1;
466 }
467 return;
468 }
469
470 sub get_m2m_source {
471 my ( $self, $relation ) = @_;
472 my $rclass = $self->result_class;
473
474 # DBIx::Class::IntrospectableM2M
475 if ( $rclass->can('_m2m_metadata') ) {
476 return $self->result_source->related_source(
477 $rclass->_m2m_metadata->{$relation}{relation} )
478 ->related_source(
479 $rclass->_m2m_metadata->{$relation}{foreign_relation} );
480 }
481 my $object = $self->new( {} );
482 my $r = $object->$relation;
483 return $r->result_source;
484 }
485
486 sub _delete_empty_auto_increment {
487 my ( $self, $object ) = @_;
488 for my $col ( keys %{ $object->{_column_data} } ) {
489 if ($object->result_source->column_info($col)->{is_auto_increment}
490 and ( !defined $object->{_column_data}{$col}
491 or $object->{_column_data}{$col} eq '' )
492 )
493 {
494 delete $object->{_column_data}{$col};
495 }
496 }
497 }
498
499 sub _get_pk_for_related {
500 my ( $self, $relation ) = @_;
501 my $result_source;
502 if ( $self->result_source->has_relationship($relation) ) {
503 $result_source = $self->result_source->related_source($relation);
504 }
505
506 # many to many case
507 if ( is_m2m( $self, $relation ) ) {
508 $result_source = get_m2m_source( $self, $relation );
509 }
510 return $result_source->primary_columns;
511 }
512
513 # This function determines wheter a relationship should be done before or
514 # after the row is inserted into the database
515 # relationships before: belongs_to
516 # relationships after: has_many, might_have and has_one
517 # true means before, false after
518 sub _master_relation_cond {
519 my ( $self, $name ) = @_;
520
521 my $source = $self->result_source;
522 my $info = $source->relationship_info($name);
523
524 #warn "INFO: " . Dumper($info) . "\n";
525
526 # has_many rels are always after
527 return 0
528 if $info->{attrs}->{accessor} eq 'multi';
529
530 my @foreign_ids = _get_pk_for_related( $self, $name );
531
532 #warn "IDS: " . join(', ', @foreign_ids) . "\n";
533
534 my $cond = $info->{cond};
535
536 sub _inner {
537 my ( $source, $cond, @foreign_ids ) = @_;
538
539 while ( my ( $f_key, $col ) = each %{$cond} ) {
540
541 # might_have is not master
542 $col =~ s/^self\.//;
543 $f_key =~ s/^foreign\.//;
544 if ( $source->column_info($col)->{is_auto_increment} ) {
545 return 0;
546 }
547 if ( any { $_ eq $f_key } @foreign_ids ) {
548 return 1;
549 }
550 }
551 return 0;
552 }
553
554 if ( ref $cond eq 'HASH' ) {
555 return _inner( $source, $cond, @foreign_ids );
556 }
557
558 # arrayref of hashrefs
559 elsif ( ref $cond eq 'ARRAY' ) {
560 for my $new_cond (@$cond) {
561 return _inner( $source, $new_cond, @foreign_ids );
562 }
563 }
564 else {
565 $source->throw_exception(
566 "unhandled relation condition " . ref($cond) );
567 }
568 return;
569 }
570
571 1; # Magic true value required at end of module
572 __END__
573
574 =head1 SYNOPSIS
575
576 # The functional interface:
577
578 my $schema = MyDB::Schema->connect();
579 my $new_item = DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update(
580 resultset => $schema->resultset('User'),
581 updates => {
582 id => 1,
583 owned_dvds => [
584 {
585 title => "One Flew Over the Cuckoo's Nest"
586 }
587 ]
588 },
589 unknown_params_ok => 1,
590 );
591
592
593 # As ResultSet subclass:
594
595 __PACKAGE__->load_namespaces( default_resultset_class => '+DBIx::Class::ResultSet::RecursiveUpdate' );
596
597 # in the Schema file (see t/lib/DBSchema.pm). Or appropriate 'use base' in the ResultSet classes.
598
599 my $user = $schema->resultset('User')->recursive_update({
600 id => 1,
601 owned_dvds => [
602 {
603 title => "One Flew Over the Cuckoo's Nest"
604 }
605 ]
606 }, {
607 unknown_params_ok => 1,
608 });
609
610 # You'll get a warning if you pass non-result specific data to
611 # recursive_update. See L</"Additional data in the updates hashref">
612 # for more information how to prevent this.
613
614 =head1 DESCRIPTION
615
616 This is still experimental.
617
618 You can feed the ->create method of DBIx::Class with a recursive datastructure
619 and have the related records created. Unfortunately you cannot do a similar
620 thing with update_or_create. This module tries to fill that void until
621 L<DBIx::Class> has an api itself.
622
623 The functional interface can be used without modifications of the model,
624 for example by form processors like L<HTML::FormHandler::Model::DBIC>.
625
626 It is a base class for L<DBIx::Class::ResultSet>s providing the method
627 recursive_update which works just like update_or_create but can recursively
628 update or create result objects composed of multiple rows. All rows need to be
629 identified by primary keys so you need to provide them in the update structure
630 (unless they can be deduced from the parent row. For example a related row of
631 a belongs_to relationship). If any of the primary key columns are missing,
632 a new row will be created, with the expectation that the missing columns will
633 be filled by it (as in the case of auto_increment primary keys).
634
635 If the resultset itself stores an assignment for the primary key,
636 like in the case of:
637
638 my $restricted_rs = $user_rs->search( { id => 1 } );
639
640 you need to inform recursive_update about the additional predicate with the fixed_fields attribute:
641
642 my $user = $restricted_rs->recursive_update( {
643 owned_dvds => [
644 {
645 title => 'One Flew Over the Cuckoo's Nest'
646 }
647 ]
648 },
649 {
650 fixed_fields => [ 'id' ],
651 }
652 );
653
654 For a many_to_many (pseudo) relation you can supply a list of primary keys
655 from the other table and it will link the record at hand to those and
656 only those records identified by them. This is convenient for handling web
657 forms with check boxes (or a select field with multiple choice) that lets you
658 update such (pseudo) relations.
659
660 For a description how to set up base classes for ResultSets see
661 L<DBIx::Class::Schema/load_namespaces>.
662
663 =head2 Additional data in the updates hashref
664
665 If you pass additional data to recursive_update which doesn't match a column
666 name, column accessor, relationship or many-to-many helper accessor, it will
667 throw a warning by default. To disable this behaviour you can set the
668 unknown_params_ok attribute to a true value.
669
670 The warning thrown is:
671 "No such column, relationship, many-to-many helper accessor or generic accessor '$key'"
672
673 When used by L<HTML::FormHandler::Model::DBIC> this can happen if you have
674 additional form fields that aren't relevant to the database but don't have the
675 noupdate attribute set to a true value.
676
677 NOTE: in a future version this behaviour will change and throw an exception
678 instead of a warning!
679
680
681 =head1 DESIGN CHOICES
682
683 Columns and relationships which are excluded from the updates hashref aren't
684 touched at all.
685
686 =head2 Treatment of belongs_to relations
687
688 In case the relationship is included but undefined in the updates hashref,
689 all columns forming the relationship will be set to null.
690 If not all of them are nullable, DBIx::Class will throw an error.
691
692 Updating the relationship:
693
694 my $dvd = $dvd_rs->recursive_update( {
695 id => 1,
696 owner => $user->id,
697 });
698
699 Clearing the relationship (only works if cols are nullable!):
700
701 my $dvd = $dvd_rs->recursive_update( {
702 id => 1,
703 owner => undef,
704 });
705
706 =head2 Treatment of might_have relationships
707
708 In case the relationship is included but undefined in the updates hashref,
709 all columns forming the relationship will be set to null.
710
711 Updating the relationship:
712
713 my $user = $user_rs->recursive_update( {
714 id => 1,
715 address => {
716 street => "101 Main Street",
717 city => "Podunk",
718 state => "New York",
719 }
720 });
721
722 Clearing the relationship:
723
724 my $user = $user_rs->recursive_update( {
725 id => 1,
726 address => undef,
727 });
728
729 =head2 Treatment of has_many relations
730
731 If a relationship key is included in the data structure with a value of undef
732 or an empty array, all existing related rows will be deleted, or their foreign
733 key columns will be set to null.
734
735 The exact behaviour depends on the nullability of the foreign key columns and
736 the value of the "if_not_submitted" parameter. The parameter defaults to
737 undefined which neither nullifies nor deletes.
738
739 When the array contains elements they are updated if they exist, created when
740 not and deleted if not included.
741
742 =head3 All foreign table columns are nullable
743
744 In this case recursive_update defaults to nullifying the foreign columns.
745
746 =head3 Not all foreign table columns are nullable
747
748 In this case recursive_update deletes the foreign rows.
749
750 Updating the relationship:
751
752 Passing ids:
753
754 my $user = $user_rs->recursive_update( {
755 id => 1,
756 owned_dvds => [1, 2],
757 });
758
759 Passing hashrefs:
760
761 my $user = $user_rs->recursive_update( {
762 id => 1,
763 owned_dvds => [
764 {
765 name => 'temp name 1',
766 },
767 {
768 name => 'temp name 2',
769 },
770 ],
771 });
772
773 Passing objects:
774
775 my $user = $user_rs->recursive_update( {
776 id => 1,
777 owned_dvds => [ $dvd1, $dvd2 ],
778 });
779
780 You can even mix them:
781
782 my $user = $user_rs->recursive_update( {
783 id => 1,
784 owned_dvds => [ 1, { id => 2 } ],
785 });
786
787 Clearing the relationship:
788
789 my $user = $user_rs->recursive_update( {
790 id => 1,
791 owned_dvds => undef,
792 });
793
794 This is the same as passing an empty array:
795
796 my $user = $user_rs->recursive_update( {
797 id => 1,
798 owned_dvds => [],
799 });
800
801 =head2 Treatment of many-to-many pseudo relations
802
803 If a many-to-many accessor key is included in the data structure with a value
804 of undef or an empty array, all existing related rows are unlinked.
805
806 When the array contains elements they are updated if they exist, created when
807 not and deleted if not included.
808
809 See L</is_m2m> for many-to-many pseudo relationship detection.
810
811 Updating the relationship:
812
813 Passing ids:
814
815 my $dvd = $dvd_rs->recursive_update( {
816 id => 1,
817 tags => [1, 2],
818 });
819
820 Passing hashrefs:
821
822 my $dvd = $dvd_rs->recursive_update( {
823 id => 1,
824 tags => [
825 {
826 id => 1,
827 file => 'file0'
828 },
829 {
830 id => 2,
831 file => 'file1',
832 },
833 ],
834 });
835
836 Passing objects:
837
838 my $dvd = $dvd_rs->recursive_update( {
839 id => 1,
840 tags => [ $tag1, $tag2 ],
841 });
842
843 You can even mix them:
844
845 my $dvd = $dvd_rs->recursive_update( {
846 id => 1,
847 tags => [ 2, { id => 3 } ],
848 });
849
850 Clearing the relationship:
851
852 my $dvd = $dvd_rs->recursive_update( {
853 id => 1,
854 tags => undef,
855 });
856
857 This is the same as passing an empty array:
858
859 my $dvd = $dvd_rs->recursive_update( {
860 id => 1,
861 tags => [],
862 });
863
864
865 =head1 INTERFACE
866
867 =head1 METHODS
868
869 =head2 recursive_update
870
871 The method that does the work here.
872
873 =head2 is_m2m
874
875 =over 4
876
877 =item Arguments: $name
878
879 =item Return Value: true, if $name is a many to many pseudo-relationship
880
881 =back
882
883 The function gets the information about m2m relations from
884 L<DBIx::Class::IntrospectableM2M>. If it isn't loaded in the ResultSource
885 class, the code relies on the fact:
886
887 if($object->can($name) and
888 !$object->result_source->has_relationship($name) and
889 $object->can( 'set_' . $name )
890 )
891
892 to identify a many to many pseudo relationship. In a similar ugly way the
893 ResultSource of that many to many pseudo relationship is detected.
894
895 So if you need many to many pseudo relationship support, it's strongly
896 recommended to load L<DBIx::Class::IntrospectableM2M> in your ResultSource
897 class!
898
899 =head2 get_m2m_source
900
901 =over 4
902
903 =item Arguments: $name
904
905 =item Return Value: $result_source
906
907 =back
908
909 =head1 CONFIGURATION AND ENVIRONMENT
910
911 DBIx::Class::RecursiveUpdate requires no configuration files or environment variables.
912
913 =head1 DEPENDENCIES
914
915 DBIx::Class
916
917 optional but recommended:
918 DBIx::Class::IntrospectableM2M
919
920 =head1 INCOMPATIBILITIES
921
922 None reported.
923
924
925 =head1 BUGS AND LIMITATIONS
926
927 The list of reported bugs can be viewed at L<http://rt.cpan.org/Public/Dist/Display.html?Name=DBIx-Class-ResultSet-RecursiveUpdate>.
928
929 Please report any bugs or feature requests to
930 C<bug-dbix-class-recursiveput@rt.cpan.org>, or through the web interface at
931 L<http://rt.cpan.org>.
This page took 0.079359 seconds and 4 git commands to generate.