Store the role which first defines an attribute, and pass that along when cloning.
Dave Rolsky [Mon, 14 Mar 2011 04:04:51 +0000 (23:04 -0500)]
Use the original role's applied_attribute_metaclass when we finally apply the
attribute, rather than the _current_ role's applied_attribute_metaclass.

lib/Moose/Meta/Role/Application/ToClass.pm
lib/Moose/Meta/Role/Attribute.pm
t/roles/role_attr_application.t

index d544f7f..2ec5531 100644 (file)
@@ -125,7 +125,6 @@ sub check_required_attributes {
 
 sub apply_attributes {
     my ($self, $role, $class) = @_;
-    my $attr_metaclass = $role->applied_attribute_metaclass;
 
     foreach my $attribute_name ($role->get_attribute_list) {
         # it if it has one already
@@ -136,7 +135,7 @@ sub apply_attributes {
         }
         else {
             $class->add_attribute(
-                $role->get_attribute($attribute_name)->attribute_for_class($attr_metaclass)
+                $role->get_attribute($attribute_name)->attribute_for_class
             );
         }
     }
index 9132f1e..15721da 100644 (file)
@@ -22,6 +22,12 @@ __PACKAGE__->meta->add_attribute(
 );
 
 __PACKAGE__->meta->add_attribute(
+    '_original_role' => (
+        reader => '_original_role',
+    )
+);
+
+__PACKAGE__->meta->add_attribute(
     'is' => (
         reader => 'is',
     )
@@ -39,9 +45,12 @@ sub new {
     (defined $name)
         || confess "You must provide a name for the attribute";
 
+    my $role = delete $options{_original_role};
+
     return bless {
         name             => $name,
         original_options => \%options,
+        _original_role   => $role,
         %options,
     }, $class;
 }
@@ -56,9 +65,16 @@ sub attach_to_role {
     weaken( $self->{'associated_role'} = $role );
 }
 
+sub original_role {
+    my $self = shift;
+
+    return $self->_original_role || $self->associated_role;
+}
+
 sub attribute_for_class {
-    my $self      = shift;
-    my $metaclass = shift;
+    my $self = shift;
+
+    my $metaclass = $self->original_role->applied_attribute_metaclass;
 
     return $metaclass->interpolate_class_and_new(
         $self->name => %{ $self->original_options } );
@@ -67,7 +83,13 @@ sub attribute_for_class {
 sub clone {
     my $self = shift;
 
-    return ( ref $self )->new( $self->name, %{ $self->original_options } );
+    my $role = $self->original_role;
+
+    return ( ref $self )->new(
+        $self->name,
+        %{ $self->original_options },
+        _original_role => $role,
+    );
 }
 
 sub is_same_as {
@@ -123,6 +145,12 @@ Returns the option as passed to the constructor.
 
 Returns the L<Moose::Meta::Role> to which this attribute belongs, if any.
 
+=item B<< $attr->original_role >>
+
+Returns the L<Moose::Meta::Role> in which this attribute was first
+defined. This may not be the same as the value C<associated_role()> in the
+case of composite role, or the case where one role consumes other roles.
+
 =item B<< $attr->original_options >>
 
 Returns a hash reference of options passed to the constructor. This is used
index 00fd53b..5c53ce5 100644 (file)
@@ -243,10 +243,7 @@ ok(!Moose::Util::does_role(Baz->meta->get_attribute('foo'), 'Baz::Meta::Attribut
     my $baz = Quux->meta->get_attribute('baz');
     ok(! does_role($baz, 'Quux::Meta::Role::Attribute'),
        "applied_attribute traits do not end up applying to attributes from other roles during composition");
-}
 
-{
-    local $TODO = "applied_attribute metaroles are lost in role composition";
     my $bar = Quux->meta->get_attribute('bar');
     does_ok($bar, 'Quux::Meta::Role::Attribute',
             "attribute metarole applied correctly");