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