backporting the set_column/store_column fix
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Row.pm
index 1bc569f..ba05001 100644 (file)
@@ -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<DBIx::Class::ResultSource> objects.
 
+Row objects are returned from L<DBIx::Class::ResultSet>s using the
+L<create|DBIx::Class::ResultSet/create>, L<find|DBIx::Class::ResultSet/find>,
+L<next|DBIx::Class::ResultSet/next> and L<all|DBIx::Class::ResultSet/all> methods,
+as well as invocations of 'single' (
+L<belongs_to|DBIx::Class::Relationship/belongs_to>,
+L<has_one|DBIx::Class::Relationship/has_one> or
+L<might_have|DBIx::Class::Relationship/might_have>)
+relationship accessors of L<DBIx::Class::Row> 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<new> directly on
+this class, you are better off calling it on a
+L<DBIx::Class::ResultSet> object.
 
-Creates a new row object from column => value mappings passed as a hash ref
+When calling it directly, you will not get a complete, usable row
+object until you pass or set the C<source_handle> attribute, to a
+L<DBIx::Class::ResultSource> instance that is attached to a
+L<DBIx::Class::Schema> 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<source_handle>.
 
 Passing an object, or an arrayref of objects as a value will call
 L<DBIx::Class::Relationship::Base/set_from_related> for you. When
@@ -46,6 +75,37 @@ For a more involved explanation, see L<DBIx::Class::ResultSet/create>.
 ## 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,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<create> (see
-L<DBIx::Class::ResultSet/create>).
+=over
+
+=item Arguments: none
+
+=item Returns: The Row object
+
+=back
+
+Inserts an object previously created by L</new> 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<create> (see L<DBIx::Class::ResultSet/create>).
+
+To fetch an uninserted row object, call
+L<new|DBIx::Class::ResultSet/new> on a resultset.
 
 This will also insert any uninserted, related objects held inside this
 one, see L<DBIx::Class::ResultSet/create> for more details.
@@ -161,11 +235,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..
@@ -180,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);
@@ -208,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 {
@@ -229,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) {
@@ -244,27 +304,43 @@ 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();
+          }
         }
       }
     }
-    $source->storage->txn_commit;
-    $rollback_guard->dismiss;
+    $rollback_guard->commit;
   }
 
   $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
 
-Indicates whether the object exists as a row in the database or not
+=back
+
+Indicates whether the object exists as a row in the database or
+not. This is set to true when L<DBIx::Class::ResultSet/find>,
+L<DBIx::Class::ResultSet/create> or L<DBIx::Class::ResultSet/insert>
+are used. 
+
+Creating a row object using L<DBIx::Class::ResultSet/new>, or calling
+L</delete> on one, sets it to false.
 
 =cut
 
@@ -276,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
 
-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.
+Throws an exception if the row object is not yet in the database,
+according to L</in_storage>.
 
-Also takes an options hashref of C<< column_name => value> pairs >> to update
-first. But be awawre that the hashref will be passed to
-C<set_inflated_columns>, which might edit it in place, so dont rely on it being
-the same after a call to C<update>.  If you need to preserve the hashref, it is
-sufficient to pass a shallow copy to C<update>, e.g. ( { %{ $href } } )
+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<set_inflated_columns>, which might edit it in place, so
+don't rely on it being the same after a call to C<update>.  If you
+need to preserve the hashref, it is sufficient to pass a shallow copy
+to C<update>, 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<SQL::Abstract> 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</discard_changes>
+after the update.
+
+  $row->update()->discard_changes();
+
+To determine before calling this method, which column values have
+changed and will be updated, call L</get_dirty_columns>.
+
+To check if any columns will be updated, call L</is_changed>.
+
+To force a column to be updated, call L</make_column_dirty> before
+this method.
 
 =cut
 
@@ -317,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</in_storage>. 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<has_many>
-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<DBIx::Class::ResultSet/delete>.
+The object is still perfectly usable, but L</in_storage> will
+now return 0 and the object must be reinserted using L</insert>
+before it can be used to L</update> the row again. 
+
+If you delete an object in a class with a C<has_many> 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<DBIx::Class::Relationship>. 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<DBIx::Class::Storage/txn_do>)
+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<DBIx::Class::ResultSet/delete>.
 
 =cut
 
