The Sysadmin Notebook  

Sitemap

Modules and Moose

Building Modules and Testing

Contents

Install Moose

Top Bottom

Start by installing Moose and Module::Starter from CPAN

user:~> PERL_MM_USE_DEFAULT=1 cpan
cpan> install Moose Module::Starter Module::Install

Module Starter

Top Bottom

Module::Starter provides a simple script to get your package development environment setup properly with a skeleton module file for your package

user:~> module-starter --mi --module=Robot \
> --author="Anon E. Moose" --email=Moose@large.com

This provides the following directories and files:

.
|-- Robot
    |-- Changes
    |-- ignore.txt
    |-- lib
    |   `-- Robot.pm
    |-- Makefile.PL
    |-- MANIFEST
    |-- README
    `-- t
        |-- 00-load.t
        |-- boilerplate.t
        |-- manifest.t
        |-- pod-coverage.t
        `-- pod.t

3 directories, 11 files

The Robot.pm file is going to be the our OO Module built with Moose. To make a Moose modules, replace 'use strict' and 'use warnings' with a single 'use Moose' declaration - Moose already requires strict and warnings for you. With the pod stripped out your module should look like this:

package Robot;

use Moose;

our $VERSION = '0.01';

1; 

Because our module uses Moose, we should add a "requires 'Moose' declaration to Makefile.pl after "build_requires". By inserting the 'use Moose' statement, we now have a functioning OO-Perl Module with a constructor that returns an object of the class:

use strict;
use warnings;

use Test::More tests => 2;

use Robot '0.01';

note ("Testing Robot Version: $Robot::VERSION");
ok(my $robot = Robot->new(), "Robot Constructor");
ok(ref($robot) eq 'Robot', "Object Type");

Moose comes with a lot of features and exports a lot of functions into your classes namespaces. To keep your class namespace clean, it is recommended that you add 'no Moose' at the end of your module (or use namespace::clean or namespace::autoclean). Another optimisation is to use Moose's immutibilisation feature. This tells Moose that the class will not be changing any further and allows Moose to optimise the module for runtime execution. The downside of this is an increased cost at compile-time

package Robot;

use Moose;

our $VERSION = '0.02';

no Moose;

__PACKAGE__->meta->make_immutable;

1; 

Attributes

Top Bottom

Attributes are added to Moose objects using the 'has' function:

package Robot;

use Date::Formatter;
use Moose;

our $VERSION = '0.03';

has 'name' 	=> ( 	is => 'rw');

has 'creator' 	=> ( 	is => 'rw',
			required => 1,
			default => 'Dr Edward Morbius');

has 'created' 	=> ( 	is => 'ro',
			required => 1,
			lazy => 1,
			builder => '_creation_date');

sub _creation_date {
	my $date = Date::Formatter->now();
	$date->createDateFormatter("(YYYY)-(MM)-(DD) (hh):(mm):(ss)");
	return $date;
}

no Moose;

__PACKAGE__->meta->make_immutable;

1; 

Here we've added three properties for our Robot objects:

name
The name attribute is declared with one option ('is) that flags the attribute as a read-write attribute, which means both accessor and mutator methods will be made available for it
creator
The creator attribute is also declared as read-write. The required option is set to a true value, so each new object must have a creator attribute. A default value has been specified should the constructor be called without a value for 'creator'
created
The created attribute is set as read-only, so no mutator method will be available for it. The required option is set to true, but the value is set by the subroutine named in the builder option ('_creation_date')

The default option can be set to string literal or a sub_routine reference. Alternatively, the builder option can be set to the name of a subroutine. The return value of the subroutine will be used to set the default value

use Test::More qw/no_plan/;
use Robot '0.03';

my $robot = Robot->new();

my @methods = qw/name creator created/;
can_ok($robot, @methods);

ok(!$robot->name, "no value set for name");
ok($robot->name('Robert'), "name mutator method");
cmp_ok($robot->name, 'eq', 'Robert', "name set by mutator");

ok($robot->creator eq 'Dr Edward Morbius', 'default set for creator');
ok($robot->creator('Morbius'), 'mutator for creator');
cmp_ok($robot->creator, 'eq', 'Morbius', 'creator set by mutator');

ok($robot->created, 'created accessor method');
like($robot->created, qr/\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}/,
	'created date format correct');

ok(my $robot = Robot->new(name => 'Roberto', creator => 'Morbius', 
	created => '1956'), 'Robot with all parameters specified');
ok($robot->name eq 'Roberto', 'name set in constructor');
ok($robot->creator eq 'Morbius', 'creator set in constructor');
ok($robot->created eq '1956', 'created set in constructor');


The default value for created is set by the 'builder' option, which names a subroutine '_creation_date'. The builder subroutine is called as a method, so that you have access to the invocant. However, if you try to access an invocant property that has not yet been set, this will cause an exception. You can set the attribute to 'lazy' so that it is not built until the reader method is called, when hopefully the attributes referenced have been set. Setting the attribute lazy will also save some process if the reader method is never called, and it is therefore worth setting calculated attributes (those that use builders or subroutine references) to lazy by default.

Other useful options for defining attributes:

reader
Default accessor name matches the attribute name. To use a different name for the accessor set the reader option to the name you desire
writer
Default mutator name matches the attribute name. To use a different name for the mutator set the writer option to the name you desire
predicate
A predicate method tells you whether a given attribute is set. Predicate methods are not provided by default, so set the predicate in the attribute definition. A predicate method return true if the attribute has been set - even if it has been set to undef or some other false value
clearer
Setting an attribute to a false value, will still produce a true value from the predicate method. The clearer method 'unsets' the attribute, and causes the predicate method to return false
lazy_build
If set to true for attr then this has the effect of setting:
  • lazy => 1
  • builder => '_build_attr'
  • clearer => 'clear_attr'
  • predicate => 'has_attr'
If the attribute name begins with an underscore, so will the generated clearer and predicate methods. However the builder will then become: _build__attr (two underscores between build and attr).
init_arg
Allows you to specify an alternate name for the attribute to be used in the constructor. Reader, writer, builder, predicates and clearers will still reflect the name of the attribute. If init_arg is set to undef, then this attribute is not accessible in the constructor.
weak_ref
If set to true, then Scalar::Util::weaken is called each time the attribute is set. Useful for objects that contain circular references
trigger
Can be set to a subroutine reference, which is called each time the attribute is set
isa
Setup typing for an attribute
package Robot;

use Date::Formatter;
use Moose;

our $VERSION = '0.04';

has 'name'	=> ( 	is => 'rw',
			init_arg => 'robot_name',
			predicate => 'has_name', 
			clearer => 'clear_name');

has 'creator' 	=> ( 	is => 'rw',
			required => 1,
			reader => 'find_godot',
			writer => 'conversion',
			default => 'Dr Edward Morbius');

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

sub _build_created {
	my $date = Date::Formatter->now();
	$date->createDateFormatter("(YYYY)-(MM)-(DD) (hh):(mm):(ss)");
	return $date;
}

no Moose;

__PACKAGE__->meta->make_immutable;

1; 
use Test::More qw/no_plan/;

use_ok( 'Robot', 0.04 );

note("Test predicate and clearer methods");
my $robot = Robot->new();
ok(!$robot->has_name, 'name not set yet');
$robot->name('Roboto');
ok($robot->has_name, 'name has been set' );
$robot->name(undef);
ok($robot->has_name, 'name set but undef');
$robot->clear_name; 
ok(!$robot->has_name, 'name unset');

note("Test lazy_build features");
my @methods = qw/_build_created clear_created has_created/;
can_ok($robot, @methods);

note("Test reader/writer methods");
my $robo_bot = Robot->new(name => 'Robert');
is($robo_bot->find_godot, 'Dr Edward Morbius', 'New accessor method');
ok($robo_bot->conversion('Dr Zachary Smith'), 'New mutator method');

note("Test init_args");
my $robotico = Robot->new(robot_name => 'Rob');
is($robotico->name, 'Rob', 'name set by init_arg alias'); 

Attributes are inherited from parent classes. To override an inheritable attribute, define the attribute in the child class by pre-pending the name with a plus sign (and enclose it in quotes). Build methods are also inheritable, but can be overridden simply by providing an alternative declaration in the subclass