What's the point in rewriting a simple webapp framework with Moose?
What's the big deal?
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.
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)
Moose is about quick but not dirty:
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"; }
package Moose::CAP;
use Moose;
with 'MooseX::Param';
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;
}
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
);
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] ]);
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.
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.
Thank you.
... maybe I should have just stared with a plain Moose talk.
Lookup these modules on CPAN
$self->param for free