package testDB;
use Moose;
use Carp;
use SQL::Library;
has 'lib' => (#FOLDBEG
is => 'rw',
isa => 'Str',
default => 'default',
trigger => \&_sql_lib_builder,
);#FOLDEND
has 'lib_dir' => (#FOLDBEG
is => 'ro',
isa => 'Str',
default => '/SQL',
);#FOLDEND
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
isa => 'Str',
);
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
handles => {
get_sql => 'retr',
get_elements => 'elements',
},
);
sub _sql_lib_builder {
my ($self, $lib) = shift();
$self->lib() or die("I am unable to get a lib.");
$lib = $self->lib unless $lib;
my $lib_dir = $self->lib_dir;
print $lib_dir."\n";
my $lib_file = $lib_dir . '/' . $lib . '.sql';
unless (-e $lib_file ) {
confess "SQL library $lib does not exist";
}
my $library = new SQL::Library { lib => $lib_file };
$self->_sql_lib($library);
}#FOLDEND
__PACKAGE__->meta->make_immutable;
my $tdb=testDB->new();
Using perl 5.8.8 and Moose 2.0205
$ perl testDB.pl
I am unable to get a lib. at testDB.pl line 35.
You've defined the _sql_lib attribute twice, once saying isa Str and once saying it handles methods (which a Str doesn't), but that's not the problem you're talking about.
The main problem is that you didn't define _sql_lib with lazy => 1. Any attribute whose builder (or default) depends on other attributes of the object must be lazy, because Moose doesn't guarantee the order in which attributes are assigned values during object construction.
# REMOVE THIS:
#has '_sql_lib' => (#FOLDBEG
# builder => '_sql_lib_builder',
# is => 'rw',
# isa => 'Str',
#);
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
lazy => 1, # ADD THIS LINE
handles => {
get_sql => 'retr',
get_elements => 'elements',
},
);
The reason that make_immutable brings out the bug is that calling it generates a different constructor for your class, which happens to initialize the attributes in a different order.
Related
I have a Moose class with some properties (x,y,z). I subclass it, and for the subclass, x is always 3. How can I specify this in subclass?
One could use BUILDARGS.
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
return $class->$orig(#_, x => 3 );
};
I used to work with Moo but it seems to be the same. You just need to declare the property in the subclass using + to override previous declaration.
package Foo;
use Moose;
has 'a' => (
is => 'rw',
isa => 'Num',
);
has 'b' => (
is => 'rw',
isa => 'Num',
);
has 'c' => (
is => 'rw',
isa => 'Num',
);
package My::Foo;
use Moose;
extends 'Foo';
has '+a' => (
default => 3,
);
Is there a way to die if there are extra parameters in a constructor call in Moose that are not attributes? For example, this:
package Shoe;
use Moose;
has 'size' => (is => 'ro', isa => 'Num');
has 'color' => (is => 'ro', isa => 'Str', default => 'brown');
1;
would die on
my $sneaker = Shoe->new(size => 11, colour => 'white');
because colour is not an attribute of Shoe.
I could swear I've seen a module or something to do this but I can't find it.
For me works MooseX::StrictConstructor:
package Shoe;
use Moose;
use MooseX::StrictConstructor; # <-- that's all what need
has 'size' => (is => 'ro', isa => 'Num');
has 'color' => (is => 'ro', isa => 'Str');
1;
package main;
my $sneaker = Shoe->new(size => 11, colour => 'white'); #blows up
Is there some way in Moose to specify that I want an attribute to have a specific type, but also allow there to be a null value (undef?).
For example I am writing a simple implementation of a Linked List and have a Node class where the next and prev pointers are required to be of type Node (this is probably what you would expect)
package Node;
{
use Moose;
has 'value' => (
is => 'rw',
isa => 'Any', # Nodes can be of any type
);
has 'prev' => (
is => 'rw',
isa => 'Node',
predicate => 'has_prev',
);
has 'next' => (
is => 'rw',
isa => 'Node',
predicate => 'has_next',
);
}
But I was hoping to use a sentinel, empty node stored at the head of the list to mark the head, instead of an actual element of the list. So a list of elements [1, 2, 3] would actually look like:
EMPTY -> 1 -> 2 -> 3
I was hoping to be able to specify a empty value (like undef) for the next and prev pointers, but when I create an empty Node in my List class:
package List;
{
use Moose;
has 'head' => (
is => 'rw',
isa => 'Node',
# empty head node
default => sub {
Node->new( value => undef, next => undef, prev => undef );
},
);
Moose complains because undef is not of type Node.
Is there a way around this ?
You can use the Maybe[type] syntax to allow the type or undef. For your example:
has 'head' => (
is => 'rw',
isa => 'Maybe[Node]',
# empty head node
default => sub {
Node->new( value => undef, next => undef, prev => undef );
}
);
The next:
use 5.014;
use warnings;
package Node {
use Moose;
has 'value' => ( is => 'rw');
has 'prev' => ( is => 'rw', isa => 'Undef|Node', predicate => 'has_prev', default=>undef );
has 'next' => ( is => 'rw', isa => 'Undef|Node', predicate => 'has_next', default=>undef );
}
package List {
use Moose;
has 'head' => ( is => 'rw', isa => 'Node', default => sub { Node->new() } );
}
package main;
use Data::Dumper;
my $list = List->new();
say Dumper $list;
prints:
$VAR1 = bless( {
'head' => bless( {
'next' => undef,
'prev' => undef
}, 'Node' )
}, 'List' );
The Moose::Manual::Types says for the basic hier:
Undef <---- undefined
Defined
Value
Str
Num
Int
ClassName <---- Class name
RoleName
and later in the section TYPE UNIONS says:
Moose allows you to say that an attribute can be of two or more
disparate types. For example, we might allow an Object or FileHandle:
has 'output' => (
is => 'rw',
isa => 'Object | FileHandle', );
As others already says, here is a Maybe[Something] too, I haven't idea what is better, but the Something | SomethingOther looks more "perlish" (IMHO). ;)
The authors prefer Undef|Node over Maybe[Node].
has 'prev' => (
is => 'rw',
isa => 'Undef|Node',
predicate => 'has_prev',
);
In trying to answer How to instantiate Moose classes from a big hash, I think I have hit another place where I don't fully understand Moose type coercions. For some reason, the below code issues warnings:
You cannot coerce an attribute (departments) unless its type (ArrayRef[Company::Department]) has a coercion at ./test.pl line 12.
You cannot coerce an attribute (employees) unless its type (ArrayRef[Company::Person]) has a coercion at ./test.pl line 23.
but then succeeds.
#!/usr/bin/env perl
use warnings;
use strict;
package Company;
use Moose;
use Moose::Util::TypeConstraints;
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRef[Company::Department]', coerce => 1);
coerce 'ArrayRef[Company::Department]',
from 'ArrayRef[HashRef]',
via { [ map { Company::Department->new($_) } #$_ ] };
package Company::Department;
use Moose;
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRef[Company::Person]', coerce => 1);
package Company::Person;
use Moose;
use Moose::Util::TypeConstraints;
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'age' => (is => 'ro', isa => 'Num');
coerce 'ArrayRef[Company::Person]',
from 'ArrayRef[HashRef]',
via { [ map { Company::Person->new($_) } #$_ ] };
package main;
my %hash = (
company => {
id => 1,
name => 'CorpInc',
departments => [
{
id => 1,
name => 'Sales',
employees => [
{
id => 1,
name => 'John Smith',
age => '30',
},
],
},
{
id => 2,
name => 'IT',
employees => [
{
id => 2,
name => 'Lucy Jones',
age => '28',
},
{
id => 3,
name => 'Miguel Cerveza',
age => '25',
},
],
},
],
}
);
my $company = Company->new($hash{company});
use Data::Dumper;
print Dumper $company;
How should this have been done? P.S. I tried simply doing
coerce 'Company::Department',
from 'HashRef',
via { Company::Department->new($_) };
but it died horribly.
Well, it doesn't succeed completely, and you should feel it when you'll try to update these fields with coerce => 1. That's why:
You cannot pass coerce => 1 unless the attribute's type constraint has
a coercion
Previously, this was accepted, and it sort of worked,
except that if you attempted to set the attribute after the object was
created, you would get a runtime error.
Now you will get an error when you attempt to define the attribute.
Still, I think I find the way to fix it, by introducing subtypes, first, and changing the order of packages, second:
package Company::Person;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'ArrayRefCompanyPersons',
as 'ArrayRef[Company::Person]';
coerce 'ArrayRefCompanyPersons',
from 'ArrayRef[HashRef]',
via { [ map { Company::Person->new($_) } #$_ ] };
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'age' => (is => 'ro', isa => 'Num');
package Company::Department;
use Moose;
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRefCompanyPersons', coerce => 1);
package Company;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'ArrayRefCompanyDepartments',
as 'ArrayRef[Company::Department]';
coerce 'ArrayRefCompanyDepartments',
from 'ArrayRef[HashRef]',
via { [ map { Company::Department->new($_) } #$_ ] };
has 'id' => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRefCompanyDepartments', coerce => 1);
The rest of the code is the same as in your version. This works without any warnings, and more-o-less behaves like (again, I think) it should be.
From Moose::Manual::Type docs:
LOAD ORDER ISSUES
Because Moose types are defined at runtime, you may run into load order problems. In particular, you may want to use a class's type constraint before that type has been defined.
In order to ameliorate this problem, we recommend defining all of your custom types in one module, MyApp::Types, and then loading this module in all of your other modules.
So to add to raina77ow subtype & package order answer (+1) I would recommend creating a Company::Types module:
package Company::Types;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'CompanyDepartments'
=> as 'ArrayRef[Company::Department]';
subtype 'CompanyPersons'
=> as 'ArrayRef[Company::Person]';
coerce 'CompanyDepartments'
=> from 'ArrayRef[HashRef]'
=> via {
require Company::Department;
[ map { Company::Department->new($_) } #$_ ];
};
coerce 'CompanyPersons'
=> from 'ArrayRef[HashRef]'
=> via { require Company::Person; [ map { Company::Person->new($_) } #$_ ] };
1;
And then put use Company::Types in all your Company:: classes.
Suppose I want a class like this:
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
);
has chain => (
is => 'ro',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
);
has chain_class => (
is => 'ro',
isa => 'Str',
);
1;
Now I want to instantiate the class like:
my $r = Restraint->new(
ball_class = 'Ball',
chain_class = 'Chain',
);
both arguments being mandatory.
I also want to instantiate both ball_class and chain_class during construction and assign them to corresponding attributes so that finally I can e.g. $r->ball->lift or $r->chain->lock etc.
How should I do that?
Just mark them both "required" - see Moose::Manual::Attributes
That's not going to do anything for "ball" or "chain" though. There's no connection.
You could set up ball/chain with lazy builders which would reference your classnames (see the Moose Manual again).
I'd probably just make the objects required and pass them in directly though:
my $r = Restraint->new(
ball => CannonBall->new(...),
chain => IronChain->new(...)
);
OK - you don't want to pass in objects. In that case you want lazy builders. Mark the ball as lazy and give it a builder and Moose will call the builder when the ball is first used.
http://metacpan.org/pod/Moose
http://metacpan.org/pod/Moose::Manual::Attributes
http://metacpan.org/pod/Moose::Cookbook::Basics::Recipe3
Do take the time to read the moose documentation - there's a lot of it, but it's quite well written and covers plenty of features. Lots of examples.
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
lazy => 1,
builder => '_build_ball'
);
has ball_class => (
is => 'ro',
required => 1
);
sub _build_ball {
my $self = shift;
my $ball_class = $self->ball_class;
return $ball_class->new();
}
Using BUILD for instantiation while setting required only for *_class attributes seems to work to some extent, except that:
I had to make ball and chain read-write
Code:
package Restraint;
use Moose;
has ball => (
is => 'rw',
does => 'Heavy',
);
has chain => (
is => 'rw',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
has chain_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
sub BUILD {
my $self = shift;
my $ball = $self->ball_class->new();
$self->ball( $ball );
my $chain = $self->chain_class->new();
$self->chain( $chain );
}
1;
It's not a big sacrifice for now, but I still wonder if there is more correct way, though.