1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
|
use strict;
use warnings;
use Test::More;
use Test::Fatal;
sub req_or_has ($$) {
my ( $role, $method ) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
if ( $role ) {
ok(
$role->has_method($method) || $role->requires_method($method),
$role->name . " has or requires method $method"
);
} else {
fail("role has or requires method $method");
}
}
{
package Bar;
use Moose::Role;
# this role eventually adds three methods, qw(foo bar xxy), but only one is
# known when it's still a role
has foo => ( is => "rw" );
has gorch => ( reader => "bar" );
sub xxy { "BAAAD" }
package Gorch;
use Moose::Role;
# similarly this role gives attr and gorch_method
has attr => ( is => "rw" );
sub gorch_method { "gorch method" }
around dandy => sub { shift->(@_) . "bar" };
package Quxx;
use Moose;
sub dandy { "foo" }
# this object will be used in an attr of Foo to test that Foo can do the
# Gorch interface
with qw(Gorch);
package Dancer;
use Moose::Role;
requires "twist";
package Dancer::Ballerina;
use Moose;
with qw(Dancer);
sub twist { }
sub pirouette { }
package Dancer::Robot;
use Moose::Role;
# this doesn't fail but it produces a requires in the role
# the order doesn't matter
has twist => ( is => "rw" );
::is( ::exception { with qw(Dancer) }, undef );
package Dancer::Something;
use Moose;
# this fail even though the method already exists
has twist => ( is => "rw" );
{
::is( ::exception { with qw(Dancer) }, undef );
}
package Dancer::80s;
use Moose;
# this should pass because ::Robot has the attribute to fill in the requires
# but due to the deferrence logic that doesn't actually work
{
local our $TODO = "attribute accessor in role doesn't satisfy role requires";
::is( ::exception { with qw(Dancer::Robot) }, undef );
}
package Foo;
use Moose;
with qw(Bar);
has oink => (
is => "rw",
handles => "Gorch", # should handles take the same arguments as 'with'? Meta::Role::Application::Delegation?
default => sub { Quxx->new },
);
has dancer => (
is => "rw",
does => "Dancer",
handles => "Dancer",
default => sub { Dancer::Ballerina->new },
);
sub foo { 42 }
sub bar { 33 }
sub xxy { 7 }
package Tree;
use Moose::Role;
has bark => ( is => "rw" );
package Dog;
use Moose::Role;
sub bark { warn "woof!" };
package EntPuppy;
use Moose;
{
local our $TODO = "attrs and methods from a role should clash";
::isnt( ::exception { with qw(Tree Dog) }, undef );
}
}
# these fail because of the deferral logic winning over actual methods
# this might be tricky to fix due to the 'sub foo {}; has foo => ( )' hack
# we've been doing for a long while, though I doubt people relied on it for
# anything other than fulfilling 'requires'
{
local $TODO = "attributes from role overwrite class methods";
is( Foo->new->foo, 42, "attr did not zap overriding method" );
is( Foo->new->bar, 33, "attr did not zap overriding method" );
}
is( Foo->new->xxy, 7, "method did not zap overriding method" ); # duh
# these pass, simple delegate
# mostly they are here to contrast the next blck
can_ok( Foo->new->oink, "dandy" );
can_ok( Foo->new->oink, "attr" );
can_ok( Foo->new->oink, "gorch_method" );
ok( Foo->new->oink->does("Gorch"), "Quxx does Gorch" );
# these are broken because 'attr' is not technically part of the interface
can_ok( Foo->new, "gorch_method" );
{
local $TODO = "accessor methods from a role are omitted in handles role";
can_ok( Foo->new, "attr" );
}
{
local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class";
ok( Foo->new->does("Gorch"), "Foo does Gorch" );
}
# these work
can_ok( Foo->new->dancer, "pirouette" );
can_ok( Foo->new->dancer, "twist" );
can_ok( Foo->new, "twist" );
ok( !Foo->new->can("pirouette"), "can't pirouette, not part of the iface" );
{
local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class";
ok( Foo->new->does("Dancer") );
}
my $gorch = Gorch->meta;
isa_ok( $gorch, "Moose::Meta::Role" );
ok( $gorch->has_attribute("attr"), "has attribute 'attr'" );
isa_ok( $gorch->get_attribute("attr"), "Moose::Meta::Role::Attribute" );
req_or_has($gorch, "gorch_method");
ok( $gorch->has_method("gorch_method"), "has_method gorch_method" );
ok( !$gorch->requires_method("gorch_method"), "requires gorch method" );
isa_ok( $gorch->get_method("gorch_method"), "Moose::Meta::Method" );
{
local $TODO = "method modifier doesn't yet create a method requirement or meta object";
req_or_has($gorch, "dandy" );
# this specific test is maybe not backwards compat, but in theory it *does*
# require that method to exist
ok( $gorch->requires_method("dandy"), "requires the dandy method for the modifier" );
}
{
local $TODO = "attribute related methods are not yet known by the role";
# we want this to be a part of the interface, somehow
req_or_has($gorch, "attr");
ok( $gorch->has_method("attr"), "has_method attr" );
isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method" );
isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method::Accessor" );
}
my $robot = Dancer::Robot->meta;
isa_ok( $robot, "Moose::Meta::Role" );
ok( $robot->has_attribute("twist"), "has attr 'twist'" );
isa_ok( $robot->get_attribute("twist"), "Moose::Meta::Role::Attribute" );
{
req_or_has($robot, "twist");
local $TODO = "attribute related methods are not yet known by the role";
ok( $robot->has_method("twist"), "has twist method" );
isa_ok( $robot->get_method("twist"), "Moose::Meta::Method" );
isa_ok( $robot->get_method("twist"), "Moose::Meta::Method::Accessor" );
}
done_testing;
__END__
I think Attribute needs to be refactored in some way to better support roles.
There are several possible ways to do this, all of them seem plausible to me.
The first approach would be to change the attribute class to allow it to be
queried about the methods it would install.
Then we instantiate the attribute in the role, and instead of deferring the
arguments, we just make an C<unpack>ish method.
Then we can interrogate the attr when adding it to the role, and generate stub
methods for all the methods it would produce.
A second approach is kinda like the Immutable hack: wrap the attr in an
anonmyous class that disables part of its interface.
A third method would be to create an Attribute::Partial object that would
provide a more role-ish behavior, and to do this independently of the actual
Attribute class.
Something similar can be done for method modifiers, but I think that's even simpler.
The benefits of doing this are:
* Much better introspection of roles
* More correctness in many cases (in my opinion anyway)
* More roles are more usable as interface declarations, without having to split
them into two pieces (one for the interface with a bunch of requires(), and
another for the actual impl with the problematic attrs (and stub methods to
fix the accessors) and method modifiers (dunno if this can even work at all)
|