7

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
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46

2 Answers2

7

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;
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46
  • 1
    use strict; and use warnings; is enabled automatically by use Moose and not required. I use namespace::autoclean instead of MooseX::MarkAsMethods which works fine for me too. – Alexander Hartmaier Mar 18 '14 at 15:23
  • Thank you for pointing that out. I read a ton of documentation today and can't find the exact place recommending `MooseX::MarkAsMethods` over `namespace::autoclean` right now. Do you think there's a good reason to stick to any of both? – Daniel Böhmer Mar 18 '14 at 15:32
  • Thanks for the guide, I'm favoriting this. http://stackoverflow.com/questions/7734892/why-doesnt-use-overload-work-with-use-namespaceautoclean addresses namespace::autoclean and MooseX::MarkAsMethods. – Oesor Mar 18 '14 at 16:30
  • @Oesor Thank you for that link. According to one of the answers there `MooseX::MarkAsMethods` was invented to address some problems of `namespace::autoclean` with advanced (not to say weird) uses of `Moose`. So it seems to be the safest way to keep it like that. – Daniel Böhmer Mar 18 '14 at 16:42
5

What's wrong with using use_moose in DBIx::Class::Schema::Loader? (e.g. dbicdump -o use_moose=1 MyApp::Schema <dsn> <user> <pass>

This is what use_moose=1 in DBIx::Class::Schema::Loader 0.07039 generates for the schema:

use utf8;
package MyApp::Schema;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE

use Moose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Schema';

__PACKAGE__->load_namespaces;


# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-03-19 22:50:18
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7Hx1RMeFsxCqo5YaLOzPdQ


# You can replace this text with custom code or comments, and it will be preserved on regeneration
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
1;

And the Result Class:

use utf8;
package MyApp::Schema::Result::Example;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE

use strict;
use warnings;

use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Core';

...yadda...


# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-03-19 22:50:18
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yTmu6Rh9TAEwxqgDClBdtg


# You can replace this text with custom code or comments, and it will be preserved on regeneration
__PACKAGE__->meta->make_immutable;
1;

And here's the documentation for creating a Moosified ResultSet class

Looks like it does have some extraneous use strict and use warnings but no biggie. The good part is that if anything changes, hopefully the DBIx::Class::Schema::Loader will be updated to account for any needed changes.

Apologies if I'm missing the point.

mikew
  • 912
  • 2
  • 11
  • 22
  • Thank you for pointing this out. I've started to develop database tables by writing the `Result` classes and deploy the database using the schema. I could have checked what the loader generates for my deployed schema but I still find it useful to have a template for writing a nice schema in the first place. Also I found some differences between different manuals and start the community wiki article to begin with a definite answer. – Daniel Böhmer Mar 20 '14 at 15:33
  • This should be chosen to be the right answer. It is the officially documented and supported method of dbicdump (part of DBIx::Class::Schema::Loader) to create Moose compatible DBIx::Class files ... See https://metacpan.org/pod/DBIx::Class::Schema::Loader::Base#use_moose –  May 17 '14 at 19:59