X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRow.pm;h=ba05001e5666c5b1693e046be011cc1da543e8ca;hb=0e80c4ca9995674bcc86bc59780b940ce20c48d2;hp=6768ea62394a3462cc01ad4e8b21bbafcbdf5a0f;hpb=aed5b8a46a2e48adeeb790c06906c4d48f190043;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index 6768ea6..ba05001 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -21,13 +21,42 @@ DBIx::Class::Row - Basic row methods This class is responsible for defining and doing basic operations on rows derived from L objects. +Row objects are returned from Ls using the +L, L, +L and L methods, +as well as invocations of 'single' ( +L, +L or +L) +relationship accessors of L objects. + =head1 METHODS =head2 new - my $obj = My::Class->new($attrs); + my $row = My::Class->new(\%attrs); + + my $row = $schema->resultset('MySource')->new(\%colsandvalues); + +=over + +=item Arguments: \%attrs or \%colsandvalues + +=item Returns: A Row object + +=back -Creates a new row object from column => value mappings passed as a hash ref +While you can create a new row object by calling C directly on +this class, you are better off calling it on a +L object. + +When calling it directly, you will not get a complete, usable row +object until you pass or set the C attribute, to a +L instance that is attached to a +L with a valid connection. + +C<$attrs> is a hashref of column name, value data. It can also contain +some other attributes such as the C. Passing an object, or an arrayref of objects as a value will call L for you. When @@ -46,6 +75,37 @@ For a more involved explanation, see L. ## check Relationship::CascadeActions and Relationship::Accessor for compat ## tests! +sub __new_related_find_or_new_helper { + my ($self, $relname, $data) = @_; + if ($self->__their_pk_needs_us($relname, $data)) { + return $self->result_source + ->related_source($relname) + ->resultset + ->new_result($data); + } + if ($self->result_source->pk_depends_on($relname, $data)) { + return $self->result_source + ->related_source($relname) + ->resultset + ->find_or_create($data); + } + return $self->find_or_new_related($relname, $data); +} + +sub __their_pk_needs_us { # this should maybe be in resultsource. + my ($self, $relname, $data) = @_; + my $source = $self->result_source; + my $reverse = $source->reverse_relationship_info($relname); + my $rel_source = $source->related_source($relname); + my $us = { $self->get_columns }; + foreach my $key (keys %$reverse) { + # if their primary key depends on us, then we have to + # just create a result and we'll fill it out afterwards + return 1 if $rel_source->pk_depends_on($key, $us); + } + return 0; +} + sub new { my ($class, $attrs) = @_; $class = ref $class if ref $class; @@ -58,7 +118,9 @@ sub new { if (my $handle = delete $attrs->{-source_handle}) { $new->_source_handle($handle); } - if (my $source = delete $attrs->{-result_source}) { + + my $source; + if ($source = delete $attrs->{-result_source}) { $new->result_source($source); } @@ -73,18 +135,19 @@ sub new { foreach my $key (keys %$attrs) { if (ref $attrs->{$key}) { ## Can we extract this lot to use with update(_or .. ) ? - my $info = $class->relationship_info($key); + confess "Can't do multi-create without result source" unless $source; + my $info = $source->relationship_info($key); if ($info && $info->{attrs}{accessor} && $info->{attrs}{accessor} eq 'single') { my $rel_obj = delete $attrs->{$key}; if(!Scalar::Util::blessed($rel_obj)) { - $rel_obj = $new->find_or_new_related($key, $rel_obj); + $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); } $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage); - $new->set_from_related($key, $rel_obj); + $new->set_from_related($key, $rel_obj) if $rel_obj->in_storage; $related->{$key} = $rel_obj; next; } elsif ($info && $info->{attrs}{accessor} @@ -93,11 +156,11 @@ sub new { my $others = delete $attrs->{$key}; foreach my $rel_obj (@$others) { if(!Scalar::Util::blessed($rel_obj)) { - $rel_obj = $new->new_related($key, $rel_obj); - $new->{_rel_in_storage} = 0; + $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); } $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage); + $new->set_from_related($key, $rel_obj) if $rel_obj->in_storage; } $related->{$key} = $others; next; @@ -107,9 +170,9 @@ sub new { ## 'filter' should disappear and get merged in with 'single' above! my $rel_obj = delete $attrs->{$key}; if(!Scalar::Util::blessed($rel_obj)) { - $rel_obj = $new->find_or_new_related($key, $rel_obj); - $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage); + $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); } + $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage); $inflated->{$key} = $rel_obj; next; } elsif ($class->has_column($key) @@ -132,13 +195,21 @@ sub new { =head2 insert - $obj->insert; + $row->insert; + +=over + +=item Arguments: none -Inserts an object into the database if it isn't already in -there. Returns the object itself. Requires the object's result source to -be set, or the class to have a result_source_instance method. To insert -an entirely new object into the database, use C (see -L). +=item Returns: The Row object + +=back + +Inserts an object previously created by L into the database if +it isn't already in there. Returns the object itself. Requires the +object's result source to be set, or the class to have a +result_source_instance method. To insert an entirely new row into +the database, use C (see L). To fetch an uninserted row object, call L on a resultset. @@ -181,27 +252,9 @@ sub insert { next REL unless (Scalar::Util::blessed($rel_obj) && $rel_obj->isa('DBIx::Class::Row')); - my $cond = $source->relationship_info($relname)->{cond}; - - next REL unless ref($cond) eq 'HASH'; - - # map { foreign.foo => 'self.bar' } to { bar => 'foo' } - - my $keyhash = { map { my $x = $_; $x =~ s/.*\.//; $x; } reverse %$cond }; - - # assume anything that references our PK probably is dependent on us - # rather than vice versa, unless the far side is (a) defined or (b) - # auto-increment - - foreach my $p (@pri) { - if (exists $keyhash->{$p}) { - unless (defined($rel_obj->get_column($keyhash->{$p})) - || $rel_obj->column_info($keyhash->{$p}) - ->{is_auto_increment}) { - next REL; - } - } - } + next REL unless $source->pk_depends_on( + $relname, { $rel_obj->get_columns } + ); $rel_obj->insert(); $self->set_from_related($relname, $rel_obj); @@ -209,7 +262,10 @@ sub insert { } } - $source->storage->insert($source, { $self->get_columns }); + my $updated_cols = $source->storage->insert($source, { $self->get_columns }); + foreach my $col (keys %$updated_cols) { + $self->store_column($col, $updated_cols->{$col}); + } ## PK::Auto my @auto_pri = grep { @@ -230,6 +286,9 @@ sub insert { $self->store_column($auto_pri[$_] => $ids[$_]) for 0 .. $#ids; } + $self->{_dirty_columns} = {}; + $self->{related_resultsets} = {}; + if(!$self->{_rel_in_storage}) { ## Now do the has_many rels, that need $selfs ID. foreach my $relname (keys %related_stuff) { @@ -245,7 +304,12 @@ sub insert { my $reverse = $source->reverse_relationship_info($relname); foreach my $obj (@cands) { $obj->set_from_related($_, $self) for keys %$reverse; - $obj->insert() unless ($obj->in_storage || $obj->result_source->resultset->search({$obj->get_columns})->count); + my $them = { %{$obj->{_relationship_data} || {} }, $obj->get_inflated_columns }; + if ($self->__their_pk_needs_us($relname, $them)) { + $obj = $self->find_or_create_related($relname, $them); + } else { + $obj->insert(); + } } } } @@ -253,16 +317,22 @@ sub insert { } $self->in_storage(1); - $self->{_dirty_columns} = {}; - $self->{related_resultsets} = {}; undef $self->{_orig_ident}; return $self; } =head2 in_storage - $obj->in_storage; # Get value - $obj->in_storage(1); # Set value + $row->in_storage; # Get value + $row->in_storage(1); # Set value + +=over + +=item Arguments: none or 1|0 + +=item Returns: 1|0 + +=back Indicates whether the object exists as a row in the database or not. This is set to true when L, @@ -282,17 +352,51 @@ sub in_storage { =head2 update - $obj->update \%columns?; + $row->update(\%columns?) + +=over + +=item Arguments: none or a hashref + +=item Returns: The Row object + +=back + +Throws an exception if the row object is not yet in the database, +according to L. + +This method issues an SQL UPDATE query to commit any changes to the +object to the database if required. -Must be run on an object that is already in the database; issues an SQL -UPDATE query to commit any changes to the object to the database if -required. +Also takes an optional hashref of C<< column_name => value> >> pairs +to update on the object first. Be aware that the hashref will be +passed to C, which might edit it in place, so +don't rely on it being the same after a call to C. If you +need to preserve the hashref, it is sufficient to pass a shallow copy +to C, e.g. ( { %{ $href } } ) -Also takes an options hashref of C<< column_name => value> pairs >> to update -first. But be aware that the hashref will be passed to -C, which might edit it in place, so dont rely on it being -the same after a call to C. If you need to preserve the hashref, it is -sufficient to pass a shallow copy to C, e.g. ( { %{ $href } } ) +If the values passed or any of the column values set on the object +contain scalar references, eg: + + $row->last_modified(\'NOW()'); + # OR + $row->update({ last_modified => \'NOW()' }); + +The update will pass the values verbatim into SQL. (See +L docs). The values in your Row object will NOT change +as a result of the update call, if you want the object to be updated +with the actual values from the database, call L +after the update. + + $row->update()->discard_changes(); + +To determine before calling this method, which column values have +changed and will be updated, call L. + +To check if any columns will be updated, call L. + +To force a column to be updated, call L before +this method. =cut @@ -323,16 +427,40 @@ sub update { =head2 delete - $obj->delete + $row->delete + +=over + +=item Arguments: none + +=item Returns: The Row object + +=back + +Throws an exception if the object is not in the database according to +L. Runs an SQL DELETE statement using the primary key +values to locate the row. + +The object is still perfectly usable, but L will +now return 0 and the object must be reinserted using L +before it can be used to L the row again. -Deletes the object from the database. The object is still perfectly -usable, but C<< ->in_storage() >> will now return 0 and the object must -reinserted using C<< ->insert() >> before C<< ->update() >> can be used -on it. If you delete an object in a class with a C -relationship, all the related objects will be deleted as well. To turn -this behavior off, pass C<< cascade_delete => 0 >> in the C<$attr> -hashref. Any database-level cascade or restrict will take precedence -over a DBIx-Class-based cascading delete. See also L. +If you delete an object in a class with a C relationship, an +attempt is made to delete all the related objects as well. To turn +this behaviour off, pass C<< cascade_delete => 0 >> in the C<$attr> +hashref of the relationship, see L. Any +database-level cascade or restrict will take precedence over a +DBIx-Class-based cascading delete. + +If you delete an object within a txn_do() (see L) +and the transaction subsequently fails, the row object will remain marked as +not being in storage. If you know for a fact that the object is still in +storage (i.e. by inspecting the cause of the transaction's failure), you can +use C<< $obj->in_storage(1) >> to restore consistency between the object and +the database. This would allow a subsequent C<< $obj->delete >> to work +as expected. + +See also L. =cut @@ -340,7 +468,7 @@ sub delete { my $self = shift; if (ref $self) { $self->throw_exception( "Not in database" ) unless $self->in_storage; - my $ident_cond = $self->ident_condition; + my $ident_cond = $self->{_orig_ident} || $self->ident_condition; $self->throw_exception("Cannot safely delete a row in a PK-less table") if ! keys %$ident_cond; foreach my $column (keys %$ident_cond) { @@ -362,7 +490,18 @@ sub delete { =head2 get_column - my $val = $obj->get_column($col); + my $val = $row->get_column($col); + +=over + +=item Arguments: $columnname + +=item Returns: The value of the column + +=back + +Throws an exception if the column name given doesn't exist according +to L. Returns a raw column value from the row object, if it has already been fetched from the database or set by an accessor. @@ -370,6 +509,13 @@ been fetched from the database or set by an accessor. If an L has been set, it will be deflated and returned. +Note that if you used the C or the C