Merge 'trunk' into 'DBIx-Class-current'
Daniel Westermann-Clark [Wed, 10 May 2006 17:00:11 +0000 (13:00 -0400)]
r8956@fortuna (orig r1603):  dwc | 2006-05-10 12:00:11 -0400
Row::update encapsulates this when passed a hashref; using set_columns bypasses deflation

1  2 
Changes
lib/DBIx/Class/ResultSet.pm

diff --combined Changes
+++ b/Changes
@@@ -1,22 -1,8 +1,24 @@@
  Revision history for DBIx::Class
  
 +        - modified SQLT parser to skip dupe table names
 +        - added remove_column(s) to ResultSource/ResultSourceProxy
 +        - added add_column alias to ResultSourceProxy
 +        - added source_name to ResultSource
 +        - load_classes now uses source_name and sets it if necessary
 +        - add update_or_create_related to Relationship::Base
 +        - add find_or_new to ResultSet/ResultSetProxy and find_or_new_related
 +          to Relationship::Base
 +        - add accessors for unique constraint names and coulums to
 +          ResultSource/ResultSourceProxy
 +        - rework ResultSet::find() to search unique constraints
 +        - CDBICompat: modify retrieve to fix column casing when ColumnCase is
 +          loaded
 +        - CDBICompat: override find_or_create to fix column casing when
 +          ColumnCase is loaded
 +
  0.06003
+         - don't set_columns explicitly in update_or_create; instead use
+           update($hashref) so InflateColumn works
          - fix for has_many prefetch with 0 related rows
          - make limit error if rows => 0
          - added memory cycle tests and a long-needed weaken call
          - remove build dependency on version.pm
  
  0.05004 2006-02-13 20:59:00
 -        - allow specification of related columns via cols attr when primary 
 +        - allow specification of related columns via cols attr when primary
            keys of the related table are not fetched
          - fix count for group_by as scalar
          - add horrific fix to make Oracle's retarded limit syntax work
@@@ -10,7 -10,6 +10,7 @@@ use Data::Page
  use Storable;
  use Scalar::Util qw/weaken/;
  
 +use DBIx::Class::ResultSetColumn;
  use base qw/DBIx::Class/;
  __PACKAGE__->load_components(qw/AccessorGroup/);
  __PACKAGE__->mk_group_accessors('simple' => qw/result_source result_class/);
@@@ -196,28 -195,7 +196,28 @@@ call it as C<search(undef, \%attrs)>
  
  sub search {
    my $self = shift;
 -    
 +  my $rs = $self->search_rs( @_ );
 +  return (wantarray ? $rs->all : $rs);
 +}
 +
 +=head2 search_rs
 +
 +=over 4
 +
 +=item Arguments: $cond, \%attrs?
 +
 +=item Return Value: $resultset
 +
 +=back
 +
 +This method does the same exact thing as search() except it will 
 +always return a resultset, even in list context.
 +
 +=cut
 +
 +sub search_rs {
 +  my $self = shift;
 +
    my $attrs = { %{$self->{attrs}} };
    my $having = delete $attrs->{having};
    $attrs = { %$attrs, %{ pop(@_) } } if @_ > 1 and ref $_[$#_] eq 'HASH';
      }
    }
    
 -  return (wantarray ? $rs->all : $rs);
 +  return $rs;
  }
  
  =head2 search_literal
