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

Jess Robinson castaway at desert-island.me.uk
Thu Mar 12 09:19:01 GMT 2009


Thanks Jason! I was reading through this thread muttering about blind 
leading blind here.. No offense meant, David, fREW.. But you guys missed a 
few things, so I need to get into the DBIC docs and make sure the next lot 
don't miss them..

On Fri, 20 Feb 2009, Jason Gottshall wrote:

> David Ihnen wrote:
>>  Jason Gottshall wrote:
>> >  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?
>> 
> 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.

In addition to what Jason said about chained rewsultsets/searches being 
saner, the search() method overriding idea doesn't catch all the places 
where you might want searches restricted.

For example:

   $schema->resultset('Books');

Returns a perfectly usable Books resultset, and wont know about any 
restriction in search to non-deleted books only.

Also:

   $author->books;

Will return all books belonging to an author via a has_many relationship, 
and also wont see any changes / modifications to the "search" method in 
the Books resultset.

Writing your own explicit one to call as Jason showed is saner, then you 
know when you're doing it.

If you want a permanent one for always, that also works across chained 
resultsets and relationships, use the resultset_attributes class method, 
eg:

   __PACKAGE__->resultset_attributes({ where => { deleted => undef }});

"undef" in SQL::Abstract produces an "IS NULL" btw, so no need for those 
literal strings.

Jess (adding to docs!)



More information about the DBIx-Class mailing list