[Dbix-class] Fun with auto-restricted result sets

Jason Gottshall jgottshall at capwiz.com
Fri Feb 20 19:51:15 GMT 2009


David Ihnen wrote:
> Jason Gottshall wrote:
>> David Ihnen wrote:
>>> So in the wake of fREW showing a modification of the classes to allow 
>>> the modification of the delete, I utilized a corrollary concept... 
>>> restricting a result set automatically/transparently, as you would in 
>>> case of that delete - not normally showing the deleted rows.  In my 
>>> case, my alerts table has a possible action window, possibly has been 
>>> dismissed, and possibly might be inactive.  This made the resultset 
>>> class more complicated, but not unmanageable. It was fun to work 
>>> through the logic of modifying an arbitrarily complex requested 
>>> search clause.  If you specify values for these, it is assumed you 
>>> know what you're doing and this result set class won't further modify 
>>> the terms.  I may have missed the handling of already defined 
>>> alert_expire terms though... maybe I should make it inactive if you 
>>> specify any of the terms anywhere in the \%where tree.
>>>
>>>
>>> package DB::Schema::active_alert_resultset;
>>> use base 'DBIx::Class::ResultSet';
>>>
>>> sub search {
>>>    my $self = shift;
>>>    $_[0]->{'inactive'}     ||= 0;
>>>    $_[0]->{'dismissed'}    ||= \"IS NULL";
>>>    $_[0]->{'alert_time'}   ||= { '<' => \"NOW()" };
>>>    my $aeor = [];
>>>    push @{$aeor}, { alert_expire => { '>' => \'NOW()'} };
>>>    push @{$aeor}, { alert_expire => \'IS NULL' };
>>>    if ($_[0]->{'-or'}) {  # If there is already an -or, we need to 
>>> nest it into an -and
>>>                           # so we don't overwrite the term, or change 
>>> the logic by orring with it.
>>>      my $and = $_[0]->{'-and'} ||= [];  # Use an existing -and term 
>>> if already supplied.
>>>      push @{$and}, { -or => delete $_[0]->{'-or'} }; # Move the old -and
>>>      push @{$and}, { -or => $aeor }; # add our alert expire or term
>>>    } else {
>>>      $_[0]->{'-or'} = $aeor;
>>>    }
>>>    return $self->next::method( @_ );
>>> }
>>
>> You're only checking to see if the given columns have specified values 
>> passed in, but what if ->search was called on a resultset object that 
>> *already* has constraints for these columns?
> What?  How can you be in a result class and not in it too?  Can you give 
> an example of how to write that in code?
> 
> 
>> Until DBIC goes Moose, it's tricky (and not very safe) to introspect 
>> an existing RS to see what's already been done to it...
> If I have already restricted the resultset, then re-restricting it by 
> the same terms will filter out whats not there, right?  And asking for 
> dismissed => \'IS NOT NULL' on an alerts (or derived) resultset will 
> always give you empty set.  No active alerts have dismissed NOT NULL

The problem comes when you try to "derive" one resultset from another; 
any criteria not present are clobbered by your default assumptions. 
Let's say that one part of your code retrieves inactive alerts:

   my $rs = $schema->resultset('alert')->search({inactive => 1});

Then another part of your code wants to take that same result set and 
limit it to just those alerts from last year:

   my $new_rs = $rs->search({alert_time => {'<' => \'current_yr()'}});
   # replacing 'current_yr()' with the appropriate syntax for your dbms

Using your methodology, this invocation of ->search will set 'inactive' 
back to '0', because it's not passed in.

Of course, if you only call search once with both criteria, you don't 
have a problem. But I've been finding the additive technique (sometimes 
referred to as "resultset chaining") to make for very clean, readable, 
maintainable code. In fact, you could use such an approach as an 
alternative to overriding ->search with your defaults:

   package DB::ResultSet::alerts;
   use base 'DBIx::Class::ResultSet';

   sub active {
       return shift->search({
           inactive   => 0,
           dismissed  => \'is null',
           alert_time => { '<' => \"NOW()" }
           '-or' => [
               { alert_expire => { '>' => \'NOW()'} },
               { alert_expire => \'IS NULL' },
           ],
       });
   }

Then anywhere in your code that you want to enforce "activeness", just do:

   my $rs = $schema->resultset('alerts')->active->search({foo => 'bar'});

It's not *quite* as slick as having it defaulted automatically, but it 
protects you from unwittingly clobbering previously set criteria.

-- 
Jason Gottshall
jgottshall at capwiz.com




More information about the DBIx-Class mailing list