use Scalar::Util 'blessed';
use List::Util 'first';
use Try::Tiny;
+use DBIx::Class::Carp;
###
### Internal method
use namespace::clean;
+__PACKAGE__->mk_group_accessors ( simple => [ in_storage => '_in_storage' ] );
+
=head1 NAME
DBIx::Class::Row - Basic row methods
=head1 NOTE
All "Row objects" derived from a Schema-attached L<DBIx::Class::ResultSet>
-object (such as a typical C<< L<search|DBIx::Class::ResultSet/search
->->L<next|DBIx::Class::ResultSet/next> >> call) are actually Result
+object (such as a typical C<< L<search|DBIx::Class::ResultSet/search>->
+L<next|DBIx::Class::ResultSet/next> >> call) are actually Result
instances, based on your application's
L<Result class|DBIx::Class::Manual::Glossary/Result_class>.
=head2 new
- my $row = My::Class->new(\%attrs);
+ my $result = My::Class->new(\%attrs);
- my $row = $schema->resultset('MySource')->new(\%colsandvalues);
+ my $result = $schema->resultset('MySource')->new(\%colsandvalues);
=over
=cut
## It needs to store the new objects somewhere, and call insert on that list later when insert is called on this object. We may need an accessor for these so the user can retrieve them, if just doing ->new().
-## This only works because DBIC doesnt yet care to check whether the new_related objects have been passed all their mandatory columns
+## This only works because DBIC doesn't yet care to check whether the new_related objects have been passed all their mandatory columns
## When doing the later insert, we need to make sure the PKs are set.
## using _relationship_data in new and funky ways..
## check Relationship::CascadeActions and Relationship::Accessor for compat
## tests!
sub __new_related_find_or_new_helper {
- my ($self, $relname, $data) = @_;
+ my ($self, $relname, $values) = @_;
my $rsrc = $self->result_source;
# create a mock-object so all new/set_column component overrides will run:
my $rel_rs = $rsrc->related_source($relname)->resultset;
- my $new_rel_obj = $rel_rs->new_result($data);
+ my $new_rel_obj = $rel_rs->new_result($values);
my $proc_data = { $new_rel_obj->get_columns };
if ($self->__their_pk_needs_us($relname)) {
- MULTICREATE_DEBUG and warn "MC $self constructing $relname via new_result";
+ MULTICREATE_DEBUG and print STDERR "MC $self constructing $relname via new_result\n";
return $new_rel_obj;
}
elsif ($rsrc->_pk_depends_on($relname, $proc_data )) {
if (! keys %$proc_data) {
# there is nothing to search for - blind create
- MULTICREATE_DEBUG and warn "MC $self constructing default-insert $relname";
+ MULTICREATE_DEBUG and print STDERR "MC $self constructing default-insert $relname\n";
}
else {
- MULTICREATE_DEBUG and warn "MC $self constructing $relname via find_or_new";
+ MULTICREATE_DEBUG and print STDERR "MC $self constructing $relname via find_or_new\n";
# this is not *really* find or new, as we don't want to double-new the
# data (thus potentially double encoding or whatever)
my $exists = $rel_rs->find ($proc_data);
sub __their_pk_needs_us { # this should maybe be in resultsource.
my ($self, $relname) = @_;
- my $source = $self->result_source;
- my $reverse = $source->reverse_relationship_info($relname);
- my $rel_source = $source->related_source($relname);
+ my $rsrc = $self->result_source;
+ my $reverse = $rsrc->reverse_relationship_info($relname);
+ my $rel_source = $rsrc->related_source($relname);
my $us = { $self->get_columns };
foreach my $key (keys %$reverse) {
# if their primary key depends on us, then we have to
my ($class, $attrs) = @_;
$class = ref $class if ref $class;
- my $new = bless { _column_data => {} }, $class;
+ my $new = bless { _column_data => {}, _in_storage => 0 }, $class;
if ($attrs) {
$new->throw_exception("attrs must be a hashref")
unless ref($attrs) eq 'HASH';
- my $source = delete $attrs->{-result_source};
+ my $rsrc = delete $attrs->{-result_source};
if ( my $h = delete $attrs->{-source_handle} ) {
- $source ||= $h->resolve;
+ $rsrc ||= $h->resolve;
}
- $new->result_source($source) if $source;
+ $new->result_source($rsrc) if $rsrc;
if (my $col_from_rel = delete $attrs->{-cols_from_relations}) {
@{$new->{_ignore_at_insert}={}}{@$col_from_rel} = ();
if (ref $attrs->{$key}) {
## Can we extract this lot to use with update(_or .. ) ?
$new->throw_exception("Can't do multi-create without result source")
- unless $source;
- my $info = $source->relationship_info($key);
+ unless $rsrc;
+ my $info = $rsrc->relationship_info($key);
my $acc_type = $info->{attrs}{accessor} || '';
if ($acc_type eq 'single') {
my $rel_obj = delete $attrs->{$key};
$new->{_rel_in_storage}{$key} = 1;
$new->set_from_related($key, $rel_obj);
} else {
- MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj\n";
+ MULTICREATE_DEBUG and print STDERR "MC $new uninserted $key $rel_obj\n";
}
$related->{$key} = $rel_obj;
$rel_obj->throw_exception ('A multi relationship can not be pre-existing when doing multicreate. Something went wrong');
} else {
MULTICREATE_DEBUG and
- warn "MC $new uninserted $key $rel_obj (${\($idx+1)} of $total)\n";
+ print STDERR "MC $new uninserted $key $rel_obj (${\($idx+1)} of $total)\n";
}
push(@objects, $rel_obj);
}
$new->{_rel_in_storage}{$key} = 1;
}
else {
- MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj";
+ MULTICREATE_DEBUG and print STDERR "MC $new uninserted $key $rel_obj\n";
}
$inflated->{$key} = $rel_obj;
next;
# Each pair does the same thing
# (un-inflated, regular column)
- my $val = $row->get_column('first_name');
- my $val = $row->first_name;
+ my $val = $result->get_column('first_name');
+ my $val = $result->first_name;
- $row->set_column('first_name' => $val);
- $row->first_name($val);
+ $result->set_column('first_name' => $val);
+ $result->first_name($val);
# (inflated column via DBIx::Class::InflateColumn::DateTime)
- my $val = $row->get_inflated_column('last_modified');
- my $val = $row->last_modified;
+ my $val = $result->get_inflated_column('last_modified');
+ my $val = $result->last_modified;
- $row->set_inflated_column('last_modified' => $val);
- $row->last_modified($val);
+ $result->set_inflated_column('last_modified' => $val);
+ $result->last_modified($val);
=over
=head2 insert
- $row->insert;
+ $result->insert;
=over
sub insert {
my ($self) = @_;
return $self if $self->in_storage;
- my $source = $self->result_source;
+ my $rsrc = $self->result_source;
$self->throw_exception("No result_source set on this object; can't insert")
- unless $source;
+ unless $rsrc;
- my $storage = $source->storage;
+ my $storage = $rsrc->storage;
my $rollback_guard;
if (! $self->{_rel_in_storage}{$relname}) {
next unless (blessed $rel_obj && $rel_obj->isa('DBIx::Class::Row'));
- next unless $source->_pk_depends_on(
+ next unless $rsrc->_pk_depends_on(
$relname, { $rel_obj->get_columns }
);
# The guard will save us if we blow out of this scope via die
$rollback_guard ||= $storage->txn_scope_guard;
- MULTICREATE_DEBUG and warn "MC $self pre-reconstructing $relname $rel_obj\n";
+ MULTICREATE_DEBUG and print STDERR "MC $self pre-reconstructing $relname $rel_obj\n";
my $them = { %{$rel_obj->{_relationship_data} || {} }, $rel_obj->get_columns };
my $existing;
MULTICREATE_DEBUG and do {
no warnings 'uninitialized';
- warn "MC $self inserting (".join(', ', $self->get_columns).")\n";
+ print STDERR "MC $self inserting (".join(', ', $self->get_columns).")\n";
};
# perform the insert - the storage will return everything it is asked to
# (autoinc primary columns and any retrieve_on_insert columns)
my %current_rowdata = $self->get_columns;
my $returned_cols = $storage->insert(
- $source,
+ $rsrc,
{ %current_rowdata }, # what to insert, copy because the storage *will* change it
);
$self->{related_resultsets} = {};
foreach my $relname (keys %related_stuff) {
- next unless $source->has_relationship ($relname);
+ next unless $rsrc->has_relationship ($relname);
my @cands = ref $related_stuff{$relname} eq 'ARRAY'
? @{$related_stuff{$relname}}
if (@cands && blessed $cands[0] && $cands[0]->isa('DBIx::Class::Row')
) {
- my $reverse = $source->reverse_relationship_info($relname);
+ my $reverse = $rsrc->reverse_relationship_info($relname);
foreach my $obj (@cands) {
$obj->set_from_related($_, $self) for keys %$reverse;
if ($self->__their_pk_needs_us($relname)) {
if (exists $self->{_ignore_at_insert}{$relname}) {
- MULTICREATE_DEBUG and warn "MC $self skipping post-insert on $relname";
+ MULTICREATE_DEBUG and print STDERR "MC $self skipping post-insert on $relname\n";
}
else {
- MULTICREATE_DEBUG and warn "MC $self inserting $relname $obj";
+ MULTICREATE_DEBUG and print STDERR "MC $self inserting $relname $obj\n";
$obj->insert;
}
} else {
- MULTICREATE_DEBUG and warn "MC $self post-inserting $obj";
+ MULTICREATE_DEBUG and print STDERR "MC $self post-inserting $obj\n";
$obj->insert();
}
}
=head2 in_storage
- $row->in_storage; # Get value
- $row->in_storage(1); # Set value
+ $result->in_storage; # Get value
+ $result->in_storage(1); # Set value
=over
Creating a result object using L<DBIx::Class::ResultSet/new_result>, or
calling L</delete> on one, sets it to false.
-=cut
-
-sub in_storage {
- my ($self, $val) = @_;
- $self->{_in_storage} = $val if @_ > 1;
- return $self->{_in_storage} ? 1 : 0;
-}
=head2 update
- $row->update(\%columns?)
+ $result->update(\%columns?)
=over
=back
Throws an exception if the result object is not yet in the database,
-according to L</in_storage>.
+according to L</in_storage>. Returns the object itself.
This method issues an SQL UPDATE query to commit any changes to the
object to the database if required (see L</get_dirty_columns>).
If the values passed or any of the column values set on the object
contain scalar references, e.g.:
- $row->last_modified(\'NOW()')->update();
+ $result->last_modified(\'NOW()')->update();
# OR
- $row->update({ last_modified => \'NOW()' });
+ $result->update({ last_modified => \'NOW()' });
The update will pass the values verbatim into SQL. (See
L<SQL::Abstract> docs). The values in your Result object will NOT change
with the actual values from the database, call L</discard_changes>
after the update.
- $row->update()->discard_changes();
+ $result->update()->discard_changes();
To determine before calling this method, which column values have
changed and will be updated, call L</get_dirty_columns>.
=head2 delete
- $row->delete
+ $result->delete
=over
);
delete $self->{_column_data_in_storage};
- $self->in_storage(undef);
+ $self->in_storage(0);
}
else {
my $rsrc = try { $self->result_source_instance }
=head2 get_column
- my $val = $row->get_column($col);
+ my $val = $result->get_column($col);
=over
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,
+which C<$result> 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>.
=head2 has_column_loaded
- if ( $row->has_column_loaded($col) ) {
+ if ( $result->has_column_loaded($col) ) {
print "$col has been loaded from db";
}
=head2 get_columns
- my %data = $row->get_columns;
+ my %data = $result->get_columns;
=over
my $self = shift;
if (exists $self->{_inflated_column}) {
foreach my $col (keys %{$self->{_inflated_column}}) {
- $self->store_column($col, $self->_deflated_column($col, $self->{_inflated_column}{$col}))
- unless exists $self->{_column_data}{$col};
+ unless (exists $self->{_column_data}{$col}) {
+
+ # if cached related_resultset is present assume this was a prefetch
+ carp_unique(
+ "Returning primary keys of prefetched 'filter' rels as part of get_columns() is deprecated and will "
+ . 'eventually be removed entirely (set DBIC_COLUMNS_INCLUDE_FILTER_RELS to disable this warning)'
+ ) if (
+ ! $ENV{DBIC_COLUMNS_INCLUDE_FILTER_RELS}
+ and
+ defined $self->{related_resultsets}{$col}
+ and
+ defined $self->{related_resultsets}{$col}->get_cache
+ );
+
+ $self->store_column($col, $self->_deflated_column($col, $self->{_inflated_column}{$col}));
+ }
}
}
return %{$self->{_column_data}};
=head2 get_dirty_columns
- my %data = $row->get_dirty_columns;
+ my %data = $result->get_dirty_columns;
=over
=head2 make_column_dirty
- $row->make_column_dirty($col)
+ $result->make_column_dirty($col)
=over
really changed.
=cut
+
sub make_column_dirty {
my ($self, $column) = @_;
grep { $self->has_column_loaded($_) } $self->columns
]);
- my %inflated;
- for my $col (keys %$loaded_colinfo) {
- if (exists $loaded_colinfo->{$col}{accessor}) {
- my $acc = $loaded_colinfo->{$col}{accessor};
- $inflated{$col} = $self->$acc if defined $acc;
- }
- else {
- $inflated{$col} = $self->$col;
+ my %cols_to_return = ( %{$self->{_column_data}}, %$loaded_colinfo );
+
+ unless ($ENV{DBIC_COLUMNS_INCLUDE_FILTER_RELS}) {
+ for (keys %$loaded_colinfo) {
+ # if cached related_resultset is present assume this was a prefetch
+ if (
+ $loaded_colinfo->{$_}{_inflate_info}
+ and
+ defined $self->{related_resultsets}{$_}
+ and
+ defined $self->{related_resultsets}{$_}->get_cache
+ ) {
+ carp_unique(
+ "Returning prefetched 'filter' rels as part of get_inflated_columns() is deprecated and will "
+ . 'eventually be removed entirely (set DBIC_COLUMNS_INCLUDE_FILTER_RELS to disable this warning)'
+ );
+ last;
+ }
}
}
- # return all loaded columns with the inflations overlayed on top
- return %{ { $self->get_columns, %inflated } };
+ map { $_ => (
+ (
+ ! exists $loaded_colinfo->{$_}
+ or
+ (
+ exists $loaded_colinfo->{$_}{accessor}
+ and
+ ! defined $loaded_colinfo->{$_}{accessor}
+ )
+ ) ? $self->get_column($_)
+ : $self->${ \(
+ defined $loaded_colinfo->{$_}{accessor}
+ ? $loaded_colinfo->{$_}{accessor}
+ : $_
+ )}
+ )} keys %cols_to_return;
}
sub _is_column_numeric {
=head2 set_column
- $row->set_column($col => $val);
+ $result->set_column($col => $val);
=over
#
# FIXME - this is a quick *largely incorrect* hack, pending a more
# serious rework during the merge of single and filter rels
- my $rels = $self->result_source->{_relationships};
- for my $rel (keys %$rels) {
+ my $relnames = $self->result_source->{_relationships};
+ for my $relname (keys %$relnames) {
- my $acc = $rels->{$rel}{attrs}{accessor} || '';
+ my $acc = $relnames->{$relname}{attrs}{accessor} || '';
- if ( $acc eq 'single' and $rels->{$rel}{attrs}{fk_columns}{$column} ) {
- delete $self->{related_resultsets}{$rel};
- delete $self->{_relationship_data}{$rel};
- #delete $self->{_inflated_column}{$rel};
+ if ( $acc eq 'single' and $relnames->{$relname}{attrs}{fk_columns}{$column} ) {
+ delete $self->{related_resultsets}{$relname};
+ delete $self->{_relationship_data}{$relname};
+ #delete $self->{_inflated_column}{$relname};
}
- elsif ( $acc eq 'filter' and $rel eq $column) {
- delete $self->{related_resultsets}{$rel};
- #delete $self->{_relationship_data}{$rel};
- delete $self->{_inflated_column}{$rel};
+ elsif ( $acc eq 'filter' and $relname eq $column) {
+ delete $self->{related_resultsets}{$relname};
+ #delete $self->{_relationship_data}{$relname};
+ delete $self->{_inflated_column}{$relname};
}
}
=head2 set_columns
- $row->set_columns({ $col => $val, ... });
+ $result->set_columns({ $col => $val, ... });
=over
=cut
sub set_columns {
- my ($self,$data) = @_;
- foreach my $col (keys %$data) {
- $self->set_column($col,$data->{$col});
- }
+ my ($self, $values) = @_;
+ $self->set_column( $_, $values->{$_} ) for keys %$values;
return $self;
}
=head2 set_inflated_columns
- $row->set_inflated_columns({ $col => $val, $relname => $obj, ... });
+ $result->set_inflated_columns({ $col => $val, $relname => $obj, ... });
=over
my $info = $self->relationship_info($key);
my $acc_type = $info->{attrs}{accessor} || '';
if ($acc_type eq 'single') {
- my $rel = delete $upd->{$key};
- $self->set_from_related($key => $rel);
- $self->{_relationship_data}{$key} = $rel;
+ my $rel_obj = delete $upd->{$key};
+ $self->set_from_related($key => $rel_obj);
+ $self->{_relationship_data}{$key} = $rel_obj;
}
elsif ($acc_type eq 'multi') {
$self->throw_exception(
# Its possible we'll have 2 relations to the same Source. We need to make
# sure we don't try to insert the same row twice else we'll violate unique
# constraints
- my $rels_copied = {};
+ my $relnames_copied = {};
- foreach my $rel ($self->result_source->relationships) {
- my $rel_info = $self->result_source->relationship_info($rel);
+ foreach my $relname ($self->result_source->relationships) {
+ my $rel_info = $self->result_source->relationship_info($relname);
next unless $rel_info->{attrs}{cascade_copy};
my $resolved = $self->result_source->_resolve_condition(
- $rel_info->{cond}, $rel, $new, $rel
+ $rel_info->{cond}, $relname, $new, $relname
);
- my $copied = $rels_copied->{ $rel_info->{source} } ||= {};
- foreach my $related ($self->search_related($rel)) {
+ my $copied = $relnames_copied->{ $rel_info->{source} } ||= {};
+ foreach my $related ($self->search_related($relname)->all) {
my $id_str = join("\0", $related->id);
next if $copied->{$id_str};
$copied->{$id_str} = 1;
=head2 store_column
- $row->store_column($col => $val);
+ $result->store_column($col => $val);
=over
=cut
sub inflate_result {
- my ($class, $source, $me, $prefetch) = @_;
-
- $source = $source->resolve
- if $source->isa('DBIx::Class::ResultSourceHandle');
+ my ($class, $rsrc, $me, $prefetch) = @_;
my $new = bless
- { _column_data => $me, _result_source => $source },
+ { _column_data => $me, _result_source => $rsrc },
ref $class || $class
;
- foreach my $pre (keys %{$prefetch||{}}) {
+ if ($prefetch) {
+ for my $relname ( keys %$prefetch ) {
- my @pre_vals;
- @pre_vals = (ref $prefetch->{$pre}[0] eq 'ARRAY')
- ? @{$prefetch->{$pre}} : $prefetch->{$pre}
- if @{$prefetch->{$pre}};
+ my $relinfo = $rsrc->relationship_info($relname) or do {
+ my $err = sprintf
+ "Inflation into non-existent relationship '%s' of '%s' requested",
+ $relname,
+ $rsrc->source_name,
+ ;
+ if (my ($colname) = sort { length($a) <=> length ($b) } keys %{$prefetch->{$relname}[0] || {}} ) {
+ $err .= sprintf ", check the inflation specification (columns/as) ending in '...%s.%s'",
+ $relname,
+ $colname,
+ }
- my $pre_source = $source->related_source($pre);
+ $rsrc->throw_exception($err);
+ };
- my $accessor = $source->relationship_info($pre)->{attrs}{accessor}
- or $class->throw_exception("No accessor type declared for prefetched relationship '$pre'");
+ $class->throw_exception("No accessor type declared for prefetched relationship '$relname'")
+ unless $relinfo->{attrs}{accessor};
- my @pre_objects;
- for my $me_pref (@pre_vals) {
+ my @rel_objects;
+ if (
+ $prefetch->{$relname}
+ and
+ @{$prefetch->{$relname}}
+ and
+ ref($prefetch->{$relname}) ne $DBIx::Class::ResultSource::RowParser::Util::null_branch_class
+ ) {
- # FIXME SUBOPTIMAL - the new row parsers can very well optimize
- # this away entirely, and *never* return such empty rows.
- # For now we maintain inflate_result API backcompat, see
- # t/resultset/inflate_result_api.t
- next unless defined first { defined $_ } values %{$me_pref->[0]};
+ my $rel_rs = $new->related_resultset($relname);
- push @pre_objects, $pre_source->result_class->inflate_result(
- $pre_source, @$me_pref
- );
- }
+ if (ref $prefetch->{$relname}[0] eq 'ARRAY') {
+ my $rel_rsrc = $rel_rs->result_source;
+ my $rel_class = $rel_rs->result_class;
+ my $rel_inflator = $rel_class->can('inflate_result');
+ @rel_objects = map
+ { $rel_class->$rel_inflator ( $rel_rsrc, @$_ ) }
+ @{$prefetch->{$relname}}
+ ;
+ }
+ else {
+ @rel_objects = $rel_rs->result_class->inflate_result(
+ $rel_rs->result_source, @{$prefetch->{$relname}}
+ );
+ }
+ }
- if ($accessor eq 'single') {
- $new->{_relationship_data}{$pre} = $pre_objects[0];
- }
- elsif ($accessor eq 'filter') {
- $new->{_inflated_column}{$pre} = $pre_objects[0];
- }
+ if ($relinfo->{attrs}{accessor} eq 'single') {
+ $new->{_relationship_data}{$relname} = $rel_objects[0];
+ }
+ elsif ($relinfo->{attrs}{accessor} eq 'filter') {
+ $new->{_inflated_column}{$relname} = $rel_objects[0];
+ }
- $new->related_resultset($pre)->set_cache(\@pre_objects);
+ $new->related_resultset($relname)->set_cache(\@rel_objects);
+ }
}
$new->in_storage (1);
=head2 update_or_insert
- $row->update_or_insert
+ $result->update_or_insert
=over
=head2 is_changed
- my @changed_col_names = $row->is_changed();
- if ($row->is_changed()) { ... }
+ my @changed_col_names = $result->is_changed();
+ if ($result->is_changed()) { ... }
=over
=head2 is_column_changed
- if ($row->is_column_changed('col')) { ... }
+ if ($result->is_column_changed('col')) { ... }
=over
=head2 result_source
- my $resultsource = $row->result_source;
+ my $resultsource = $result->result_source;
=over
=head2 get_from_storage
- my $copy = $row->get_from_storage($attrs)
+ my $copy = $result->get_from_storage($attrs)
=over
=head2 discard_changes
- $row->discard_changes
+ $result->discard_changes
=over
=head2 id
- my @pk = $row->id;
+ my @pk = $result->id;
=over