sub recursive_update {
my ( $self, $updates, $fixed_fields ) = @_;
-
# warn 'entering: ' . $self->result_source->from();
+
+ carp 'fixed fields needs to be an array ref' if $fixed_fields && ref($fixed_fields) ne 'ARRAY';
+ my %fixed_fields;
+ %fixed_fields = map { $_ => 1 } @$fixed_fields if $fixed_fields;
+
if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
return $updates;
}
- if ($fixed_fields) {
- carp if !( ref($fixed_fields) eq 'HASH' );
- $updates = { %$updates, %$fixed_fields };
- }
+
# direct column accessors
my %columns;
-# relations that that should be done before the row is inserted into the database
-# like belongs_to
+ # relations that that should be done before the row is inserted into the database
+ # like belongs_to
my %pre_updates;
-# relations that that should be done after the row is inserted into the database
-# like has_many and might_have
+ # relations that that should be done after the row is inserted into the database
+ # like has_many and might_have
my %post_updates;
my %columns_by_accessor = $self->_get_columns_by_accessor;
-# warn 'columns_by_accessor: ' . Dumper( \%columns_by_accessor ); use Data::Dumper;
for my $name ( keys %$updates ) {
my $source = $self->result_source;
if ( $columns_by_accessor{$name}
$post_updates{$name} = $updates->{$name};
}
}
-
# warn 'columns: ' . Dumper( \%columns ); use Data::Dumper;
my $object;
my @missing =
- grep { !exists $columns{$_} } $self->result_source->primary_columns;
+ grep { !exists $columns{$_} && !exists $fixed_fields{$_} } $self->result_source->primary_columns;
if ( !scalar @missing ) {
$object = $self->find( \%columns, { key => 'primary' } );
}
my $info = $object->result_source->relationship_info($name);
$self->_update_relation( $name, $updates, $object, $info );
}
- $self->_delete_empty_auto_increment($object);
+# $self->_delete_empty_auto_increment($object);
# don't allow insert to recurse to related objects - we do the recursion ourselves
# $object->{_rel_in_storage} = 1;
next if exists $columns{$name};
my $value = $updates->{$name};
- # many to many case
if ( $self->is_m2m($name) ) {
my ($pk) = $self->_get_pk_for_related($name);
my @rows;
# warn 'resolved: ' . Dumper( $resolved ); use Data::Dumper;
$resolved = undef
- if $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION == $resolved;
+ if defined $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION && $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION == $resolved;
if ( ref $updates->{$name} eq 'ARRAY' ) {
for my $sub_updates ( @{ $updates->{$name} } ) {
+ $sub_updates = { %$sub_updates, %$resolved } if $resolved && ref( $sub_updates ) eq 'HASH';
my $sub_object =
- $related_result->recursive_update( $sub_updates, $resolved );
+ $related_result->recursive_update( $sub_updates );
}
}
else {
+ my $sub_updates = $updates->{$name};
+ $sub_updates = { %$sub_updates, %$resolved } if $resolved && ref( $sub_updates ) eq 'HASH';
my $sub_object =
- $related_result->recursive_update( $updates->{$name}, $resolved );
+ $related_result->recursive_update( $sub_updates );
$object->set_from_related( $name, $sub_object );
}
}
id => 1,
owned_dvds => [
{
- id => undef,
title => 'One Flew Over the Cuckoo's Nest'
}
]
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).
-When creating new rows in a table with auto_increment primary keys you need to
-put 'undef' for the key value - this is then removed
-and a correct INSERT statement is generated.
+If not all colums comprising the primary key are specified - then 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,
+like in the case of:
+
+ my $restricted_rs = $user_rs->search( { id => 1 } );
+
+then you need to inform recursive_update about additional predicate with a second argument:
+
+ my $user = $restricted_rs->recursive_update( {
+ owned_dvds => [
+ {
+ title => 'One Flew Over the Cuckoo's Nest'
+ }
+ ]
+ },
+ [ 'id' ]
+ );
+
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
=head2 Treatment of many to many pseudo relations
-Matt Trout expressed following criticism of the support for many to many in
-RecursiveUpdate and since this is an extension of his DBIx::Class I feel obliged to
-reply to it. It is about two points leading in his opinion to 'fragile and
-implicitely broken code'.
-
-1. That I rely on the fact that
-
- if($object->can($name) and
+The function gets the information about m2m relations from DBIx::Class::IntrospectableM2M.
+If it is not loaded in the ResultSource classes - then the code relies on the fact that:
+ if($object->can($name) and
!$object->result_source->has_relationship($name) and
$object->can( 'set_' . $name )
)
similarly ugly was I find out what is the ResultSource of objects from
that many to many pseudo relation.
-2. That I treat uniformly relations and many to many (which are
-different from relations because they require traversal of the bridge
-table).
-
-To answer 1) I've refactored that 'dirty' code into is_m2m and get_m2m_source so
-that it can be easily overridden. I agree that this code is not too nice - but
-currenlty it is the only way to do what I need - and I'll replace it as soon as
-there is a more clean way. I don't think it is extremely brittle - sure it will
-break if many to many (pseudo) relations don't get 'set_*' methods anymore - but
-I would say it is rather justified for this kind of change in underlying library
-to break it.
-
-
-Ad 2) - first this is not strictly true - RecursiveUpdate does have
-different code to cope with m2m and other cases (see the point above for
-example) - but it let's the user to treat m2m and 'normal' relations in a
-uniform way. I consider this a form of abstraction - it is the work that
-RecursiveUpdate does for the programmer.
-
=head1 INTERFACE