package MyApp::Schema::Artist;
use base qw/DBIx::Class/;
- __PACKAGE__->load_components(qw/Core/);
+ __PACKAGE__->load_components(qw/Core/)
__PACKAGE__->table('artist');
__PACKAGE__->add_columns(qw/artistid name/);
__PACKAGE__->set_primary_key('artistid');
my (%related, $info);
- foreach my $key (keys %$input_query) {
+ KEY: foreach my $key (keys %$input_query) {
if (ref($input_query->{$key})
&& ($info = $self->result_source->relationship_info($key))) {
+ my $val = delete $input_query->{$key};
+ next KEY if (ref($val) eq 'ARRAY'); # has_many for multi_create
my $rel_q = $self->result_source->resolve_condition(
- $info->{cond}, delete $input_query->{$key}, $key
+ $info->{cond}, $val, $key
);
die "Can't handle OR join condition in find" if ref($rel_q) eq 'ARRAY';
@related{keys %$rel_q} = values %$rel_q;
$attrs->{where}, $attrs
);
- return (@data ? ($self->_construct_object(@data))[0] : ());
+ return (@data ? ($self->_construct_object(@data))[0] : undef);
}
# _is_unique_query
? @{delete $self->{stashed_row}}
: $self->cursor->next
);
- return unless (@row);
+ return undef unless (@row);
my ($row, @more) = $self->_construct_object(@row);
$self->{stashed_objects} = \@more if @more;
return $row;
return 1;
}
+=head2 populate
+
+=over 4
+
+=item Arguments: $source_name, \@data;
+
+=back
+
+Pass an arrayref of hashrefs. Each hashref should be a structure suitable for
+submitting to a $resultset->create(...) method.
+
+In void context, C<insert_bulk> in L<DBIx::Class::Storage::DBI> is used
+to insert the data, as this is a faster method.
+
+Otherwise, each set of data is inserted into the database using
+L<DBIx::Class::ResultSet/create>, and a arrayref of the resulting row
+objects is returned.
+
+Example: Assuming an Artist Class that has many CDs Classes relating:
+
+ my $Artist_rs = $schema->resultset("Artist");
+
+ ## Void Context Example
+ $Artist_rs->populate([
+ { artistid => 4, name => 'Manufactured Crap', cds => [
+ { title => 'My First CD', year => 2006 },
+ { title => 'Yet More Tweeny-Pop crap', year => 2007 },
+ ],
+ },
+ { artistid => 5, name => 'Angsty-Whiny Girl', cds => [
+ { title => 'My parents sold me to a record company' ,year => 2005 },
+ { title => 'Why Am I So Ugly?', year => 2006 },
+ { title => 'I Got Surgery and am now Popular', year => 2007 }
+ ],
+ },
+ ]);
+
+ ## Array Context Example
+ my ($ArtistOne, $ArtistTwo, $ArtistThree) = $Artist_rs->populate([
+ { name => "Artist One"},
+ { name => "Artist Two"},
+ { name => "Artist Three", cds=> [
+ { title => "First CD", year => 2007},
+ { title => "Second CD", year => 2008},
+ ]}
+ ]);
+
+ print $ArtistOne->name; ## response is 'Artist One'
+ print $ArtistThree->cds->count ## reponse is '2'
+
+=cut
+use Data::Dump qw/dump/;
+sub populate {
+ my ($self, $data) = @_;
+
+ if(defined wantarray) {
+ my @created;
+ foreach my $item (@$data) {
+ push(@created, $self->create($item));
+ }
+ return @created;
+ } else {
+ my ($first, @rest) = @$data;
+
+ my @names = grep {!ref $first->{$_}} keys %$first;
+ my @rels = grep { $self->result_source->has_relationship($_) } keys %$first;
+ my @pks = $self->result_source->primary_columns;
+
+ ## do the belongs_to relationships
+ foreach my $index (0..$#{@$data})
+ {
+ foreach my $rel (@rels)
+ {
+ next unless $data->[$index]->{$rel} && ref $data->[$index]->{$rel} eq "HASH";
+
+ my $result = $self->related_resultset($rel)->create($data->[$index]->{$rel});
+
+ my ($reverse) = keys %{$self->result_source->reverse_relationship_info($rel)};
+
+ my $related = $result->result_source->resolve_condition(
+
+ $result->result_source->relationship_info($reverse)->{cond},
+ $self,
+ $result,
+ );
+
+ delete $data->[$index]->{$rel};
+ $data->[$index] = {%{$data->[$index]}, %$related};
+
+ push @names, keys %$related if $index == 0;
+ }
+ }
+
+ my @values = map {
+ [ map {
+ defined $_ ? $_ : $self->throw_exception("Undefined value for column!")
+ } @$_{@names} ]
+ } @$data;
+
+ $self->result_source->storage->insert_bulk(
+ $self->result_source,
+ \@names,
+ \@values,
+ );
+
+ ## do the has_many relationships
+ foreach my $item (@$data) {
+
+ foreach my $rel (@rels) {
+ next unless $item->{$rel} && ref $item->{$rel} eq "ARRAY";
+
+ my $parent = $self->find(map {{$_=>$item->{$_}} } @pks) || next;
+ my $child = $parent->$rel;
+
+ my $related = $child->result_source->resolve_condition(
+ $parent->result_source->relationship_info($rel)->{cond},
+ $child,
+ $parent,
+ );
+
+ my @rows_to_add = ref $item->{$rel} eq 'ARRAY' ? @{$item->{$rel}} : ($item->{$rel});
+ my @populate = map { {%$_, %$related} } @rows_to_add;
+
+ $child->populate( \@populate );
+ }
+ }
+ }
+}
+
=head2 pager
=over 4
my $join_count = $seen->{$rel};
my $alias = ($join_count > 1 ? join('_', $rel, $join_count) : $rel);
- $self->_source_handle->schema->resultset($rel_obj->{class})->search_rs(
- undef, {
- %{$self->{attrs}||{}},
- join => undef,
- prefetch => undef,
- select => undef,
- as => undef,
- alias => $alias,
- where => $self->{cond},
- seen_join => $seen,
- from => $from,
- });
+ #XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi
+ my %attrs = %{$self->{attrs}||{}};
+ delete $attrs{result_class};
+
+ my $new_cache;
+
+ if (my $cache = $self->get_cache) {
+ if ($cache->[0] && $cache->[0]->related_resultset($rel)->get_cache) {
+ $new_cache = [ map { @{$_->related_resultset($rel)->get_cache} }
+ @$cache ];
+ }
+ }
+
+ my $new = $self->_source_handle
+ ->schema
+ ->resultset($rel_obj->{class})
+ ->search_rs(
+ undef, {
+ %attrs,
+ join => undef,
+ prefetch => undef,
+ select => undef,
+ as => undef,
+ alias => $alias,
+ where => $self->{cond},
+ seen_join => $seen,
+ from => $from,
+ });
+ $new->set_cache($new_cache) if $new_cache;
+ $new;
};
}
use base qw/DBIx::Class/;
use Carp::Clan qw/^DBIx::Class/;
+use Scalar::Util ();
__PACKAGE__->mk_group_accessors('simple' => qw/_source_handle/);
Creates a new row object from column => value mappings passed as a hash ref
+Passing an object, or an arrayref of objects as a value will call
+L<DBIx::Class::Relationship::Base/set_from_related> for you. When
+passed a hashref or an arrayref of hashrefs as the value, these will
+be turned into objects via new_related, and treated as if you had
+passed objects.
+
=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
+## 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 {
my ($class, $attrs) = @_;
$class = ref $class if ref $class;
unless ref($attrs) eq 'HASH';
my ($related,$inflated);
+ ## Pretend all the rels are actual objects, unset below if not, for insert() to fix
+ $new->{_rel_in_storage} = 1;
+
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);
if ($info && $info->{attrs}{accessor}
&& $info->{attrs}{accessor} eq 'single')
{
- $new->set_from_related($key, $attrs->{$key});
- $related->{$key} = $attrs->{$key};
+ 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);
+ }
+ $new->set_from_related($key, $rel_obj);
+ $related->{$key} = $rel_obj;
next;
- }
- elsif ($class->has_column($key)
- && exists $class->column_info($key)->{_inflate_info})
+ } elsif ($info && $info->{attrs}{accessor}
+ && $info->{attrs}{accessor} eq 'multi'
+ && ref $attrs->{$key} eq 'ARRAY') {
+ 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;
+ }
+ }
+ $related->{$key} = $others;
+ next;
+ } elsif ($info && $info->{attrs}{accessor}
+ && $info->{attrs}{accessor} eq 'filter')
{
+ ## '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);
+ }
+ $inflated->{$key} = $rel_obj;
+ next;
+ } elsif ($class->has_column($key)
+ && $class->column_info($key)->{_inflate_info}) {
$inflated->{$key} = $attrs->{$key};
next;
}
}
+ use Data::Dumper;
$new->throw_exception("No such column $key on $class")
unless $class->has_column($key);
$new->store_column($key => $attrs->{$key});
$self->throw_exception("No result_source set on this object; can't insert")
unless $source;
+ # Check if we stored uninserted relobjs here in new()
+ my %related_stuff = (%{$self->{_relationship_data} || {}},
+ %{$self->{_inflated_column} || {}});
+ if(!$self->{_rel_in_storage})
+ {
+ $source->storage->txn_begin;
+
+ ## Should all be in relationship_data, but we need to get rid of the
+ ## 'filter' reltype..
+ ## These are the FK rels, need their IDs for the insert.
+ foreach my $relname (keys %related_stuff) {
+ my $rel_obj = $related_stuff{$relname};
+ if(Scalar::Util::blessed($rel_obj) && $rel_obj->isa('DBIx::Class::Row')) {
+ $rel_obj->insert();
+ $self->set_from_related($relname, $rel_obj);
+ }
+ }
+ }
+
$source->storage->insert($source, { $self->get_columns });
+
+ ## PK::Auto
+ my ($pri, $too_many) = grep { !defined $self->get_column($_) ||
+ ref($self->get_column($_)) eq 'SCALAR'} $self->primary_columns;
+ if(defined $pri) {
+ $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
+ if defined $too_many;
+
+ my $storage = $self->result_source->storage;
+ $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
+ unless $storage->can('last_insert_id');
+ my $id = $storage->last_insert_id($self->result_source,$pri);
+ $self->throw_exception( "Can't get last insert id" ) unless $id;
+ $self->store_column($pri => $id);
+ }
+
+ if(!$self->{_rel_in_storage})
+ {
+ ## Now do the has_many rels, that need $selfs ID.
+ foreach my $relname (keys %related_stuff) {
+ my $relobj = $related_stuff{$relname};
+ if(ref $relobj eq 'ARRAY') {
+ foreach my $obj (@$relobj) {
+ my $info = $self->relationship_info($relname);
+ ## What about multi-col FKs ?
+ my $key = $1 if($info && (keys %{$info->{cond}})[0] =~ /^foreign\.(\w+)/);
+ $obj->set_from_related($key, $self);
+ $obj->insert() if(!$obj->in_storage);
+ }
+ }
+ }
+ $source->storage->txn_commit;
+ }
+
$self->in_storage(1);
$self->{_dirty_columns} = {};
$self->{related_resultsets} = {};
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})
{
keys %{$self->{_dirty_columns}};
}
+ =head2 get_inflated_columns
+
+ my $inflated_data = $obj->get_inflated_columns;
+
+ Similar to get_columns but objects are returned for inflated columns instead of their raw non-inflated values.
+
+ =cut
+
+ sub get_inflated_columns {
+ my $self = shift;
+ return map {
+ my $accessor = $self->column_info($_)->{'accessor'} || $_;
+ ($_ => $self->$accessor);
+ } $self->columns;
+ }
+
=head2 set_column
$obj->set_column($col => $val);
$fetched = $pre_source->result_class->inflate_result(
$pre_source, @{$pre_val});
}
+ $new->related_resultset($pre)->set_cache([ $fetched ]);
my $accessor = $source->relationship_info($pre)->{attrs}{accessor};
$class->throw_exception("No accessor for prefetched $pre")
unless defined $accessor;