DBIx::Class Anti-Verbosity
Every time I set up a DBIx::Class schema for a project, I find myself re-implementing the same pattern. While DBIC is one of the great things about modern Perl, it requires a lot of typing. Whenever I want to grab some stuff from a table, it's
my @records = $schema->resultset( 'CamelCasedClassName' )->search( ... );
Today, I used DBIx::Class::Schema::Loader to auto-generate a schema for a fun little 182-table database. I ended up with modules with names like MyApp::DBIC::Result::XStreamQueryParentArticle
and MyApp::DBIC::Result::ArticleElementContent
and so forth. Very useful, but what painful nomenclature.
So I ended up doing the same little hack that I always do, but improved upon it a bit this time, and have finally arrived at a solution that I really like.
First, I modified my main DBIC class to instantiate the schema and wrap that up in a little object for which there is a simple constructor.
sub new {
my $class = shift;
my %args =
( type => 'master',
@_
);
# ... bunch of config stuff here
my $dsn = sprintf 'dbi:mysql:%s;host=%s', $db, $host;
my $schema = $class->connect( $dsn, $user, $pass );
$args{schema} = $schema;
return bless \%args;
}
Then I added a loop to load up some shortcut methods when the module starts up:
foreach my $source ( __PACKAGE__->sources ) {
my $source_class = 'MyApp::DBIC::Result::' . $source;
my $tbl = $source_class->table;
no strict 'refs';
*{ 'MyApp::DBIC::rs_' . $tbl } = sub {
my $self = shift;
return $self->{schema}->resultset( $source );
};
}
Now, instead of writing
my $schema = MyApp::DBIC->connect( ... );
my @records = $schema->resultset( 'OmnitureArticlePopularity' )->search( ... )
I can write
my $db = MyApp::DBIC->new( type => 'master' );
my @records = $db->rs_omniture_article_popularity->search( ... )
The auto-generated method names all come from the DB's table names (prepended with rs_
) which are much easier to remember, and easier to type, than the CamelCased module names. The $db
object also keeps track of some useful data about our environment, where there are multiple dev/stage/production databases and abstracts away the configuration.
Don't forget to use Sub::Name!
I'm not familiar with Sub::Name but I just looked it up. I don't quite see what it does that would be useful here. Can you elaborate?
These generated methods will show up in stack traces as
__ANON__
. Sub::Name lets you tag them with the correct name so that stack traces will read properly.Ah, cool. I'll try it out.
If you don't like the way names are generated by DBIx::Class:Schema::Loader you might want to poke around the docs (http://search.cpan.org/~rkitover/DBIx-Class-Schema-Loader-0.06001/) and see if there's anything you can do to influence the naming of bits to your satisfaction.
I usually try to reserve Schema methods for domain logic that is cutting across results, for whatever my thoughts are worth :)
Took me a while to remember the name, but you might find DBICx::Shortcuts useful.