Moose Role Derivation - perl

I would like to now what is the better pattern to do what I need. I try to reduce the problem to a minimum, let me explain it step by step.
I have an interface Role like:
{
package Likeable;
use Moose::Role;
requires 'likers';
requires 'do_like';
}
After this, I need 2 Abstract Roles that semi-implement the previous interface (in this case they implement all):
{
package Likeable::OnSelf;
use Moose::Role;
with 'Likeable';
has 'likers' => ( is => 'rw', isa => 'ArrayRef' );
sub do_like { }
}
{
package Likeable::OnParent;
use Moose::Role;
with 'Likeable';
requires 'parent';
sub likers { shift->parent->likers(#_) }
sub do_like { shift->parent->do_like(#_) }
}
and later I need this code to compile
{
package OBJ::OnSelf;
use Moose;
with 'Likeable::OnSelf';
}
{
package OBJ::OnParent;
use Moose;
with 'Likeable::OnParent';
has 'parent' => ( is => 'rw', isa => 'Obj' );
}
foreach my $obj (OBJ::OnSelf->new, OBJ::OnParent->new(parent => OBJ::OnSelf->new)) {
if ( $obj->does('Likeable') ) {
$obj->do_like
}
}
The problem seems to me that is that I'm trying to do derivation on the Moose::Role, but I have no ideia how to solve the problem correctly.
May I have your suggestions?

There's no problem really with your overall role composition, but I assume you are getting an error like this:
'Likeable::OnParent' requires the method 'parent' to be implemented by 'OBJ::OnParent' at .../Moose/Meta/Role/Application.pm line 51
The problem is that has is called to create the attribute accesssor method after with is called to check for the method. (These are just subroutines being called, not actual language constructs.)
There are couple good solutions I know of. I prefer this one:
package OBJ::OnParent;
use Moose;
has 'parent' => ( is => 'rw', isa => 'Obj' );
with 'Likeable::OnParent';
Do your with statement after the attribute(s) are defined. The other option I know of is this:
package OBJ::OnParent;
use Moose;
with 'Likeable::OnParent';
BEGIN {
has 'parent' => ( is => 'rw', isa => 'Obj' );
}
By placing your has calls in a BEGIN block, the attributes are added to the package just after use Moose is run and before with. I don't like sticking in BEGIN blocks like this, but that's mostly a personal preference.
In this particular case, though, I might suggest just changing Likeable::OnParent to such that you better specify that the parent method returns a Likeable, which will also bypass the need to change your object definitions:
package Likeable::OnParent;
use Moose::Role;
with 'Likeable';
has parent => (
is => 'rw',
does => 'Likeable',
required => 1,
);
sub likers { shift->parent->likers(#_) }
sub do_like { shift->parent->do_like(#_) }
This way you have confidence that your calls to likers and do_like will succeed because the attribute must be set and it must implement the role that requires those methods and the documented contract.

Related

Is it possible to make an attribute configuration dependent on another attribute in Moo?

I have read various tutorials and the Moo documentation but I cannot find anything that describes what I want to do.
What I want to do is something like the following:
has 'status' => (
is => 'ro',
isa => Enum[qw(pending waiting completed)],
);
has 'someother' => (
is => is_status() eq 'waiting' ? 'ro' : 'rw',
required => is_status() eq 'completed' ? 1 : 0,
isa => Str,
lazy => 1,
);
If I'm just way off base with this idea, how would I go about making an attribute 'ro' or 'rw' and required or not, depending on the value of another attribute?
Note, the Enum is from Type::Tiny.
Ask yourself why you want to do this. You are dealing with objects. Those are data that has a set of logic applied to them. That logic is described in the class, and the object is an instance of data that has the class's logic applied.
If there is a property (which is data) that can have two different logics applied to it, is it still of the same class? After all, whether a property is changeable is a very distinct rule.
So you really have two different classes. One where the someother property is read-only, and one where it is changeable.
In Moo (and Moose) there are several ways to build that.
implement Foo::Static and Foo::Dynamic (or Changeable or Whatever) where both are subclasses of Foo and only the one property changes
implement Foo and implement a subclass
implement Foo and a role that changes the behaviour of someother, and apply it in the constructor. Moo::Role inherits that from Role::Tiny.
Here is an example of the approach that uses roles.
package Foo;
use Moo;
use Role::Tiny ();
has 'status' => ( is => 'ro', );
has 'someother' => (
is => 'ro',
lazy => 1,
);
sub BUILD {
my ( $self) = #_;
Role::Tiny->apply_roles_to_object($self, 'Foo::Role::Someother::Dynamic')
if $self->status eq 'foo';
}
package Foo::Role::Someother::Dynamic;
use Moo::Role;
has '+someother' => ( is => 'rw', required => 1 );
package main;
use strict;
use warnings;
use Data::Printer;
# ...
First we'll create an object that has a dynamic someother.
my $foo = Foo->new( status => 'foo', someother => 'foo' );
p $foo;
$foo->someother('asdf');
print $foo->someother;
__END__
Foo__WITH__Foo::Role::Someother::Dynamic {
Parents Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic, Foo
Linear #ISA Foo__WITH__Foo::Role::Someother::Dynamic, Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic, Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic::_BASE, Foo, Moo::Object
public methods (0)
private methods (0)
internals: {
someother "foo",
status "foo"
}
}
asdf
As you can see, that works. Now let's make a static one.
my $bar = Foo->new( status => 'bar', someother => 'bar' );
p $bar;
$bar->someother('asdf');
__END__
Foo {
Parents Moo::Object
public methods (4) : BUILD, new, someother, status
private methods (0)
internals: {
someother "bar",
status "bar"
}
}
Usage: Foo::someother(self) at /home/julien/code/scratch.pl line 327.
Ooops. A warning. Not a nice 'read-only' exception like in Moose, but I guess this is as good as it gets.
However, this will not help with the required attribute. You can create a Foo->new( status => 'foo' ) without someother and it will still come out ok.
So you might want to settle for the subclass approach or use a role and build a factory class.

perl moose triggers in subclasses disrupt method modifiers

I've found that if a subclass adds a trigger, then method modifiers from the base class don't run. This seems like a Moose bug, or at least non-intuitive. Here's my example:
package Foo {
use Moose;
has 'foo' => (
is => 'rw',
isa => 'Str',
);
before 'foo' => sub {
warn "before foo";
};
};
package FooChild {
use Moose;
extends 'Foo';
has '+foo' => ( trigger => \&my_trigger, );
sub my_trigger {
warn 'this is my_trigger';
}
};
my $fc = FooChild->new();
$fc->foo(10);
If you run this example, only the "this is my_trigger" warn runs, and the "before" modifier is ignored. I'm using Perl 5.14.2 with Moose 2.0402.
Is this correct behavior? It doesn't seem right, especially since the trigger will fire after the before when the trigger is defined directly in the base class.
On the principle that you should not be able to distinguish between inherited code and code in the class, I'd call this a bug.
It appears to be a general problem where adding to an attribute removes method modifiers. This code demonstrates your bug without involving triggers.
package Foo {
use Moose;
has 'foo' => (
is => 'rw',
isa => 'Str',
default => 5,
);
before 'foo' => sub {
warn "before foo";
};
};
package FooChild {
use Moose;
extends 'Foo';
has '+foo' => ( default => 99 );
};
my $fc = FooChild->new();
print $fc->foo;
Please report this to the Moose folks.

Use a single module and get Moose plus several MooseX extensions

Let's say I have a codebase with a bunch of Moose-based classes and I want them all to use a common set of MooseX::* extension modules. But I don't want each Moose-based class to have to start like this:
package My::Class;
use Moose;
use MooseX::Aliases;
use MooseX::HasDefaults::RO;
use MooseX::StrictConstructor;
...
Instead, I want each class to begin like this:
package MyClass;
use My::Moose;
and have it be exactly equivalent to the above.
My first attempt at implementing this was based on the approach used by Mason::Moose (source):
package My::Moose;
use Moose;
use Moose::Exporter;
use MooseX::Aliases();
use MooseX::StrictConstructor();
use MooseX::HasDefaults::RO();
use Moose::Util::MetaRole;
Moose::Exporter->setup_import_methods(also => [ 'Moose' ]);
sub init_meta {
my $class = shift;
my %params = #_;
my $for_class = $params{for_class};
Moose->init_meta(#_);
MooseX::Aliases->init_meta(#_);
MooseX::StrictConstructor->init_meta(#_);
MooseX::HasDefaults::RO->init_meta(#_);
return $for_class->meta();
}
But this approach is not recommended by the folks in the #moose IRC channel on irc.perl.org, and it doesn't always work, depending on the mix of MooseX::* modules. For example, trying to use the My::Moose class above to make My::Class like this:
package My::Class;
use My::Moose;
has foo => (isa => 'Str');
Results in the following error when the class is loaded:
Attribute (foo) of class My::Class has no associated methods (did you mean to provide an "is" argument?)
at /usr/local/lib/perl5/site_perl/5.12.1/darwin-2level/Moose/Meta/Attribute.pm line 1020.
Moose::Meta::Attribute::_check_associated_methods('Moose::Meta::Class::__ANON__::SERIAL::2=HASH(0x100bd6f00)') called at /usr/local/lib/perl5/site_perl/5.12.1/darwin-2level/Moose/Meta/Class.pm line 573
Moose::Meta::Class::add_attribute('Moose::Meta::Class::__ANON__::SERIAL::1=HASH(0x100be2f10)', 'foo', 'isa', 'Str', 'definition_context', 'HASH(0x100bd2eb8)') called at /usr/local/lib/perl5/site_perl/5.12.1/darwin-2level/Moose.pm line 79
Moose::has('Moose::Meta::Class::__ANON__::SERIAL::1=HASH(0x100be2f10)', 'foo', 'isa', 'Str') called at /usr/local/lib/perl5/site_perl/5.12.1/darwin-2level/Moose/Exporter.pm line 370
Moose::has('foo', 'isa', 'Str') called at lib/My/Class.pm line 5
require My/Class.pm called at t.pl line 1
main::BEGIN() called at lib/My/Class.pm line 0
eval {...} called at lib/My/Class.pm line 0
The MooseX::HasDefaults::RO should be preventing this error, but it's apparently not being called upon to do its job. Commenting out the MooseX::Aliases->init_meta(#_); line "fixes" the problem, but a) that's one of the modules I want to use, and b) that just further emphasizes the wrongness of this solution. (In particular, init_meta() should only be called once.)
So, I'm open to suggestions, totally ignoring my failed attempt to implement this. Any strategy is welcome as long as if gives the results described at the start of this question.
Based on #Ether's answer, I now have the following (which also doesn't work):
package My::Moose;
use Moose();
use Moose::Exporter;
use MooseX::Aliases();
use MooseX::StrictConstructor();
use MooseX::HasDefaults::RO();
my %class_metaroles = (
class => [
'MooseX::StrictConstructor::Trait::Class',
],
attribute => [
'MooseX::Aliases::Meta::Trait::Attribute',
'MooseX::HasDefaults::Meta::IsRO',
],
);
my %role_metaroles = (
role =>
[ 'MooseX::Aliases::Meta::Trait::Role' ],
application_to_class =>
[ 'MooseX::Aliases::Meta::Trait::Role::ApplicationToClass' ],
application_to_role =>
[ 'MooseX::Aliases::Meta::Trait::Role::ApplicationToRole' ],
);
if (Moose->VERSION >= 1.9900) {
push(#{$class_metaroles{class}},
'MooseX::Aliases::Meta::Trait::Class');
push(#{$role_metaroles{applied_attribute}},
'MooseX::Aliases::Meta::Trait::Attribute',
'MooseX::HasDefaults::Meta::IsRO');
}
else {
push(#{$class_metaroles{constructor}},
'MooseX::StrictConstructor::Trait::Method::Constructor',
'MooseX::Aliases::Meta::Trait::Constructor');
}
*alias = \&MooseX::Aliases::alias;
Moose::Exporter->setup_import_methods(
also => [ 'Moose' ],
with_meta => ['alias'],
class_metaroles => \%class_metaroles,
role_metaroles => \%role_metaroles,
);
With a sample class like this:
package My::Class;
use My::Moose;
has foo => (isa => 'Str');
I get this error:
Attribute (foo) of class My::Class has no associated methods (did you mean to provide an "is" argument?) at ...
With a sample class like this:
package My::Class;
use My::Moose;
has foo => (isa => 'Str', alias => 'bar');
I get this error:
Found unknown argument(s) passed to 'foo' attribute constructor in 'Moose::Meta::Attribute': alias at ...
I might get raked over the coals for this, but when in doubt, lie :)
package MyMoose;
use strict;
use warnings;
use Carp 'confess';
sub import {
my $caller = caller;
eval <<"END" or confess("Loading MyMoose failed: $#");
package $caller;
use Moose;
use MooseX::StrictConstructor;
use MooseX::FollowPBP;
1;
END
}
1;
By doing that, you're evaling the use statements into the calling package. In other words, you're lying to them about what class they are used in.
And here you declare your person:
package MyPerson;
use MyMoose;
has first_name => ( is => 'ro', required => 1 );
has last_name => ( is => 'rw', required => 1 );
1;
And tests!
use lib 'lib';
use MyPerson;
use Test::Most;
throws_ok { MyPerson->new( first_name => 'Bob' ) }
qr/\QAttribute (last_name) is required/,
'Required attributes should be required';
throws_ok {
MyPerson->new(
first_name => 'Billy',
last_name => 'Bob',
what => '?',
);
}
qr/\Qunknown attribute(s) init_arg passed to the constructor: what/,
'... and unknown keys should throw an error';
my $person;
lives_ok { $person = MyPerson->new( first_name => 'Billy', last_name => 'Bob' ) }
'Calling the constructor with valid arguments should succeed';
isa_ok $person, 'MyPerson';
can_ok $person, qw/get_first_name get_last_name set_last_name/;
ok !$person->can("set_first_name"),
'... but we should not be able to set the first name';
done_testing;
And the test results:
ok 1 - Required attributes should be required
ok 2 - ... and unknown keys should throw an error
ok 3 - Calling the constructor with valid arguments should succeed
ok 4 - The object isa MyPerson
ok 5 - MyPerson->can(...)
ok 6 - ... but we should not be able to set the first name
1..6
Let's keep this our little secret, shall we? :)
As discussed, you shouldn't be calling other extensions' init_meta methods directly. Instead, you should essentially inline those extensions' init_meta methods: combine what all those methods do, into your own init_meta. This is fragile because now you are tying your module to other modules' innards, which are subject to change at any time.
e.g. to combine MooseX::HasDefaults::IsRO, MooseX::StrictConstructor and MooseX::Aliases, you'd do something like this (warning: untested) (now tested!):
package Mooseish;
use Moose ();
use Moose::Exporter;
use MooseX::StrictConstructor ();
use MooseX::Aliases ();
my %class_metaroles = (
class => ['MooseX::StrictConstructor::Trait::Class'],
attribute => [
'MooseX::Aliases::Meta::Trait::Attribute',
'MooseX::HasDefaults::Meta::IsRO',
],
);
my %role_metaroles = (
role =>
['MooseX::Aliases::Meta::Trait::Role'],
application_to_class =>
['MooseX::Aliases::Meta::Trait::Role::ApplicationToClass'],
application_to_role =>
['MooseX::Aliases::Meta::Trait::Role::ApplicationToRole'],
);
if (Moose->VERSION >= 1.9900) {
push #{$class_metaroles{class}}, 'MooseX::Aliases::Meta::Trait::Class';
push #{$role_metaroles{applied_attribute}}, 'MooseX::Aliases::Meta::Trait::Attribute';
}
else {
push #{$class_metaroles{constructor}},
'MooseX::StrictConstructor::Trait::Method::Constructor',
'MooseX::Aliases::Meta::Trait::Constructor';
}
*alias = \&MooseX::Aliases::alias;
Moose::Exporter->setup_import_methods(
also => ['Moose'],
with_meta => ['alias'],
class_metaroles => \%class_metaroles,
role_metaroles => \%role_metaroles,
);
1;
This can be tested with this class and tests:
package MyObject;
use Mooseish;
sub foo { 1 }
has this => (
isa => 'Str',
alias => 'that',
);
1;
use strict;
use warnings;
use MyObject;
use Test::More;
use Test::Fatal;
like(
exception { MyObject->new(does_not_exist => 1) },
qr/unknown attribute.*does_not_exist/,
'strict constructor behaviour is present',
);
can_ok('MyObject', qw(alias this that has with foo));
my $obj = MyObject->new(this => 'thing');
is($obj->that, 'thing', 'can access attribute by its aliased name');
like(
exception { $obj->this('new value') },
qr/Cannot assign a value to a read-only accessor/,
'attribute defaults to read-only',
);
done_testing;
Which prints:
ok 1 - strict constructor behaviour is present
ok 2 - MyObject->can(...)
ok 3 - can access attribute by its aliased name
ok 4 - attribute defaults to read-only
1..4
So long as the MooseX you want to use are all well-behaved and use Moose::Exporter, you can use Moose::Exporter to create a package that will behave like Moose for you:
package MyMoose;
use strict;
use warnings;
use Moose::Exporter;
use MooseX::One ();
use MooseX::Two ();
Moose::Exporter->setup_import_methods(
also => [ qw{ Moose MooseX::One MooseX::Two } ],
);
1;
Note that in also we're using the name of the package that the Moose extension using Moose::Exporter (generally the main package from the extension), and NOT using any of the trait application bits. Moose::Exporter handles that all behind the scenes.
The advantage here? Everything works as expected, all sugar from Moose and extensions is installed and can be removed via 'no MyMoose;'.
I should point out here that some extensions do not play well with others, usually due to their not anticipating that they'll be required to coexist in harmony with others. Luckily, these are becoming increasingly uncommon.
For a larger scale example, check out Reindeer on the CPAN, which collects several extensions and integrates them together in a coherent, consistent fashion.

Dependency injection for Moose classes

I have a Moose class that needs to send requests of type Foo::Request. I need to make this dependency accessible from the outside, so that I can easily exchange the request implementation in tests. I came up with the following attribute:
has request_builder => (
is => 'rw',
isa => 'CodeRef',
default => sub {
sub { Foo::Request->new(#_) }
}
);
And then in code:
my $self = shift;
my $request = $self->request_builder->(path => …);
And in tests:
my $tested_class = …;
my $request = Test::MockObject->new;
$request->mock(…);
$tested_class->request_builder(sub { $request });
Is there a more simple / more idiomatic solution?
How about applying a role dynamically in your tests with Moose::Util::apply_all_roles? I have been wanting to use this for a while, but haven't had an excuse yet. Here is how I think it would work.
First, modify your original attribute slightly:
package MyClientThing;
has request => (
is => 'rw',
isa => 'Foo::Request',
builder => '_build_request',
);
sub _build_request { Foo::Request->new };
....
Then create a Test::RequestBuilder role:
package Test::RequestBuilder;
use Moose::Role;
use Test::Foo::Request; # this module could inherit from Foo::Request I guess?
sub _build_request { return Test::Foo::Request->new };
Meanwhile in 't/my_client_thing.t' you would write something like this:
use MyClientThing;
use Moose::Util qw( apply_all_roles );
use Test::More;
my $client = MyClientThing->new;
apply_all_roles( $client, 'Test::RequestBuilder' );
isa_ok $client->request, 'Test::Foo::Request';
See Moose::Manual::Roles for more info.
My suggestion, following the model in chromatic's article (comment above by Mike), is this:
In your class:
has request => (
is => 'ro',
isa => 'CodeRef',
default => sub {
Foo::Request->new(#_)
}
);
In your test:
my $request = Test::MockObject->new;
$request->mock(…);
my $tested_class = MyClass->new(request => $request, ...);
Does exactly what your code does, with the following refinements:
make the attribute read-only and set it in the constructor, if possible, for better encapsulation.
your request attribute is a ready-to-use object; no need to dereference the sub ref
Consider this approach:
In your Moose class define an 'abstract' method called make_request. Then define two roles which implement make_request - one which calls Foo::Request->new and another one which calls Test::MockObject->new.
Example:
Your main class and the two roles:
package MainMooseClass;
use Moose;
...
# Note: this class requires a role that
# provides an implementation of 'make_request'
package MakeRequestWithFoo;
use Moose::Role;
use Foo::Request; # or require it
sub make_request { Foo::Request->new(...) }
package MakeRequestWithMock;
use Moose::Role;
use Test::MockRequest; # or require it
sub make_request { Test::MockRequest->new(...) }
If you want to test your main class, mix it with the 'MakeRequestWithMock' role:
package TestVersionOfMainMooseClass;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithMock';
package main;
my $test_object = TestVersionOfMainMooseClass->new(...);
If you want to use it with the Foo implementation of 'make_request', mix it in with the 'MakeRequestWithFoo' role.
Some advantages:
You will only load in modules that you need. For instance, the class TestVersionOfMainMooseClass will not load the module Foo::Request.
You can add data that is relevant/required by your implementation of make_request as instance members of your new class. For example, your original approach of using a CODEREF can be implemented with this role:
package MakeRequestWithCodeRef;
use Moose::Role;
has request_builder => (
is => 'rw',
isa => 'CodeRef',
required => 1,
);
sub make_request { my $self = shift; $self->request_builder->(#_) };
To use this class you need to supply an initializer for request_builder, e.g.:
package Example;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithCodeRef';
package main;
my $object = Example->new(request_builder => sub { ... });
As a final consideration, the roles you write might be usable with other classes.
I know this post is a little old, but for anyone referring to this question now the requester could use a framework like Bread::Board.

How do I create an instance of value from the attribute's meta object with Moose?

I'm working on a serialization tool using Moose to read and write a file that conforms to a nonstandard format. Right now, I determine how to load the next item based on the default values for the objects in the class, but that has its own drawbacks. Instead, I'd like to be able to use information in the attribute meta-class to generate a new value of the right type. I suspect that there's a way to determine what the 'isa' restriction is and derive a generator from it, but I saw no particular methods in Moose::Meta::Attribute or Class::MOP::Attribute that could help me.
Here's a bit further of an example. Let's say I have the following class:
package Example;
use Moose;
use My::Trait::Order;
use My::Class;
with 'My::Role::Load', 'My::Role::Save';
has 'foo' => (
traits => [ 'Order' ],
isa => 'Num',
is => 'rw',
default => 0,
order => 1,
);
has 'bar' => (
traits => [ 'Order' ],
isa => 'ArrayRef[Str]',
is => 'rw',
default => sub { [ map { "" } 1..8 ] }
order => 2,
);
has 'baz' => (
traits => [ 'Order' ],
isa => 'Custom::Class',
is => 'rw',
default => sub { Custom::Class->new() },
order => 3,
);
__PACKAGE__->meta->make_immutable;
1;
(Further explanation: My::Role::Load and My::Role::Save implement the serialization roles for this file type. They iterate over the attributes of the class they're attached to, and look at the attribute classes for an order to serialize in.)
In the My::Role::Load role, I can iterate over the meta object for the class, looking at all the attributes available to me, and picking only those that have my Order trait:
package My::Role::Load;
use Moose;
...
sub load {
my ($self, $path) = #_;
foreach my $attribute ( $self->meta->get_all_attributes ) {
if (does_role($attribute, 'My::Trait::Order') ) {
$self->load_attribute($attribute) # do the loading
}
}
}
Now, I need to know the isa of the attribute that the meta-attribute represents. Right now, I test that by getting an instance of it, and testing it with something that's kind of like this:
use 5.010_001; # need smartmatch fix.
...
sub load_attribute {
my ($self, $attribute, $fh) = #_;
my $value = $attribute->get_value($self); # <-- ERROR PRONE PROBLEM HERE!
if (ref($value) && ! blessed($value)) { # get the arrayref types.
given (ref($value)) {
when('ARRAY') {
$self->load_array($attribute);
}
when('HASH') {
$self->load_hash($attribute);
}
default {
confess "unable to serialize ref of type '$_'";
}
}
}
else {
when (\&blessed) {
confess "don't know how to load it if it doesn't 'load'."
if ! $_->can('load');
$_->load();
}
default {
$attribute->set_value($self, <$fh>);
}
}
}
But, as you can see at # <-- ERROR PRONE PROBLEM HERE!, this whole process relies on there being a value in the attribute to begin with! If the value is undef, I have no indication as to what to load. I'd like to replace the $attribute->get_value($self) with a way to get information about the type of value that needs to be loaded instead. My problem is that the docs I linked to above for the Class::MOP::Attribute and the Moose::Meta::Attribute don't seem to have any way of getting at the type of object that the attribute is supposed to get.
The type information for an attribute is basically what I'm trying to get at.
(Note to future readers: the answer here got me started, but is not the final solution in of itself. You will have to dig into the Moose::Meta::TypeConstraint classes to actually do what I'm looking for here.)
Not sure I follow what you are after and perhaps Coercions might do what you want?
However to get the attributes isa:
{
package Foo;
use Moose;
has 'bar' => ( isa => 'Str', is => 'rw' );
}
my $foo = Foo->new;
say $foo->meta->get_attribute('bar')->type_constraint; # => 'Str'
/I3az/
Out of curiosity why not use/extend MooseX::Storage? It does Serialization, and has for about two and a half years. At the very least MooseX::Storage will help you by showing how a (well tested and production ready) serialization engine for Moose is written.
I'm not quite sure I understand (perhaps you can include some pseudocode that demonstrates what you are looking for), but it sounds like you could possibly get the behaviour you want by defining a new attribute trait: set up your attribute so that a bunch of methods on the class delegate to the attribute's object (isa => 'MySerializer', handles => [ qw(methods) ]).
You might possibly also need to subclass Moose::Meta::Class (or better, add a role to it) which augments the behaviour of add_attribute().
Edit: If you look at the source for Moose::Meta::Attribute (specifically the _process_options method), you will see that the isa option is processed by Moose::Util::TypeConstraints to return the actual type to be stored in the type_constraint field in the object. This will be a Moose::Meta::TypeConstraint::Class object, which you can make calls like is_a_type_of() against.
This field is available via the type_constraint method in Moose::Meta::Attribute. See Moose::Meta::TypeConstraint for all the interfaces available to you for checking an attributes's type.