Search code examples
perloopmoose

perl - searching in list of objects which are an accessor of another object


I am a Perl-OO beginner and I am encountering a design-challenge. I hope you can give me some hints to get to an elegant solution. I am working with Mouse Object System here.

For a minimal example lets say I have a User-Object. A user has a name.

package User;
use Mouse;    

has "name" => (
   is        => "rw",
   isa       => "Str|Undef",
);  

Then I have a User-Cache-Object, which gets a list of all Users (from an LDAP-Server). You can say this is a "has-a" Relationship between the User Cache and the User.

package UserCache;
use Mouse;    

has "users" => (
   is        => 'rw',
   isa       => 'ArrayRef|Undef',
   default   => sub { [] },
);  

I store this list of Users as an Array of User-Objects in the accessor of the User-Cache.

 my $cache = UserCache->new();

 foreach my $entry ( $ldap->searchGetEntries() ) { 
      my $user = User->new();
      $user->name($entry->get_value('userdn'));
      push @{ $cache->users }, $user;
 }

Now this is where my Problem comes in. If I want to find a User-Object with specific attributes (e.g. a User named John), I have to loop over this whole Array of User-Objects and query each object for its name. When given a list of names, this gets a really inefficient process.

foreach my $user ( @{ $cache->users } ) {
      if ( $user->name eq 'John' ) {
           #do something with John
      }...
} 

Is there a way of storing Lists of Objects in other Objects in a way, that I can efficently search? Like $cache->get_users->get_name('John') and that returns the object I need?


Solution

  • No. At least not universally. You can of course build indexes for common things. Or you could cache searches once you have done them.

    Lookups are best implemented as hashes. Those could be attached to the UserCache object. Something like:

    my @users = $cache->find( name => 'John' );
    

    That would internally map to a hashref with search fields.

    package UserCache;
    #...
    
    has _search_index => (
        is  => 'ro',
        isa => 'HashRef',
        default => sub { {} },
    );
    

    And the hash reference would look something like this:

    {
        name => {
            John => [
                User->new( name => 'John', last_name => 'Smith' ),
                User->new( name => 'John', last_name => 'Wayne' ),
                User->new( name => 'John', last_name => 'Bon Jovi' ),
            ],
            James => [ ... ],
        },
        id => {
            # ...
        },
    ),
    

    But again, you'd have to build those. So you need to do the lookup once. But I think the lookup should be done inside UserCache and stored there too.

    sub find {
        my ($self, $key, $value) = @_;
    
        # get operation
        return @{ $self->_search_index->{$key}->{$value} } 
            if exists $self->_search_index->{$key}->{$value};
    
        # set operation
        foreach my $user ( @{ $self->users } ) {
            push @{ $self->_search_index->{$key}->{$value} }, $user
                if $user->$key eq $value
        }
        return @{ $self->_search_index->{$key}->{$value} } 
    }
    

    This is a very naive implementation and it doesn't support multiple lookups, but it's a start.

    Note that if you have a lot of users and a lot of indexes, the data structure might become large.

    To make it easier, Moose's built-in traits might be helpful. If you want a stronger cache behavior, look at CHI.