Search code examples
perlmoosedbix-class

How to setup a DBIx::Class schema with Moose -- the definite guide


I found it rather difficult to find information about how to assemble a DBIx::Class schema structure using Moose. How to do that correctly (essentially working) and in modern Perl (good style, fast, without warnings)?

These are my goals:

  • follow Moose' Moose::Manual::BestPractices, especially:
    • use namespace::autoclean and
    • use __PACKAGE__->meta->make_immutable.
  • use common base classes for Result and ResultSet
  • when using any magic tricks have a comment explaining them (during research I found a guide suggesting sub BUILDARGS { $_[2] } explained by don't ask)
  • move common code, e.g. MooseX::NonMoose (if necessary) or __PACKAGE__->load_components, into common base class as suggested by DBIx::Class::Manual::Cookbook

These are the problems I ran into:

  • when using __PACKAGE__->meta->make_immutable I got warnings like Not inlining 'new' for MyApp::Schema::Result::MyTable since it is not inheriting the default Moose::Object::new
  • when moving all calls to __PACKAGE__->load_components to the Result base class my datetime columns didn't get inflated

Solution

  • The solutions to the problems that arose:

    • make_immutable conflicts with a non-Moose new constructor: this is automatically handled by use MooseX::NonMoose; in contrast to its documentation no further arguments or options are necessary; beware that DBIx::Class::Schema has no new method and therefore, MyApp::Schema does not need this helper
    • InflateColumn::DateTime not inflating when loaded in base class: This was triggered by the order of components given to load_components(); there is no hint in the documentation that order should matter and I've filed a bug report about this; reordering helped

    With the solutions above included my example DBIx::Class schema with Moose looks like this:

    Schema class:

    package MyApp::Schema;
    
    use Moose; # we want Moose
    use MooseX::MarkAsMethods autoclean => 1; # this helps removing unused symbols like Moose keywords
    # do NOT 'use MooseX::NonMoose' here because Schema class has no 'new' method
    
    extends 'DBIx::Class::Schema'; # the Moose way of inheritance
    
    # load all table modules automatically
    __PACKAGE__->load_namespaces(
        # ResultSet class for tables without custom ResultSet class
        # (would be DBIx::Class::ResultSet otherwise)
        default_resultset_class => '+MyApp::Schema::ResultSet',
    );
    
    # tell Moose this class is finished: some Moose stuff is removed and things go faster
    __PACKAGE__->meta->make_immutable;
    
    1;
    

    Common Result base class:

    # a base class for all table class of this app
    package MyApp::Schema::Result;
    
    use Moose;
    use MooseX::MarkAsMethods autoclean => 1;
    use MooseX::NonMoose; # this is important for correctly handling DBIx::Class' new
    
    extends 'DBIx::Class::Core';
    
    # this is the right place to implement generic stuff
    
    # DBIx::Class::Cookbook recommends loading components in a central place
    __PACKAGE__->load_components(qw/
        InflateColumn::DateTime
        ...
    /);
    
    __PACKAGE__->meta->make_immutable;
    
    1;
    

    Common ResultSet base class:

    package MyApp::Schema::ResultSet;
    
    use Moose;
    use MooseX::MarkAsMethods autoclean => 1;
    use MooseX::NonMoose;
    
    extends 'DBIx::Class::ResultSet';
    
    __PACKAGE__->meta->make_immutable;
    
    1;
    

    Example ResultSet class for table my_table:

    package MyApp::Schema::ResultSet::MyTable;
    
    use Moose;
    use MooseX::MarkAsMethods autoclean => 1;
    
    extends 'MyApp::Schema::ResultSet';
    
    sub oldest {
        my $self = shift;
    
        $self->search({}, {order_by => {-ASC => 'date'}})->first;
    }
    
    __PACKAGE__->meta->make_immutable;
    
    1;
    

    Example Result class for table my_table:

    package MyApp::Schema::Result::MyTable;
    
    use Moose;
    use MooseX::MarkAsMethods autoclean => 1;
    
    extends 'MyApp::Schema::Result';
    
    __PACKAGE__->table("my_table");
    
    __PACKAGE__->add_columns(
        id   => {data_type => "integer", is_auto_increment => 1},
        date => {data_type => "date"},
    );
    
    __PACKAGE__->set_primary_key("id");
    
    __PACKAGE__->meta->make_immutable;
    
    1;