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