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.

6 Comments

Don't forget to use Sub::Name!

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.

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.

Leave a comment

About Mike Friedman

user-pic Mike Friedman is a professional computer programmer living and working in the New York City area.