Moose::CAP

Dave Doyle
dave.s.doyle@gmail.com

Raybec Communications

This is my first talk

Moose::CAP

Why?

What's the point in rewriting a simple webapp framework with Moose?

Quickly: Moose

What's the big deal?

Moose in a nutshell

Making a Moose based class is pretty quick.

    Package My::Foo;
    use Moose;       # turns on strict and warnings
    
    # an attribute, think $self->{bar}
    has 'bar'   => (
        is      => 'rw',    # this creates an accessor/mutator
        isa     => 'Str',   # Basic type checking built in
        default => 'meese', # a default value
        lazy    => 1,       # lazy load
    );
    

The new constructor and accessor/mutator bar method is created for you.

One more time in classic OO

    package My::Foo;
    use strict; use warnings; use Carp;
    
    sub new {
        my $proto = shift;
        my $self = { };
        bless($self, $ref $proto ? ref $proto : $proto);
        return $self;
    }
    
    sub bar {
        my ($self, $val) = @_;

        # first access, create it
        $self->{x} = 'meese' unless exists $self->{x};

        # set the val
        if ( defined $val ) {
            croak "must be scalar" if ref $val;
            $self->{x} = $val;
        }
        return $self->{x};            
    }
    

(and that doesn't include the type checking)

Quickly: Moose

Moose is about quick but not dirty:

Original Moose::CAP goals

Not epic failure

A simple Moose::CAP subclass

    package TestApp5;
    
    use Moose;
    extends 'Moose::CAP'; # instead of use base
    
    override 'setup' => sub {
        my $self = shift;
        
        $self->start_mode('nomode');
        $self->mode_param('rm');
        $self->run_modes(
            'basic_test1' => 'basic_test1',
            'basic_test2'		=> 'basic_test2',
            'basic_test3'		=> 'basic_test3',
        );
    };
    ############################
    ####  RUN MODE METHODS  ####
    ############################
    sub basic_test1 { return "Hello World: basic_test1"; }
    sub basic_test2 { return "Hello World: basic_test2"; }
    sub basic_test3 { return "Hello World: basic_test3"; }

Seemed like a good idea

Param Hash

    package Moose::CAP;
    use Moose;
    with 'MooseX::Param';
    

Params Hash (cont'd)

Params Hash (the code)

has 'params' => ( is => 'rw', isa => 'HashRef', lazy => 1, builder  => 'init_params', );

sub init_params { +{} }

sub param {
    my $self = shift;
    
    # if they want the list of keys ...
    return keys %{$self->params}  if scalar @_ == 0;
    
    # if they want to fetch a particular key ...    
    return $self->params->{$_[0]} if scalar @_ == 1 && !ref $_[0];

    confess "parameter assignment must be an even numbered list"
        unless ((scalar @_ % 2) == 0) || ref $_[0] eq 'HASH';

    # hashref or even numbered list-as-hash
    my %new = ref $_[0] eq 'HASH' ? (%{$_[0]}) : @_;

    while (my ($key, $value) = each %new) { $self->params->{$key} = $value; }
    
    # if we're setting exactly one param, return it
    return scalar @_ == 2 ? $_[1] : undef;
}
    

Template path

Template path (cont'd)

So, this was no good for declaring tmpl_path attribute.

    has 'tmpl_path' => (
        'is'    => 'rw',            # create accessor/mutator
        'isa'   => 'ArrayRef[Str]', # Array ref of strings 
        default => sub { [] };      # default to empty
    );
    

Template path (fixed)

How I fixed it: A subtype + coercion

use Moose::Util::TypeConstraints;

subtype 'Moose::CAP::tmpl_path'
    => as 'ArrayRef'
    => where { ref $_ eq 'ARRAY' }
    => message { "Invalid tmpl_path specified ($_)" };
 
coerce 'Moose::CAP::tmpl_path'
    => from 'Str'
        => via { [ ( defined $_ ? $_ : '') ] };

has 'tmpl_path' => (
    is          => 'rw',
    isa         => 'Moose::CAP::tmpl_path',
    default     => sub { [] },
    coerce      => 1,
);

#elswhere
$app = Moose::CAP->new();
$app->tmpl_path('/my/project/tmpl'); # this would have failed
$app->tmpl_path([ qw[/my/project/tmpl /other/project/tmpl] ]);
    

header_type

CGI::App uses the header_type method to determine weather we're sending a Content-type header (header; default), a redirect (redirect) or no header at all (none)

	use Moose::Util::TypeConstraints;
	has 'header_type' => (
		is  => 'rw',
		isa => enum([ qw/header redirect none/ ]),
		default => 'header',
	);
	

The enum function provides us with a virtual enum to restrict it's value.

Handy.

Orignal header_type

	sub header_type {
		my $c = shift;
		my ($header_type) = @_;
	
		my @allowed_header_types = qw(header redirect none);
	
		# First use?  Create new __HEADER_TYPE!
		$c->{__HEADER_TYPE} = 'header' unless (exists($c->{__HEADER_TYPE}));
	
		# If data is provided, set it!
		if (defined($header_type)) {
			$header_type = lc($header_type);
			croak("Invalid header_type '$header_type'")
				unless(grep { $_ eq $header_type } @allowed_header_types);
			$c->{__HEADER_TYPE} = $header_type;
		}
	
		# If we've gotten this far, return the value!
		return $c->{__HEADER_TYPE};
	}
	

Moose is prettier.

Why I failed 14 tests

Hardest Parts

What I can do

Thank you.

... maybe I should have just stared with a plain Moose talk.

Resources

Lookup these modules on CPAN

  • Moose
    • Moose::Util::TypeConstraints - Custom types
  • MooseX::Param - Get a $self->param for free
  • MooseX::AttributeHelpers - Extra methods for dealing with various types (the Hash and List ones are helpful)
  • MooseX::ClassAttributes - Class based attributes
  • MooseX::Types - Organize custom types into libraries