@@@ -293,17 -271,12 +293,17 @@@ sub search_literal 
  
  =back
  
 -Finds a row based on its primary key or unique constraint. For example:
 +Finds a row based on its primary key or unique constraint. For example, to find
 +a row by its primary key:
  
    my $cd = $schema->resultset('CD')->find(5);
  
 -Also takes an optional C<key> attribute, to search by a specific key or unique
 -constraint. For example:
 +You can also find a row by a specific unique constraint using the C<key>
 +attribute. For example:
 +
 +  my $cd = $schema->resultset('CD')->find('Massive Attack', 'Mezzanine', { key => 'artist_title' });
 +
 +Additionally, you can specify the columns explicitly by name:
  
    my $cd = $schema->resultset('CD')->find(
      {
      { key => 'artist_title' }
    );
  
 -See also L</find_or_create> and L</update_or_create>.
 +If no C<key> is specified and you explicitly name columns, it searches on all
 +unique constraints defined on the source, including the primary key.
 +
 +If the C<key> is specified as C<primary>, it searches only on the primary key.
 +
 +See also L</find_or_create> and L</update_or_create>. For information on how to
 +declare unique constraints, see
 +L<DBIx::Class::ResultSource/add_unique_constraint>.
  
  =cut
  
  sub find {
 -  my ($self, @vals) = @_;
 -  my $attrs = (@vals > 1 && ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {});
 +  my $self = shift;
 +  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  
 -  my @cols = $self->result_source->primary_columns;
 -  if (exists $attrs->{key}) {
 -    my %uniq = $self->result_source->unique_constraints;
 +  # Parse out a hash from input
 +  my @cols = exists $attrs->{key}
 +    ? $self->result_source->unique_constraint_columns($attrs->{key})
 +    : $self->result_source->primary_columns;
 +
 +  my $hash;
 +  if (ref $_[0] eq 'HASH') {
 +    $hash = { %{$_[0]} };
 +  }
 +  elsif (@_ == @cols) {
 +    $hash = {};
 +    @{$hash}{@cols} = @_;
 +  }
 +  else {
      $self->throw_exception(
 -      "Unknown key $attrs->{key} on '" . $self->result_source->name . "'"
 -    ) unless exists $uniq{$attrs->{key}};
 -    @cols = @{ $uniq{$attrs->{key}} };
 +      "Arguments to find must be a hashref or match the number of columns in the "
 +        . exists $attrs->{key} ? "$attrs->{key} unique constraint" : "primary key"
 +    );
    }
 -  #use Data::Dumper; warn Dumper($attrs, @vals, @cols);
 +
 +  # Check the hash we just parsed against our source's unique constraints
 +  my @constraint_names = exists $attrs->{key}
 +    ? ($attrs->{key})
 +    : $self->result_source->unique_constraint_names;
    $self->throw_exception(
      "Can't find unless a primary key or unique constraint is defined"
 -  ) unless @cols;
 -
 -  my $query;
 -  if (ref $vals[0] eq 'HASH') {
 -    $query = { %{$vals[0]} };
 -  } elsif (@cols == @vals) {
 -    $query = {};
 -    @{$query}{@cols} = @vals;
 -  } else {
 -    $query = {@vals};
 -  }
 -  foreach my $key (grep { ! m/\./ } keys %$query) {
 -    $query->{"$self->{attrs}{alias}.$key"} = delete $query->{$key};
 +  ) unless @constraint_names;
 +
 +  my @unique_queries;
 +  foreach my $name (@constraint_names) {
 +    my @unique_cols = $self->result_source->unique_constraint_columns($name);
 +    my $unique_query = $self->_build_unique_query($hash, \@unique_cols);
 +
 +    # Add the ResultSet's alias
 +    foreach my $key (grep { ! m/\./ } keys %$unique_query) {
 +      $unique_query->{"$self->{attrs}{alias}.$key"} = delete $unique_query->{$key};
 +    }
 +
 +    push @unique_queries, $unique_query if %$unique_query;
    }
 -  #warn Dumper($query);
 -  
 +
 +  # Handle cases where the ResultSet already defines the query
 +  my $query = @unique_queries ? \@unique_queries : undef;
 +
 +  # Run the query
    if (keys %$attrs) {
 -      my $rs = $self->search($query,$attrs);
 -      return keys %{$rs->{collapse}} ? $rs->next : $rs->single;
 -  } else {
 -      return keys %{$self->{collapse}} ?
 -        $self->search($query)->next :
 -        $self->single($query);
 +    my $rs = $self->search($query, $attrs);
 +    return keys %{$rs->{collapse}} ? $rs->next : $rs->single;
 +  }
 +  else {
 +    return keys %{$self->{collapse}}
 +      ? $self->search($query)->next
 +      : $self->single($query);
    }
  }
  
 +# _build_unique_query
 +#
 +# Constrain the specified query hash based on the specified column names.
 +
 +sub _build_unique_query {
 +  my ($self, $query, $unique_cols) = @_;
 +
 +  my %unique_query =
 +    map  { $_ => $query->{$_} }
 +    grep { exists $query->{$_} }
 +    @$unique_cols;
 +
 +  return \%unique_query;
 +}
 +
  =head2 search_related
  
  =over 4
@@@ -458,7 -390,7 +458,7 @@@ sub cursor 
    my $cd = $schema->resultset('CD')->single({ year => 2001 });
  
  Inflates the first result without creating a cursor if the resultset has
 -any records in it; if not returns nothing. Used by find() as an optimisation.
 +any records in it; if not returns nothing. Used by L</find> as an optimisation.
  
  =cut
  
@@@ -482,28 -414,6 +482,28 @@@ sub single 
    return (@data ? $self->_construct_object(@data) : ());
  }
  
 +=head2 get_column
 +
 +=over 4
 +
 +=item Arguments: $cond?
 +
 +=item Return Value: $resultsetcolumn
 +
 +=back
 +
 +  my $max_length = $rs->get_column('length')->max;
 +
 +Returns a ResultSetColumn instance for $column based on $self
 +
 +=cut
 +
 +sub get_column {
 +  my ($self, $column) = @_;
 +
 +  my $new = DBIx::Class::ResultSetColumn->new($self, $column);
 +  return $new;
 +}
  
  =head2 search_like
  
