X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRow.pm;h=d35d299a26202c0ef9bfdd3a07f12772faae39ae;hb=c00b00deb4354de9c59e46bd83f4a2b23b953425;hp=96c766ed5b150933f2158b6851b67bd449d8744d;hpb=3568822060ed84cfa974be736a3f0e049e9978a5;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index 96c766e..d35d299 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 + +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. -Creates a new row object from column => value mappings passed as a hash ref +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,20 +75,68 @@ 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)) { +# print STDERR "PK needs us\n"; +# print STDERR "Data: ", Data::Dumper::Dumper($data); + return $self->result_source + ->related_source($relname) + ->resultset + ->new_result($data); + } + if ($self->result_source->pk_depends_on($relname, $data)) { +# print STDERR "PK depends on\n"; + return $self->result_source + ->related_source($relname) + ->resultset + ->find_or_create($data); + } +# print STDERR "Neither, find_or_new\n"; + 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); +# print STDERR "Found reverse rel info: ", Data::Dumper::Dumper($reverse); + my $rel_source = $source->related_source($relname); + my $us = { $self->get_columns }; +# print STDERR "Test on self cols: ", Data::Dumper::Dumper($us); + 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 + my $dep = $rel_source->pk_depends_on($key, $us); + if($dep) { +# print STDERR "Assigning $self to $key\n"; + $data->{$key} = $self; + return 1; + } +# return 1 if $rel_source->pk_depends_on($key, $us); + } + return 0; +} + sub new { my ($class, $attrs) = @_; $class = ref $class if ref $class; - my $new = { _column_data => {} }; + my $new = { + _column_data => {}, + }; bless $new, $class; 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); } +# print "Source ", $source->source_name, " is $new\n"; if ($attrs) { $new->throw_exception("attrs must be a hashref") unless ref($attrs) eq 'HASH'; @@ -71,33 +148,40 @@ 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') { +# print STDERR "Single $key ", Data::Dumper::Dumper($attrs); +# print STDERR "from $class to: $info->{class}\n"; 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; +# print STDERR "Related :", join(", ", keys %$related), "\n"; next; } elsif ($info && $info->{attrs}{accessor} && $info->{attrs}{accessor} eq 'multi' && ref $attrs->{$key} eq 'ARRAY') { +# print STDERR "Multi $key ", Data::Dumper::Dumper($attrs); +# print STDERR "from $class to: $info->{class}\n"; 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; +# print STDERR "Related :", join(", ", keys %$related), "\n"; next; } elsif ($info && $info->{attrs}{accessor} && $info->{attrs}{accessor} eq 'filter') @@ -105,9 +189,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) @@ -115,8 +199,8 @@ sub new { $inflated->{$key} = $attrs->{$key}; next; } +# print STDERR "Done :", join(", ", keys %$related), "\n"; } - use Data::Dumper; $new->throw_exception("No such column $key on $class") unless $class->has_column($key); $new->store_column($key => $attrs->{$key}); @@ -131,13 +215,24 @@ sub new { =head2 insert - $obj->insert; + $row->insert; -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). +=over + +=item Arguments: none + +=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. This will also insert any uninserted, related objects held inside this one, see L for more details. @@ -160,11 +255,9 @@ sub insert { %{$self->{_inflated_column} || {}}); if(!$self->{_rel_in_storage}) { - $source->storage->txn_begin; # The guard will save us if we blow out of this scope via die - - $rollback_guard = Scope::Guard->new(sub { $source->storage->txn_rollback }); + $rollback_guard = $source->storage->txn_scope_guard; ## Should all be in relationship_data, but we need to get rid of the ## 'filter' reltype.. @@ -173,41 +266,33 @@ sub insert { my @pri = $self->primary_columns; REL: foreach my $relname (keys %related_stuff) { - +# print STDERR "Looking at: $relname\n"; my $rel_obj = $related_stuff{$relname}; 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; - } - } - } - +# print STDERR "Check pk: from ", $source->source_name, " to $relname\n"; +# print STDERR "With ", Data::Dumper::Dumper({ $rel_obj->get_columns }); + next REL unless $source->pk_depends_on( + $relname, { $rel_obj->get_columns } + ); +# print STDERR "$rel_obj\n"; +# print STDERR "in_storage: ", $rel_obj->in_storage, "\n"; +# print STDERR "Inserting $relname\n"; $rel_obj->insert(); $self->set_from_related($relname, $rel_obj); delete $related_stuff{$relname}; } } - $source->storage->insert($source, { $self->get_columns }); +# print STDERR "self $self\n"; +# print STDERR "self in_storage ", $self->in_storage, "\n"; +# print STDERR "Ran out of rels, insert ", $source->source_name, "\n"; + my $updated_cols = $source->storage->insert($source, { $self->get_columns }); + $self->set_columns($updated_cols); + $self->in_storage(1); +# print STDERR "$self\n"; ## PK::Auto my @auto_pri = grep { @@ -228,6 +313,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) { @@ -243,27 +331,48 @@ 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 }; + my $them = { $obj->get_inflated_columns }; +# print STDERR "Does $relname need our PK?\n"; + if ($self->__their_pk_needs_us($relname, $them)) { +# print STDERR "Yes\n"; + # $obj = $self->find_or_create_related($relname, $them); + $obj->insert(); + } else { +# print STDERR "No\n"; + $obj->insert(); + } } } } - $source->storage->txn_commit; - $rollback_guard->dismiss; + $rollback_guard->commit; } - $self->in_storage(1); - $self->{_dirty_columns} = {}; - $self->{related_resultsets} = {}; +# $self->in_storage(1); 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, +L or L +are used. -Indicates whether the object exists as a row in the database or not +Creating a row object using L, or calling +L on one, sets it to false. =cut @@ -275,16 +384,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. + +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 } } ) -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. +If the values passed or any of the column values set on the object +contain scalar references, eg: -Also takes an options hashref of C<< column_name => value> pairs >> to update -first. But be aware that this hashref might be edited 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 } } ) + $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 @@ -295,38 +439,7 @@ sub update { $self->throw_exception("Cannot safely update a row in a PK-less table") if ! keys %$ident_cond; - if ($upd) { - foreach my $key (keys %$upd) { - if (ref $upd->{$key}) { - my $info = $self->relationship_info($key); - if ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'single') - { - my $rel = delete $upd->{$key}; - $self->set_from_related($key => $rel); - $self->{_relationship_data}{$key} = $rel; - } elsif ($info && $info->{attrs}{accessor} - && $info->{attrs}{accessor} eq 'multi' - && ref $upd->{$key} eq 'ARRAY') { - my $others = delete $upd->{$key}; - foreach my $rel_obj (@$others) { - if(!Scalar::Util::blessed($rel_obj)) { - $rel_obj = $self->create_related($key, $rel_obj); - } - } - $self->{_relationship_data}{$key} = $others; -# $related->{$key} = $others; - next; - } - elsif ($self->has_column($key) - && exists $self->column_info($key)->{_inflate_info}) - { - $self->set_inflated_column($key, delete $upd->{$key}); - } - } - } - $self->set_columns($upd); - } + $self->set_inflated_columns($upd) if $upd; my %to_update = $self->get_dirty_columns; return $self unless keys %to_update; my $rows = $self->result_source->storage->update( @@ -346,16 +459,32 @@ 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. -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 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. +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. + +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. + +See also L. =cut @@ -363,7 +492,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) { @@ -385,12 +514,31 @@ 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. -Gets a column value from a row object. Does not do any queries; the column -must have already been fetched from the database and stored in the object. If -there is an inflated value stored that has not yet been deflated, it is deflated -when the method is invoked. +Returns a raw column value from the row object, if it has already +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