@@ -334,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) {
@@ -356,12 +490,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
 
-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.
+=back
+
+Throws an exception if the column name given doesn't exist according
+to L</has_column>.
+
+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<inflated value|DBIx::Class::InflateColumn> has been set, it
+will be deflated and returned.
+
+Note that if you used the C<columns> or the C<select/as>
+L<search attributes|DBIx::Class::ResultSet/ATTRIBUTES> on the resultset from
+which C<$row> was derived, and B<did not include> C<$columnname> in the list,
+this method will return C<undef> even if the database contains some value.
+
+To retrieve all loaded column values as a hash, use L</get_columns>.
 
 =cut
 
@@ -379,10 +532,18 @@ sub get_column {
 
 =head2 has_column_loaded
 
-  if ( $obj->has_column_loaded($col) ) {
+  if ( $row->has_column_loaded($col) ) {
      print "$col has been loaded from db";
   }
 
+=over
+
+=item Arguments: $columnname
+
+=item Returns: 0|1
+
+=back
+
 Returns a true value if the column value has been loaded from the
 database (or set locally).
 
@@ -397,9 +558,18 @@ sub has_column_loaded {
 
 =head2 get_columns
 
-  my %data = $obj->get_columns;
+  my %data = $row->get_columns;
+
+=over
+
+=item Arguments: none
+
+=item Returns: A hash of columnname, value pairs.
 
-Does C<get_column>, for all column values at once.
+=back
+
+Returns all loaded column data as a hash, containing raw values. To
+get just one value for a particular column, use L</get_column>.
 
 =cut
 
@@ -416,9 +586,20 @@ sub get_columns {
 
 =head2 get_dirty_columns
 
-  my %data = $obj->get_dirty_columns;
+  my %data = $row->get_dirty_columns;
+
+=over
+
+=item Arguments: none
+
+=item Returns: A hash of column, value pairs
+
+=back
+
+Only returns the column, value pairs for those columns that have been
+changed on this object since the last L</update> or L</insert> call.
 
-Identical to get_columns but only returns those that have been changed.
+See L</get_columns> to fetch all column/value pairs.
 
 =cut
 
@@ -428,11 +609,50 @@ sub get_dirty_columns {
            keys %{$self->{_dirty_columns}};
 }
 
+=head2 make_column_dirty
+
+  $row->make_column_dirty($col)
+
+=over
+
+=item Arguments: $columnname
+
+=item Returns: undefined
+
+=back
+
+Throws an exception if the column does not exist.
+
+Marks a column as having been changed regardless of whether it has
+really changed.  
+
+=cut
+sub make_column_dirty {
+  my ($self, $column) = @_;
+
+  $self->throw_exception( "No such column '${column}'" )
+    unless exists $self->{_column_data}{$column} || $self->has_column($column);
+  $self->{_dirty_columns}{$column} = 1;
+}
+
 =head2 get_inflated_columns
 
-  my $inflated_data = $obj->get_inflated_columns;
+  my %inflated_data = $obj->get_inflated_columns;
+
+=over
+
+=item Arguments: none
+
+=item Returns: A hash of column, object|value pairs
+
+=back
 
-Similar to get_columns but objects are returned for inflated columns instead of their raw non-inflated values.
+Returns a hash of all column keys and associated values. Values for any
+columns set to use inflation will be inflated and returns as objects.
+
+See L</get_columns> to get the uninflated values.
+
+See L<DBIx::Class::InflateColumn> for how to setup inflation.
 
 =cut
 
@@ -446,33 +666,57 @@ sub get_inflated_columns {
 
 =head2 set_column
 
-  $obj->set_column($col => $val);
+  $row->set_column($col => $val);
+
+=over
+
+=item Arguments: $columnname, $value
+
+=item Returns: $value
+
+=back
 
-Sets a column value. If the new value is different from the old one,
-the column is marked as dirty for when you next call $obj->update.
+Sets a raw column value. If the new value is different from the old one,
+the column is marked as dirty for when you next call L</update>.
+
+If passed an object or reference as a value, this method will happily
+attempt to store it, and a later L</insert> or L</update> will try and
+stringify/numify as appropriate. To set an object to be deflated
+instead, see L</set_inflated_columns>.
 
 =cut
 
 sub set_column {
-  my $self = shift;
-  my ($column) = @_;
+  my ($self, $column, $new_value) = @_;
+
   $self->{_orig_ident} ||= $self->ident_condition;
-  my $old = $self->get_column($column);
-  my $ret = $self->store_column(@_);
+  my $old_value = $self->get_column($column);
+
+  $self->store_column($column, $new_value);
   $self->{_dirty_columns}{$column} = 1
-    if (defined $old xor defined $ret) || (defined $old && $old ne $ret);
+    if (defined $old_value xor defined $new_value) || (defined $old_value && $old_value ne $new_value);
 
   # XXX clear out the relation cache for this column
   delete $self->{related_resultsets}{$column};
 
-  return $ret;
+  return $new_value;
 }
 
 =head2 set_columns
 
-  my $copy = $orig->set_columns({ $col => $val, ... });
+  $row->set_columns({ $col => $val, ... });
+
+=over 
+
+=item Arguments: \%columndata
 
-Sets more than one column value at once.
+=item Returns: The Row object
+
+=back
+
+Sets multiple column, raw value pairs at once.
+
+Works as L</set_column>.
 
 =cut
 
@@ -486,13 +730,34 @@ sub set_columns {
 
 =head2 set_inflated_columns
 
-  my $copy = $orig->set_inflated_columns({ $col => $val, $rel => $obj, ... });
+  $row->set_inflated_columns({ $col => $val, $relname => $obj, ... });
+
+=over
+
+=item Arguments: \%columndata
+
+=item Returns: The Row object
 
-Sets more than one column value at once, taking care to respect inflations and
-relationships if relevant. Be aware that this hashref might be edited in place,
-so dont rely on it being the same after a call to C<set_inflated_columns>. If
-you need to preserve the hashref, it is sufficient to pass a shallow copy to
-C<set_inflated_columns>, e.g. ( { %{ $href } } )
+=back
+
+Sets more than one column value at once. Any inflated values are
+deflated and the raw values stored. 
+
+Any related values passed as Row objects, using the relation name as a
+key, are reduced to the appropriate foreign key values and stored. If
+instead of related row objects, a hashref of column, value data is
+passed, will create the related object first then store.
+
+Will even accept arrayrefs of data as a value to a
+L<DBIx::Class::Relationship/has_many> key, and create the related
+objects if necessary.
+
+Be aware that the input hashref might be edited in place, so dont rely
+on it being the same after a call to C<set_inflated_columns>. If you
+need to preserve the hashref, it is sufficient to pass a shallow copy
+to C<set_inflated_columns>, e.g. ( { %{ $href } } )
+
+See also L<DBIx::Class::Relationship::Base/set_from_related>.
 
 =cut
 
@@ -506,24 +771,17 @@ sub set_inflated_columns {
       {
         my $rel = delete $upd->{$key};
         $self->set_from_related($key => $rel);
-        $self->{_relationship_data}{$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;
+        && $info->{attrs}{accessor} eq 'multi') {
+          $self->throw_exception(
+            "Recursive update is not supported over relationships of type multi ($key)"
+          );
       }
       elsif ($self->has_column($key)
         && exists $self->column_info($key)->{_inflate_info})
       {
-        $self->set_inflated_column($key, delete $upd->{$key});          
+        $self->set_inflated_column($key, delete $upd->{$key});
       }
     }
   }
@@ -534,7 +792,22 @@ sub set_inflated_columns {
 
   my $copy = $orig->copy({ change => $to, ... });
 
-Inserts a new row with the specified changes.
+=over
+
+=item Arguments: \%replacementdata
+
+=item Returns: The Row object copy
+
+=back
+
+Inserts a new row into the database, as a copy of the original
+object. If a hashref of replacement data is supplied, these will take
+precedence over data in the original.
+
+If the row has related objects in a
+L<DBIx::Class::Relationship/has_many> then those objects may be copied
+too depending on the L<cascade_copy|DBIx::Class::Relationship>
+relationship attribute.
 
 =cut
 
@@ -582,9 +855,22 @@ sub copy {
 
 =head2 store_column
 
-  $obj->store_column($col => $val);
+  $row->store_column($col => $val);
+
+=over
+
+=item Arguments: $columnname, $value
+
+=item Returns: The value sent to storage
+
+=back
+
+Set a raw value for a column without marking it as changed. This
+method is used internally by L</set_column> which you should probably
+be using.
 
-Sets a column value without marking it as dirty.
+This is the lowest level at which data is set on a row object,
+extend this method to catch all data setting methods.
 
 =cut
 
@@ -601,7 +887,22 @@ sub store_column {
 
   Class->inflate_result($result_source, \%me, \%prefetch?)
 
-Called by ResultSet to inflate a result from storage
+=over
+
+=item Arguments: $result_source, \%columndata, \%prefetcheddata
+
+=item Returns: A Row object
+
+=back
+
+All L<DBIx::Class::ResultSet> methods that retrieve data from the
+database and turn it into row objects call this method.
+
+Extend this method in your Result classes to hook into this process,
+for example to rebless the result into a different class.
+
+Reblessing can also be done more easily by setting C<result_class> in
+your Result class. See L<DBIx::Class::ResultSource/result_class>.
 
 =cut
 
@@ -666,9 +967,18 @@ sub inflate_result {
 
 =head2 update_or_insert
 
-  $obj->update_or_insert
+  $row->update_or_insert
 
-Updates the object if it's already in the db, else inserts it.
+=over
+
+=item Arguments: none
+
+=item Returns: Result of update or insert operation
+
+=back
+
+L</Update>s the object if it's already in the database, according to
+L</in_storage>, else L</insert>s it.
 
 =head2 insert_or_update
 
@@ -678,7 +988,8 @@ Alias for L</update_or_insert>
 
 =cut
 
-*insert_or_update = \&update_or_insert;
+sub insert_or_update { shift->update_or_insert(@_) }
+
 sub update_or_insert {
   my $self = shift;
   return ($self->in_storage ? $self->update : $self->insert);
@@ -686,10 +997,18 @@ sub update_or_insert {
 
 =head2 is_changed
 
-  my @changed_col_names = $obj->is_changed();
-  if ($obj->is_changed()) { ... }
+  my @changed_col_names = $row->is_changed();
+  if ($row->is_changed()) { ... }
+
+=over
 
-In array context returns a list of columns with uncommited changes, or
+=item Arguments: none
+
+=item Returns: 0|1 or @columnnames
+
+=back
+
+In list context returns a list of columns with uncommited changes, or
 in scalar context returns a true value if there are uncommitted
 changes.
 
@@ -701,7 +1020,15 @@ sub is_changed {
 
 =head2 is_column_changed
 
-  if ($obj->is_column_changed('col')) { ... }
+  if ($row->is_column_changed('col')) { ... }
+
+=over
+
+=item Arguments: $columname
+
+=item Returns: 0|1
+
+=back
 
 Returns a true value if the column has uncommitted changes.
 
@@ -714,9 +1041,17 @@ sub is_column_changed {
 
 =head2 result_source
 
-  my $resultsource = $object->result_source;
+  my $resultsource = $row->result_source;
+
+=over
+
+=item Arguments: none
 
-Accessor to the ResultSource this object was created from
+=item Returns: a ResultSource instance
+
+=back
+
+Accessor to the L<DBIx::Class::ResultSource> this object was created from.
 
 =cut
 
@@ -735,6 +1070,14 @@ sub result_source {
   $column_info = { .... };
   $class->register_column($column_name, $column_info);
 
+=over
+
+=item Arguments: $columnname, \%columninfo
+
+=item Returns: undefined
+
+=back
+
 Registers a column on the class. If the column_info has an 'accessor'
 key, creates an accessor named after the value if defined; if there is
 no such key, creates an accessor with the same name as the column
@@ -754,10 +1097,50 @@ sub register_column {
   $class->mk_group_accessors('column' => $acc);
 }
 
+=head2 get_from_storage
+
+  my $copy = $row->get_from_storage($attrs)
+
+=over
+
+=item Arguments: \%attrs
+
+=item Returns: A Row object
+
+=back
+
+Fetches a fresh copy of the Row object from the database and returns it.
+
+If passed the \%attrs argument, will first apply these attributes to
+the resultset used to find the row.
+
+This copy can then be used to compare to an existing row object, to
+determine if any changes have been made in the database since it was
+created.
+
+To just update your Row object with any latest changes from the
+database, use L</discard_changes> instead.
+
+The \%attrs argument should be compatible with
+L<DBIx::Class::ResultSet/ATTRIBUTES>.
+
+=cut
+
+sub get_from_storage {
+    my $self = shift @_;
+    my $attrs = shift @_;
+    my $resultset = $self->result_source->resultset;
+    
+    if(defined $attrs) {
+       $resultset = $resultset->search(undef, $attrs);
+    }
+    
+    return $resultset->find($self->{_orig_ident} || $self->ident_condition);
+}
 
 =head2 throw_exception
 
-See Schema's throw_exception.
+See L<DBIx::Class::Schema/throw_exception>.
 
 =cut
 
@@ -772,17 +1155,37 @@ sub throw_exception {
 
 =head2 id
 
+  my @pk = $row->id;
+
+=over
+
+=item Arguments: none
+
+=item Returns: A list of primary key values
+
+=back
+
 Returns the primary key(s) for a row. Can't be called as a class method.
-Actually implemented in L<DBIx::Class::Pk>
+Actually implemented in L<DBIx::Class::PK>
 
 =head2 discard_changes
 
-Re-selects the row from the database, losing any changes that had
-been made.
+  $row->discard_changes
+
+=over
+
+=item Arguments: none
+
+=item Returns: nothing (updates object in-place)
+
+=back
+
+Retrieves and sets the row object data from the database, losing any
+local changes made.
 
 This method can also be used to refresh from storage, retrieving any
 changes made since the row was last read from storage. Actually
-implemented in L<DBIx::Class::Pk>
+implemented in L<DBIx::Class::PK>
 
 =cut