MooseX::Types coercions and $self - perl

Is there anyway to get $self into a MooseX::Types coercion? I have other data in the object that I want to use to seed my coercion from a String to an Object. Alternatively, is there anything like Class::MOP's initializer that will permit me to do this -- it would have to fire before the type checks.
Requested pseudo code:
with 'DBHandle';
has 'database' => ( isa => 'Str', is => 'ro', default => 'Db' );
has 'schema' => ( isa => 'Str', is => 'ro', default => 'schema' );
has 'table' => ( isa => 'Str', is => 'ro', default => 'column );
has 'columns' => ( isa => DBCols, is => 'ro', default => sub {[qw/foo bar baz/]} );
Here, I want "columns" to coerce to a DBCols -- an ArrayRef of DBCol's (objects) -- requiring the use of catalog, schema, and col attributes found in the class, and with a dbh/singleton provided by DBHandle.
To make this less-pseudo, the actually situation is only slightly more complex. I was able to accomplish the above with around, now what I want I to do is create an attribute trait that would permit this syntax:
has 'column_id' => (
isa => Int
, is => 'ro'
, traits => ['DBKey']
, default => 5
, column => 'foo'
);
Where the attribute trait column provided by DBKey, coerces to DBCol the same way that the above columns would: this would require the ability to access the classes database, schema, table, and again the singleton for the dbh.

No. It'd be nice, but coercions are really designed to be global, and no one has written a "context-sensitive coercion" yet, because no one's really sure how to. (Actually, s/coercions/type constraints/ -- it'd be useful just to say "this Str must be a valid column name, defined as an entry in this object's columns HashRef".)
People usually solve this problem with around and/or some combination of BUILD and BUILDARGS.

Related

perl Mouse set value before object returned to user

I'm trying to write a perl module using Mouse, and after the object has been initialized, but before the user makes any calls, I need to initialize object1 with the two values from object2 and object3 that the user was required to give. I tried to use Mouse's after feature to have a subroutine called after new:
package Test;
use strict;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
after 'new' => sub {
my ($self) = #_;
$self->object1(#do stuff with object2 and object3);
};
1;
However, currently I get this error:
Invalid object instance: 'Test' at lib/Test.pm line 18.
Is there a way I can initialize a value with user supplied values before the user gets the object reference returned to them?
Mouse is compatible with Moose. Object creation has the following phases:
Arguments are passed through the BUILDARGS method if one is defined. This can munge the arguments before Moose/Mouse touch them, e.g. to provide default arguments or to accommodate other calling styles than the keyword convention.
A new instance is created and the fields (declared by has) are populated.
The object is now set up and ready to use (from Mouse's/Moose's perspective). You might have a different idea of a correct setup, and can perform your checks in a BUILD method. This is the point where you can also perform remaining initialization that can't be expressed with has declarations.
So your example might become:
use strict;
use warnings;
package Your::Class;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
sub BUILD {
my ($self) = #_;
$self->object1($self->object2 . $self->object3);
};
package main;
use Test::More;
# Your::Class->new(object2 => "foo", object3 => "bar");
my $instance = new_ok('Your::Class', [object2 => "foo", object3 => "bar"]);
is($instance->object1, "foobar");
done_testing;
To learn more about object construction in Moose and Moose-compatible object systems, read Moose::Manual::Construction.

Lazy Attribute Coercion

With Moose, you can have lazy builders on attributes, where the builder is called when the attribute is first accessed if the attribute was not already populated. You can have type coercion of an attribute with coerce, but this is applied whenever the attribute is set, so even on object initialization.
I'm looking for a way to implement lazy coercion, where an attribute may be initially populated, but is only coerced when it is first accessed. This is important when coercion is expensive.
In the following example, I use a union type and method modifiers to do this:
package My::Foo;
use Moose;
has x => (
is => 'rw',
isa => 'ArrayRef | Int',
required => 1
);
around "x" => sub {
my $orig = shift;
my $self = shift;
my $val = $self->$orig(#_);
unless(ref($val)) {
# Do the cocerion
$val = [ map { 1 } 1..$val ];
sleep(1); # in my case this is expensive
}
return $val;
};
1;
my $foo = My::Foo->new( x => 4 );
is_deeply $foo->x, [ 1, 1, 1, 1 ], "x converted from int to array at call time";
However there are a few problems with this:
I dislike the union type + method modifier approach. It goes against the "Best Practices" suggestion to use coercion instead of unions. It isn't declarative.
I need to do this with many attributes across many classes. Therefore some form of DRY is needed. This could be meta-attribute roles, type-coercion, what have you.
Update:
I followed ikegami's suggestion to encapsulate the expensive type coercion inside an object and provide an outer coercion to this object:
package My::ArrayFromInt;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'My::ArrayFromInt::Inner',
as 'ArrayRef[Int]';
coerce 'My::ArrayFromInt::Inner',
from 'Int',
via { return [ (1) x $_ ] };
has uncoerced => (is => 'rw', isa => 'Any', required => 1);
has value => (
is => 'rw',
isa => 'My::ArrayFromInt::Inner',
builder => '_buildValue',
lazy => 1,
coerce => 1
);
sub _buildValue {
my ($self) = #_;
return $self->uncoerced;
}
1;
package My::Foo;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'My::ArrayFromInt::Lazy' => as class_type('My::ArrayFromInt');
coerce 'My::ArrayFromInt::Lazy',
from 'Int',
via { My::ArrayFromInt->new( uncoerced => $_ ) };
has x => (
is => 'rw',
isa => 'My::ArrayFromInt::Lazy',
required => 1,
coerce => 1
);
1;
This works if $foo->x->value is called. However this doesn't solve point #2, as I would need to create My::ArrayFromInt and the ::Lazy subtype for each attribute I would like to transform. And I'd like to avoid calling $foo->x->value if possible.
How about having the typedef along the lines described, then doing
has _x => (
is => 'ro',
isa => 'Int|MyArrayOfInts',
init_arg => 'x',
required => 1,
);
has x => (
is => 'ro',
lazy => 1,
isa => 'MyArrayOfInts',
coerce => 1,
default => sub { $_[0]->_x },
);
It'd make sense to wrap that up into some kind of helper method to create the pair of objects along the lines of
has_lazily_coerced x => (
is => 'ro',
isa => 'TargetType',
);
which would introspect on TargetType to get a list of legal types for the uncoerced shadow attribute and generate the pair of attributes for you.

In Moose, how do you declare predicate and clearer methods when defining multiple attributes?

In Moose you can declare a group of attributes at once, assuming the initialization parameters are the same:
has [qw( foo bar baz )] => (
is => 'ro',
isa => 'Str',
required => 1,
);
This is a lovely feature that saves a ton of typing. However, I find myself puzzled about how define a predicate, clearer or even a builder method using this syntax.
has 'foo' => (
is => 'ro',
isa => 'Str',
required => 1,
clearer => 'clear_foo',
predicate => 'has_foo',
);
Is there a parameter I can use that will build standard 'has_X, 'clear_X and _build_X methods for all the attributes in my list?
has $_ => (
is => 'ro',
isa => 'Str',
required => 1,
clearer => '_clear_' . $_,
# etc...
) for (qw(foo bar baz);
Note that lazy_build => 1 will automatically generate clearers, and
predicates, but they will always be public, which is starting to be frowned
upon in the Moose community. (I don't think anyone's blogged about this yet,
but it's been a topic of conversation on IRC #moose of late.)
lazy_build => 1,
That also marks them lazy, but that's recommended for attributes with a builder.

How can I create a structure after a Moose object was initialized?

I'm using Moose to write an object module.
I currently have a few mandatory fields:
has ['length'] => (
is => 'ro',
isa => 'Int',
required => 1,
);
has ['is_verified'] => (
is => 'ro',
isa => 'Bool',
required => 1,
);
has ['url'] => (
is => 'ro',
isa => 'Str',
required => 1,
);
After the object was initialized with those fields, I would like to create some structure and use it from the object methods.
how (where) should I do that?
There are (at least) two possibilities:
You can create a BUILD sub. It gets called automatically after the object is initialized.
You create a normal attribute and mark it lazy. Then you provide a sub that creates this attribute: either builder or default. You can read more about this in the manual.

Moose read-only Attribute Traits and how to set them?

How do I set a Moose read only attribute trait?
package AttrTrait;
use Moose::Role;
has 'ext' => ( isa => 'Str', is => 'ro' );
package Class;
has 'foo' => ( isa => 'Str', is => 'ro', traits => [qw/AttrTrait/] );
package main;
my $c = Class->new( foo => 'ok' );
$c->meta->get_attribute('foo')->ext('die') # ro attr trait
What is the purpose of Read Only attribute traits if you can't set it in the constructor or in runtime? Is there something I'm missing in Moose::Meta::Attribute? Is there a way to set it using meta?
$c->meta->get_attr('ext')->set_value('foo') # doesn't work either (attribute trait provided not class provided method)
You can set it in the constructor:
package Class;
has 'foo' => ( isa => 'Str', is => 'ro', ext => 'whatever', traits => ['AttrTrait'] );
You just need to pass it to the right constructor (the constructor for the attribute).
I use default to deal with ro attributes:
package Foo;
use Moose;
has 'myattr' => (is => 'ro', default => 'my value goes here');
And since you won't be setting myattr's value anywhere else, the default is used.