DBIx-EAV

 view release on metacpan or  search on metacpan

lib/DBIx/EAV/ResultSet.pm  view on Meta::CPAN


sub inflate_entity {
    my ($self, $data) = @_;
    my $type = $self->type;
    $type = $self->eav->type_by_id($data->{entity_type_id})
        if $data->{entity_type_id} && $data->{entity_type_id} != $type->id;

    my $entity = $self->entity_class->new( eav => $self->eav, type => $type, raw => $data );
    $entity->load_attributes;
    $entity;
}


{
    no warnings;
    *create = \&insert;
}

sub insert {
    my ($self, $data) = @_;
    $self->new_entity($data)->save;
}


sub populate {
    my ($self, $data) = @_;
    die 'Call populate(\@items)' unless ref $data eq 'ARRAY';

    my @result;
    foreach my $item (@$data) {
        push @result, $self->insert($item);
    }

    return wantarray ? @result : \@result;
}


sub update {
    my ($self, $data, $where) = @_;

    $where //= {};
    $where->{entity_type_id} = $self->type->id;

    # do a direct update for static attributes

}


sub delete {
    my $self = shift;
    my $eav = $self->eav;
    my $type = $self->type;
    my $entities_table = $eav->table('entities');

    # Call delete_all for SQLite since it doesn't
    # support delete with joins.
    # Better solution welcome.
    return $self->delete_all if
        $self->eav->schema->db_driver_name eq 'SQLite';

    unless ($eav->schema->database_cascade_delete) {

        # delete links by relationship id
        my @ids = map { $_->{id} } $type->relationships;

        $eav->table('entity_relationships')->delete(
            {
                relationship_id => \@ids,
                $entities_table->name.'.entity_type_id' => $type->id
            },
            { join => { $entities_table->name =>  [{ 'me.left_entity_id' => 'their.id' }, { 'me.right_entity_id' => 'their.id' }] } }
        );

        # delete attributes:
        # - group attrs by data type so only one DELETE command is sent per data type
        # - restrict by entity_type_id so we dont delete parent/sibiling/child data
        my %types;
        push @{ $types{$_->{data_type}} }, $_->{id}
            for $type->attributes(no_static => 1);

        while (my ($data_type, $ids) = each %types) {

            my $value_table = $eav->table('value_'.$data_type);
            $value_table->delete(
                {
                    attribute_id => $ids,
                    $entities_table->name.'.entity_type_id' => $type->id
                },
                { join => { $entities_table->name => { 'me.entity_id' => 'their.id' } } }
            );
        }
    }

    $entities_table->delete({ entity_type_id => $type->id });
}


sub delete_all {
    my $self = shift;

    my $rs = scalar @_ > 0 ? $self->search_rs(@_) : $self;
    my $i = 0;

    while (my $entity = $rs->next) {
        $entity->delete;
        $i++;
    }

    $i;
}


