X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fp5-DBIx-Class-ResultSet-RecursiveUpdate;a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet%2FRecursiveUpdate.pm;h=67a7ffd602bf7605eaa33d06b045a14343e68916;hp=4e4c370ea4c972b900660cc90938b1aa9b2e2845;hb=6a543315be718c91b051b5a0fbd4100892dda59f;hpb=75bcf6b8626125cd29e1bb4846022f9de080bd70 diff --git a/lib/DBIx/Class/ResultSet/RecursiveUpdate.pm b/lib/DBIx/Class/ResultSet/RecursiveUpdate.pm index 4e4c370..67a7ffd 100644 --- a/lib/DBIx/Class/ResultSet/RecursiveUpdate.pm +++ b/lib/DBIx/Class/ResultSet/RecursiveUpdate.pm @@ -34,7 +34,7 @@ sub recursive_update { } package DBIx::Class::ResultSet::RecursiveUpdate::Functions; -use Carp::Clan; +use Carp::Clan qw/^DBIx::Class|^HTML::FormHandler|^Try::Tiny/; use Scalar::Util qw( blessed ); use List::MoreUtils qw/ any /; @@ -47,22 +47,27 @@ sub recursive_update { }; $resolved ||= {}; - # warn 'entering: ' . $self->result_source->from(); + my $source = $self->result_source; + + # warn 'entering: ' . $source->from(); carp 'fixed fields needs to be an array ref' if defined $fixed_fields && ref $fixed_fields ne 'ARRAY'; if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) { return $updates; } - if ( $updates->{id} ) { + if ( exists $updates->{id} ) { + # warn "finding object by id " . $updates->{id} . "\n"; $object = $self->find( $updates->{id}, { key => 'primary' } ); + # warn "object not found by id\n" + # unless defined $object; } my %fixed_fields = map { $_ => 1 } @$fixed_fields if $fixed_fields; my @missing = grep { !exists $updates->{$_} && !exists $fixed_fields{$_} } - $self->result_source->primary_columns; + $source->primary_columns; if ( !$object && !scalar @missing ) { # warn 'finding by: ' . Dumper( $updates ); use Data::Dumper; @@ -76,7 +81,9 @@ sub recursive_update { # warn 'finding by +resolved: ' . Dumper( $updates ); use Data::Dumper; $object = $self->find( $updates, { key => 'primary' } ); } - $object ||= $self->new( {} ); + + $object = $self->new( {} ) + unless defined $object; # warn Dumper( $updates ); use Data::Dumper; # direct column accessors @@ -96,8 +103,6 @@ sub recursive_update { # warn 'updates: ' . Dumper( $updates ); use Data::Dumper; # warn 'columns: ' . Dumper( \%columns_by_accessor ); for my $name ( keys %$updates ) { - my $source = $self->result_source; - # columns if ( exists $columns_by_accessor{$name} && !( $source->has_relationship($name) @@ -575,6 +580,9 @@ __END__ unknown_params_ok => 1, }); + # You'll get a warning if you pass non-result specific data to + # recursive_update. See L + # for more information how to prevent this. =head1 DESCRIPTION @@ -582,28 +590,27 @@ This is still experimental. You can feed the ->create method of DBIx::Class with a recursive datastructure and have the related records created. Unfortunately you cannot do a similar -thing with update_or_create. -This module tries to fill that void until L has an api itself. +thing with update_or_create. This module tries to fill that void until +L has an api itself. The functional interface can be used without modifications of the model, for example by form processors like L. -It is a base class for Ls providing the method recursive_update -which works just like update_or_create but can recursively update or create -data objects composed of multiple rows. All rows need to be identified by primary keys -- so you need to provide them in the update structure (unless they can be deduced from -the parent row - for example when you have a belongs_to relationship). -If not all columns comprising the primary key are specified a new row will be created, -with the expectation that the missing columns will be filled by it (as in the case of auto_increment -primary keys). - +It is a base class for Ls providing the method +recursive_update which works just like update_or_create but can recursively +update or create result objects composed of multiple rows. All rows need to be +identified by primary keys so you need to provide them in the update structure +(unless they can be deduced from the parent row. For example a related row of +a belongs_to relationship). If any of the primary key columns are missing, +a new row will be created, with the expectation that the missing columns will +be filled by it (as in the case of auto_increment primary keys). -If the resultset itself stores an assignement for the primary key, +If the resultset itself stores an assignment for the primary key, like in the case of: my $restricted_rs = $user_rs->search( { id => 1 } ); -then you need to inform recursive_update about the additional predicate with the fixed_fields attribute: +you need to inform recursive_update about the additional predicate with the fixed_fields attribute: my $user = $restricted_rs->recursive_update( { owned_dvds => [ @@ -619,11 +626,30 @@ then you need to inform recursive_update about the additional predicate with the For a many_to_many (pseudo) relation you can supply a list of primary keys from the other table and it will link the record at hand to those and -only those records identified by them. This is convenient for handling web +only those records identified by them. This is convenient for handling web forms with check boxes (or a select field with multiple choice) that lets you update such (pseudo) relations. -For a description how to set up base classes for ResultSets see L. +For a description how to set up base classes for ResultSets see +L. + +=head2 Additional data in the updates hashref + +If you pass additional data to recursive_update which doesn't match a column +name, column accessor, relationship or many-to-many helper accessor, it will +throw a warning by default. To disable this behaviour you can set the +unknown_params_ok attribute to a true value. + +The warning thrown is: +"No such column, relationship, many-to-many helper accessor or generic accessor '$key'" + +When used by L this can happen if you have +additional form fields that aren't relevant to the database but don't have the +noupdate attribute set to a true value. + +NOTE: in a future version this behaviour will change and throw an exception +instead of a warning! + =head1 DESIGN CHOICES @@ -694,6 +720,67 @@ In this case recursive_update defaults to nullifying the foreign columns. In this case recursive_update deletes the foreign rows. +Updating the relationship: + + Passing ids: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => [1, 2], + }); + + Passing hashrefs: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => [ + { + name => 'temp name 1', + }, + { + name => 'temp name 2', + }, + ], + }); + + Passing objects: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => [ $dvd1, $dvd2 ], + }); + + You can even mix them: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => [ 1, { id => 2 } ], + }); + +Clearing the relationship: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => undef, + }); + + This is the same as passing an empty array: + + my $user = $user_rs->recursive_update( { + id => 1, + owned_dvds => [], + }); + +=head2 Treatment of many-to-many pseudo relations + +If a many-to-many accessor key is included in the data structure with a value +of undef or an empty array, all existing related rows are unlinked. + +When the array contains elements they are updated if they exist, created when +not and deleted if not included. + +See L for many-to-many pseudo relationship detection. + Updating the relationship: Passing ids: @@ -721,13 +808,16 @@ Updating the relationship: Passing objects: - TODO + my $dvd = $dvd_rs->recursive_update( { + id => 1, + tags => [ $tag1, $tag2 ], + }); You can even mix them: my $dvd = $dvd_rs->recursive_update( { id => 1, - tags => [ '2', { id => '3' } ], + tags => [ 2, { id => 3 } ], }); Clearing the relationship: @@ -744,20 +834,6 @@ Clearing the relationship: tags => [], }); -=head2 Treatment of many-to-many pseudo relations - -The function gets the information about m2m relations from L. -If it isn't loaded in the ResultSource classes the code relies on the fact that: - - if($object->can($name) and - !$object->result_source->has_relationship($name) and - $object->can( 'set_' . $name ) - ) - -Then $name must be a many to many pseudo relation. -And that in a similarly ugly was I find out what is the ResultSource of -objects from that many to many pseudo relation. - =head1 INTERFACE @@ -769,17 +845,39 @@ The method that does the work here. =head2 is_m2m -$self->is_m2m( 'name ' ) - answers the question if 'name' is a many to many -(pseudo) relation on $self. +=over 4 + +=item Arguments: $name + +=item Return Value: true, if $name is a many to many pseudo-relationship + +=back + +The function gets the information about m2m relations from +L. If it isn't loaded in the ResultSource +class, the code relies on the fact: + + if($object->can($name) and + !$object->result_source->has_relationship($name) and + $object->can( 'set_' . $name ) + ) + +to identify a many to many pseudo relationship. In a similar ugly way the +ResultSource of that many to many pseudo relationship is detected. + +So if you need many to many pseudo relationship support, it's strongly +recommended to load L in your ResultSource +class! =head2 get_m2m_source -$self->get_m2m_source( 'name' ) - returns the ResultSource linked to by the many -to many (pseudo) relation 'name' from $self. +=over 4 +=item Arguments: $name -=head1 DIAGNOSTICS +=item Return Value: $result_source +=back =head1 CONFIGURATION AND ENVIRONMENT @@ -789,6 +887,9 @@ DBIx::Class::RecursiveUpdate requires no configuration files or environment vari DBIx::Class +optional but recommended: + DBIx::Class::IntrospectableM2M + =head1 INCOMPATIBILITIES None reported. @@ -796,7 +897,7 @@ None reported. =head1 BUGS AND LIMITATIONS -No bugs have been reported. +The list of reported bugs can be viewed at L. Please report any bugs or feature requests to C, or through the web interface at