@@@ -1117,32 -1027,6 +1117,32 @@@ sub new_result 
    return $obj;
  }
  
 +=head2 find_or_new
 +
 +=over 4
 +
 +=item Arguments: \%vals, \%attrs?
 +
 +=item Return Value: $object
 +
 +=back
 +
 +Find an existing record from this resultset. If none exists, instantiate a new
 +result object and return it. The object will not be saved into your storage
 +until you call L<DBIx::Class::Row/insert> on it.
 +
 +If you want objects to be saved immediately, use L</find_or_create> instead.
 +
 +=cut
 +
 +sub find_or_new {
 +  my $self     = shift;
 +  my $attrs    = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
 +  my $hash     = ref $_[0] eq 'HASH' ? shift : {@_};
 +  my $exists   = $self->find($hash, $attrs);
 +  return defined $exists ? $exists : $self->new_result($hash);
 +}
 +
  =head2 create
  
  =over 4
@@@ -1199,8 -1083,7 +1199,8 @@@ constraint. For example
      { key => 'artist_title' }
    );
  
 -See also L</find> and L</update_or_create>.
 +See also L</find> and L</update_or_create>. For information on how to declare
 +unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>.
  
  =cut
  
@@@ -1247,8 -1130,7 +1247,8 @@@ source, including the primary key
  
  If the C<key> is specified as C<primary>, it searches only on the primary key.
  
 -See also L</find> and L</find_or_create>.
 +See also L</find> and L</find_or_create>. For information on how to declare
 +unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>.
  
  =cut
  
@@@ -1257,11 -1139,29 +1257,10 @@@ sub update_or_create 
    my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
    my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
  
 -  my %unique_constraints = $self->result_source->unique_constraints;
 -  my @constraint_names   = (exists $attrs->{key}
 -                            ? ($attrs->{key})
 -                            : keys %unique_constraints);
 -
 -  my @unique_hashes;
 -  foreach my $name (@constraint_names) {
 -    my @unique_cols = @{ $unique_constraints{$name} };
 -    my %unique_hash =
 -      map  { $_ => $hash->{$_} }
 -      grep { exists $hash->{$_} }
 -      @unique_cols;
 -
 -    push @unique_hashes, \%unique_hash
 -      if (scalar keys %unique_hash == scalar @unique_cols);
 -  }
 -
 -  if (@unique_hashes) {
 -    my $row = $self->single(\@unique_hashes);
 -    if (defined $row) {
 -      $row->update($hash);
 -      return $row;
 -    }
 +  my $row = $self->find($hash, $attrs);
 +  if (defined $row) {
-     $row->set_columns($hash);
-     $row->update;
++    $row->update($hash);
 +    return $row;
    }
  
    return $self->create($hash);