In our Catalyst controllers, we have methods like this:
sub allowed_query_params : Private { my ($self, $c) = @_; return $c->forward( 'parameters_for_type', [ qw(tag broadcast) ]); }
That let's our code know that this controller only allows query parameters for "broadcast" and "tag" searches. Along with this, everything allows "pagination" parameters. Magically, this allows us to append /doc/ to the end of most URLs for our REST interface and developers automatically see real, programatically generated documentation for allowed query parameters. (Er, sort of. This is a new, unreleased feature.)
Except that doesn't quite work. It's on a per controller basis, not a per resource basis. A controller might handle multiple resources:
/api/v1/episode/ /api/v1/episode/changes/ /api/v1/episode/pid.b0034c98/children/
Each of those resources creates a different DBIx::Class resultset. Each resultset may have a slightly different interface and thus require different query parameters to be used. This is where our conversion to Moose roles has made life much easier. In our result set base class (we may eventually eliminate all inheritance), we have this:
use Moose; use MooseX::ClassAttribute; extends 'DBIx::Class::ResultSet'; class_has query_params => ( is => 'ro', default => sub { PIPs::QueryParams->new( params => { rows => { datatype => "xsd:positive-integer" }, page => { datatype => "xsd:positive-integer" }, }, ); });
(If I recall correct, class data is not built into Moose because Stevan objects to class data. Correct me if I'm wrong!)
That bit of code ensures that all resultset classes have pagination parameters. Now let's say that an individual resulset class only allows 'tag' searches. It might look like this:
package Some::ResultSet::Class; use Moose; extends 'Some::ResultSet::Base'; with qw( DoesTagSearch );
And in the DoesTagSearch role we have this:
around query_params => sub { my ($query_params, $proto) = @_; my $params = $proto->$query_params; $params->combine_params( PIPs::QueryParams->new( params => { tag_pid => {}, tag_value => {}, tag_scheme_pid => {}, tag_scheme_namespace => {}, }, ), ); };
Now for every resource, we know the resultset class we're getting back and we can just call this:
my $params = Some::ResultSet::Class->query_params;
Merely by specifying what type of search a resultset allows, the controllers can automatically infer what type of query parameters they support. Do we want to add broadcast and title searches to a resource?
package Some::ResultSet::Class; use Moose; extends 'Some::ResultSet::Base'; with qw( DoesTagSearch DoesBroadcastSearch DoesTitleSearch );
And everything magically "just works". All of that silly "allowed_query_params" mess is going away from our controllers because that's duplicating knowledge and we've already been bitten by having to maintain this duplication. Adding new features to resultset classes is becoming stupidly easy at times.
In other news, I'll be at the Perl-QA Hackathon tomorrow. Yay!