I hope I’m not too late in my third week of posting for the iron man contest. My last post was on May 31, and so I should have posted something by June 7, but I was traveling cross-country to my original hometown of Los Angeles! Talk about a car city. I think that’s a good enough excuse. We’ll see if I get busted back to paper man.

I wanted to extend on my last post about a way to make sure your multiple-table-inheritance database can be updated properly across whatever views you are running your app off of. But I just got some new code to look at for that, and haven’t quite grokked it yet. So that will have to wait till next time.

Meanwhile, I’d like to present a couple of ideas I have been working on in my Metalabel rewrite, and perhaps solicit some feedback. I’d love to hear suggestions or strategies. The first is a module some people have seen around—it is called Moosifier, and it takes DBIx::Class Schema::Loader result class files, reads them, and adds Moose bits at the top and bottom to allow you to use your auto-generated classes with Reaction or whatever.

Before:


    package Test::Schema::Amazon;

    use strict;
    use warnings;

    use base 'DBIx::Class';

    __PACKAGE__->load_components("TimeStamp", "Core");
    __PACKAGE__->table("amazon");
    __PACKAGE__->add_columns(
      "asin",
      {
        data_type => "text",
        default_value => undef,
        is_nullable => 1,
        size => undef,
      },
    ...
    1;

After:


    package Test::Schema::Amazon;

    use Moose;

    extends 'DBIx::Class';

    has 'asin' => (
                    isa => SimpleStr,
                    is => 'rw',
                    required => 0

    );
    has 'locale' => (
                    isa => SimpleStr,
                    is => 'rw',
                    required => 0

    );
    has 'filename' => (
                    isa => SimpleStr,
                    is => 'rw',
                    required => 0

    );
    has 'refetchdate' => (
                    isa => Int,
                    is => 'rw',
                    required => 0

    );

    use namespace::autoclean;

    __PACKAGE__->load_components("TimeStamp", "Core");
    __PACKAGE__->table("amazon");
    __PACKAGE__->add_columns(
      "asin",
      {
        data_type => "text",
        default_value => undef,
        is_nullable => 1,
        size => undef,
      },
    ...
    __PACKAGE__->meta->make_immutable(inline_constructor => '0');
    1;

I wrote it because I thought I was going to be using Reaction in my rewrite, but phaylon advised against it. It adds a whole other layer to Catalyst’s M, V and C, and I agreed that it’s wisest to make sure the basic rewrite is solid before adding a whole new layer, and, for instance, having to think about chopping my as-yet-unwritten templates into widgets or what have you.

Thus Moosifier. It’s pretty darned simple: it works by reading the result class files and using a bunch of regexes to extract the Moose attributes from the __PACKAGE__->add_columns(...) lines. I was excited to put it up and finish it, but no sooner had I done so than I hit upon another problem. Now that I had my classes written, I had to make my controllers.

Metalabel 2.0 needs at least 76 controllers. There’s no way I’m going to write those by hand. I could use myapp/script/myapp_create.pl helper script to make skeleton controllers, of course. But that’s just a skeleton. What about basic CRUD? There’s no way I’m going to write CRUD for 76 controllers by hand.

I asked on #catalyst whether there are any tools to help generate CRUD automatically in your controllers, and glob recommended Catalyst::Controller::DBIC::API, for which there are RPC and REST implementations. Of course, these just provide a way to write your CRUD. They don’t do it for you. So I had a new automation problem: “How can I automate the creation of basic but smart RPC or REST controllers for my Catalyst app, based on my DBIx::Class::Schema::Loader result classes?”

Of course I asked on irc first. Knowing that I had done something similar with Moosifier, mst suggested that instead of reading the files from disk, I take the table info directly from the database using DBIx::Class::Schema::Loader. Why the hell not? It makes much more sense than stepping through a file with regexes and substitutions and such.

The goal is to create 76+ controllers (in this case, of the RPC subclass) like this sample code from the Catalyst::Controller::DBIC::API documentation:

        
    package MyApp::Controller::API::RPC::Artist;
    use base qw/Catalyst::Controller::DBIC::API::RPC/;

    __PACKAGE__->config ( action => { setup => { PathPart => 'artist', Chained => '/api/rpc/rpc_base' } },
    class => 'MyAppDB::Artist', # DBIC schema class
    create_requires => ['name', 'age'], # columns required to create
    create_allows => ['nickname'], # additional non-required columns that create allows
    update_allows => ['name', 'age', 'nickname'], # columns that update allows
    list_returns => [qw/name age/], # columns that list returns
    list_prefetch => ['cds'], # relationships that are prefetched when no prefetch param is passed
    list_prefetch_allows => [ # every possible prefetch param allowed
        'cds',
        qw/ cds /,
        { cds => 'tracks' },
        { cds => [qw/ tracks /] }
    ],
    list_ordered_by => [qw/age/], # order of generated list
    list_search_exposes => [qw/age nickname/, { cds => [qw/title year/] }],
    );

I figure I can dump out such a controller for every table I need by pulling the whole database into a hash using DBIx::Class::Schema::Loader, and using this hash to populate the __PACKAGE__->config(...) for each table/controller. For instance, the class name is easy. So is create_requires: those are the non-nullable columns in the table, besides any auto-incrementing serials or sequences. create_allows is every column but those in create_required, also excluding serials and sequences. update_allows is the columns in both create_allows and create_requires. I can’t escape some manual labor here: I am going to make list_returns return every column in the table. I’ll go through the controllers by hand to customize those. The same with list_prefetch_allows: the object of every relationship identified by DBIx::Class::Schema::Loader is going into list_prefetch_allows (none are going into the default list_prefetch). I’ll have to customize those manually, as well. list_ordered_by may need to be manually specified later as well, but using date column if it exists might be a good default. list_search_exposes will expose everything by default.

So, I am in the process of writing 2 modules to do this: one for RPC and one for REST. They’ll be on my github soon.

Meanwhile, using DBIx::Class::Schema::Loader to read table definitions directly from the database in these modules has made me realize I need to change Moosifier to do the same. In fact, D::C::S::Loader simply needs to be able to write the Moose declarations itself. Moosifier needs to move into Loader.

That’s my next project.

EDIT:

There’s a branch for it now. I need some tests though.


SPEAK / ADD YOUR COMMENT
Comments are moderated.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Return to Top