sub find {
    my ($self, $criteria, $options) = @_;

    croak "Missing find() criteria."
        unless defined $criteria;

    # simple id search
    return $self->search_rs({ id => $criteria }, $options)->next
        unless ref $criteria;

lib/DBIx/EAV/ResultSet.pm  view on Meta::CPAN

sub distinct {
    die "distinct() not implemented";
}

sub storage_size {
    die "storage_size() not implemented";
}


1;

__END__


=encoding utf-8

=head1 NAME

DBIx::EAV::ResultSet - Represents a query used for fetching a set of entities.

=head1 SYNOPSIS

    # resultsets are bound to an entity type
    my $cds_rs = $eav->resultset('CD');


    # insert CDs
    my $cd1 = $cds_rs->insert({ title => 'CD1', tracks => \@tracks });
    my $cd2 = $cds_rs->insert({ title => 'CD2', tracks => \@tracks });
    my $cd3 = $cds_rs->insert({ title => 'CD3', tracks => \@tracks });

    # ... or use populate() to insert many
    my (@cds) = $cds_rs->populate(\@cds);


    # find all 2015 cds
    my @cds = $eav->resultset('CD')->search({ year => 2015 });

    foreach my $cd (@cds) {

        printf "CD '%s' has %d tracks.\n",
            $cd->get('title'),
            $cd->get('tracks')->count;
    }

    # find one
    my $cd2 = $cds_rs->search_one({ name => 'CD2' });

    # find by related attribute
    my $cd2 = $cds_rs->search_one({ 'tracks.title' => 'Some CD2 Track' });

    # count
    my $top_cds_count = $cds_rs->search({ rating => { '>' => 7 } })->count;


    # update

    # delete all entities
    $cds_rs->delete;      # fast, but doesn't deletes related entities

    $cds_rs->delete_all;  # cascade delete all cds and related entities


=head1 DESCRIPTION

A ResultSet is an object which stores a set of conditions representing
a query. It is the backbone of DBIx::EAV (i.e. the really
important/useful bit).

No SQL is executed on the database when a ResultSet is created, it
just stores all the conditions needed to create the query.

A basic ResultSet representing the data of an entire table is returned
by calling C<resultset> on a L<DBIx::EAV> and passing in a
L<type|DBIx::EntityType> name.

  my $users_rs = $eav->resultset('User');

A new ResultSet is returned from calling L</search> on an existing
ResultSet. The new one will contain all the conditions of the
original, plus any new conditions added in the C<search> call.

A ResultSet also incorporates an implicit iterator. L</next> and L</reset>
can be used to walk through all the L<entities|DBIx::EAV::Entity> the ResultSet
represents.

The query that the ResultSet represents is B<only> executed against
the database when these methods are called:
L</find>, L</next>, L</all>, L</first>, L</count>.

If a resultset is used in a numeric context it returns the L</count>.
However, if it is used in a boolean context it is B<always> true.  So if
you want to check if a resultset has any results, you must use C<if $rs
!= 0>.

=head1 METHODS

=head2 new_entity

=over 4

=item Arguments: \%entity_data

=item Return Value: L<$entity|DBIx::EAV::EntityType>

=back

Creates a new entity object of the resultset's L<type|DBIx::EAV::EntityType> and
returns it. The row is not inserted into the database at this point, call
L<DBIx::EAV::Entity/save> to do that. Calling L<DBIx::EAV::Entity/in_storage>
will tell you whether the entity object has been inserted or not.

    # create a new entity, do some modifications...
    my $cd = $eav->resultset('CD')->new_entity({ title  => 'CD1' });
    $cd->set('year', 2016);

    # now insert it
    $cd->save;

=head2 insert

lib/DBIx/EAV/ResultSet.pm  view on Meta::CPAN


=over

=item WARNING

When subclassing ResultSet never attempt to override this method. Since
it is a simple shortcut for C<< $self->new_entity($data)->save >>, a
lot of the internals simply never call it, so your override will be
bypassed more often than not. Override either L<DBIx::EAV::Entity/new>
or L<DBIx::EAV::Entity/save> depending on how early in the
L</insert> process you need to intervene.

=back

=head2 populate

=over 4

=item Arguments: \@entites

=item Return Value: L<@inserted_entities|DBIx:EAV::Entity>

=back

Shortcut for inserting multiple entities at once. Returns a list of inserted
entities.

    my @cds = $eav->resultset('CD')->populate([
        { title => 'CD1', ... },
        { title => 'CD2', ... },
        { title => 'CD3', ... }
    ]);


=head2 count

=over 4

=item Arguments: \%where, \%options

=item Return Value: $count

=back

Performs an SQL C<COUNT> with the same query as the resultset was built
with to find the number of elements. Passing arguments is equivalent to
C<< $rs->search($cond, \%attrs)->count >>

=head2 delete

=over 4

=item Arguments: \%where

=item Return Value: $underlying_storage_rv

=back

Deletes the entities matching \%where condition without fetching them first.
This will run faster, at the cost of related entities not being casdade deleted.
Call L</delete_all> if you want to cascade delete related entities.

When L<DBIx::EAV/database_cascade_delete> is enabled, the delete operation is
done in a single query. Otherwise one more query is needed for each of the
L<values table|DBIx::EAV::Schema> and another for the
L<relationship link table|DBIx::EAV::Schema>.

=over

=item WARNING

This method requires database support for C<DELETE ... JOIN>. Since the current
implementation of DBIx::EAV is only tested against MySQL and SQLite, this method
calls L</delete_all> if SQLite database is detected.

=back

=head2 delete_all

=over 4

=item Arguments: \%where, \%options

=item Return Value: $num_deleted

=back

Fetches all objects and deletes them one at a time via
L<DBIx::EAV::Entity/delete>. Note that C<delete_all> will cascade delete related
entities, while L</delete> will not.

=head1 QUERY OPTIONS

=head2 limit

=over 4

=item Value: $rows

=back

Specifies the maximum number of rows for direct retrieval or the number of
rows per page if the page option or method is used.

=head2 offset

=over 4

=item Value: $offset

=back

Specifies the (zero-based) row number for the  first row to be returned, or the
of the first row of the first page if paging is used.

=head2 page

NOT IMPLEMENTED.

=head2 group_by

=over 4

=item Value: \@columns

=back

A arrayref of columns to group by. Can include columns of joined tables.

  group_by => [qw/ column1 column2 ... /]

=head2 having

=over 4

=item Value: \%condition

=back

The HAVING operator specifies a B<secondary> condition applied to the set
after the grouping calculations have been done. In other words it is a
constraint just like L</QUERY> (and accepting the same
L<SQL::Abstract syntax|SQL::Abstract/WHERE CLAUSES>) applied to the data
as it exists after GROUP BY has taken place. Specifying L</having> without
L</group_by> is a logical mistake, and a fatal error on most RDBMS engines.
Valid fields for criteria are all known attributes, relationships and related
attributes for the type this cursor is bound to.

E.g.



( run in 1.060 second using v1.01-cache-2.11-cpan-5a3173703d6 )