Loadable Traits for Catalyst Components

Over the past few months we've developed a loadable traits system for Catalyst components. Components are Models, Views, and Controllers.

Traits are Moose::Roles, that are applied to classes dynamically.

Roles for Controllers, with actual actions, are possible thanks to Tomas Doran's (t0m) and Florian Ragwitz's (rafl) work on MooseX::MethodAttributes.

Loadable traits support is provided by CatalystX::Component::Traits, based on Jonathan Rockway's (jrockway) work on MooseX::Traits.

An Example

Let's make a simple Model and some traits for it, so you can see how you might make use of this feature in your own projects.

    package SampleApp::Model::Fortune;

    use Moose;
    use namespace::autoclean;
    extends 'Catalyst::Model';
    with 'CatalystX::Component::Traits';

    has '+_trait_merge' => (default => 1);

    __PACKAGE__->config->{traits} = [ 'OffensiveToo' ];

    has fortune_command      => (is => 'rw', lazy_build => 1);
    has fortune_command_opts => (is => 'rw', lazy_build => 1);

    sub get_fortune {
        my $self = shift;

        my $command =
            $self->fortune_command . ' ' . $self->fortune_command_opts;

        my $output = qx{$command};
        chomp $output;

        return $output;
    }

    sub _build_fortune_command { 'fortune' }
    sub _build_fortune_command_opts { '' }

    __PACKAGE__->meta->make_immutable;

    package SampleApp::TraitFor::Model::Fortune::Russian;

    use Moose::Role;
    use namespace::autoclean;

    has percent_russian => (is => 'rw', default => 100);

    around fortune_command_opts => sub {
        my ($next, $self) = (shift, shift);

        my $dbs =
            $self->percent_russian . '% ru'
            . ' ' .
            (100 - $self->percent_russian) . '% /usr/share/games/fortunes';

        return $self->$next(@_) . ' ' . $dbs;
    };

    package SampleApp::TraitFor::Model::Fortune::Offensive;

    use Moose::Role;
    use namespace::autoclean;

    around fortune_command_opts => sub {
        my ($next, $self) = (shift, shift);

        return '-o ' . $self->$next(@_);
    };

    package SampleApp::TraitFor::Model::Fortune::OffensiveToo;

    use Moose::Role;
    use namespace::autoclean;

    around fortune_command_opts => sub {
        my ($next, $self) = (shift, shift);

        my $opts = $self->$next(@_);
        $opts =~ s/-o //; # if Offensive trait is enabled, edit it out

        return "-a $opts";
    };

    1;

Notice we turned on the "TRAIT MERGING" in CatalystX::Component::Traits feature, and we set a default list of traits to include (OffensiveToo.)

An action to make use of our new Model:

    sub index :Path :Args(0) {
        my ($self, $c) = @_; 

        $c->res->content_type('text/plain; charset=utf-8');
        $c->res->body($c->model('Fortune')->get_fortune);
    }

Now, suppose in production you don't want your customers to see offensive fortunes, and you want 50% of the fortunes to be in Russian.

Just put the following into the .conf:

    <Model::Fortune>
        traits -OffensiveToo
        traits Russian
        percent_russian 50
    </Model::Fortune>

We turned off the OffensiveToo trait, added the Russian trait and set an attribute that was defined in a trait directly from the config file.

Hopefully this demonstrates some of the power of using Moose::Roles in your Catalyst applications.

See also the Catalyst::ActionRole:: namespace for other awesome applications of Moose::Roles.

Components that use Loadable Traits

Catalyst::Model::DBIC::Schema

The model for using DBIx::Class in Catalyst has the following traits available on CPAN:

Catalyst::TraitFor::Model::DBIC::Schema::Caching

For caching the results of queries.

Catalyst::TraitFor::Model::DBIC::Schema::Replicated

For quering replicated MySQL databases.

Catalyst::TraitFor::Model::DBIC::Schema::QueryLog

DBIx::Class::QueryLog support, for analyzing query performance.

CatalystX::SimpleLogin

A reusable login/logout component for Catalyst that is injected through the Plugin list. It has the following traits available on CPAN:

CatalystX::SimpleLogin::TraitFor::Controller::Login::Logout

Adds logout support.

CatalystX::SimpleLogin::TraitFor::Controller::Login::WithRedirect

Combines with Catalyst::ActionRole::NeedsLogin to mark actions as requiring a login and redirecting back to the originally requested page.

CatalystX::SimpleLogin::TraitFor::Controller::Login::RenderAsTTTemplate

Provides a Template template for rendering the login form, for use with Catalyst::View::TT.

AUTHOR

Caelum: Rafael Kitover <rkitover@cpan.org>