subclassing and role composition for attributes
Matt S Trout [Sun, 7 Nov 2010 05:32:07 +0000 (05:32 +0000)]
lib/Class/Tiny.pm
lib/Method/Generate/Constructor.pm
lib/Role/Tiny.pm
t/class-tiny-accessors.t

index 2e308da..91d6650 100644 (file)
@@ -24,8 +24,19 @@ sub import {
     })->generate_method($target, $name, \%spec);
     ($MAKERS{$target}{constructor} ||= do {
       require Method::Generate::Constructor;
-      Method::Generate::Constructor->new(package => $target)->install_delayed
-    })->register_attribute_spec($name, \%spec);
+      Method::Generate::Constructor
+        ->new(package => $target)
+        ->install_delayed
+        ->register_attribute_specs(do {
+          my @spec;
+          if (my $super = do { no strict 'refs'; ${"${target}::ISA"}[0] }) {
+            if (my $con = $MAKERS{$super}{constructor}) {
+              @spec = %{$con->all_attribute_specs};
+            }
+          }
+          @spec;
+        });
+    })->register_attribute_specs($name, \%spec);
   };
   foreach my $type (qw(before after around)) {
     *{_getglob "${target}::${type}"} = sub {
index b08385d..41af94e 100644 (file)
@@ -5,9 +5,14 @@ use Sub::Quote;
 use base qw(Class::Tiny::Object);
 use Sub::Defer;
 
-sub register_attribute_spec {
-  my ($self, $name, $spec) = @_;
-  $self->{attribute_specs}{$name} = $spec;
+sub register_attribute_specs {
+  my ($self, %spec) = @_;
+  @{$self->{attribute_specs}||={}}{keys %spec} = values %spec;
+  $self;
+}
+
+sub all_attribute_specs {
+  $_[0]->{attribute_specs}
 }
 
 sub install_delayed {
index 100622c..b830f64 100644 (file)
@@ -24,6 +24,14 @@ sub import {
     die "Only one role supported at a time by with" if @_ > 1;
     Role::Tiny->apply_role_to_package($_[0], $target);
   };
+  *{_getglob "${target}::has"} = sub {
+    my ($name, %spec) = @_;
+    ($INFO{$target}{accessor_maker} ||= do {
+      require Method::Generate::Accessor;
+      Method::Generate::Accessor->new
+    })->generate_method($target, $name, \%spec);
+    $INFO{$target}{attributes}{$name} = \%spec;
+  };
   # grab all *non-constant* (ref eq 'SCALAR') subs present
   # in the symbol table and store their refaddrs (no need to forcibly
   # inflate constant subs into real subs) - also add '' to here (this
@@ -84,7 +92,29 @@ sub apply_role_to_package {
   if (not $INFO{$to} and not $to->can('does')) {
     ${_getglob "${to}::does"} = \&does_role;
   }
-    
+
+  if (my $attr_info = $info->{attributes}) {
+    if ($INFO{$to}) {
+      @{$INFO{$to}{attributes}||={}}{keys %$attr_info} = values %$attr_info;
+    } else {
+      my $con = $Class::Tiny::MAKERS{$to}{constructor} ||= do {
+        require Method::Generate::Constructor;
+        Method::Generate::Constructor
+          ->new(package => $to)
+          ->install_delayed
+          ->register_attribute_specs(do {
+            my @spec;
+            if (my $super = do { no strict 'refs'; ${"${to}::ISA"}[0] }) {
+              if (my $con = $Class::Tiny::MAKERS{$super}{constructor}) {
+                @spec = %{$con->all_attribute_specs};
+              }
+            }
+            @spec;
+          });
+      };
+      $con->register_attribute_specs(%$attr_info);
+    }
+  }
 
   # copy our role list into the target's
   @{$APPLIED_TO{$to}||={}}{keys %{$APPLIED_TO{$role}}} = ();
index 053b01c..3edae8a 100644 (file)
@@ -9,6 +9,22 @@ use Test::More;
   has one => (is => 'ro');
   has two => (is => 'rw', init_arg => undef);
   has three => (is => 'ro', init_arg => 'THREE', required => 1);
+
+  package Bar;
+
+  use Role::Tiny;
+
+  has four => (is => 'ro');
+
+  package Baz;
+
+  use Class::Tiny;
+
+  extends 'Foo';
+
+  with 'Bar';
+
+  has five => (is => 'rw');
 }
 
 my $foo = Foo->new(
@@ -17,7 +33,19 @@ my $foo = Foo->new(
 );
 
 is_deeply(
-  { %$foo }, { one => 1, three => 3 }, 'internals ok'
+  { %$foo }, { one => 1, three => 3 }, 'simple class ok'
+);
+
+my $baz = Baz->new(
+  one => 1,
+  THREE => 3,
+  four => 4,
+  five => 5,
+);
+
+is_deeply(
+  { %$baz }, { one => 1, three => 3, four => 4, five => 5 },
+  'subclass with role ok'
 );
 
 done_testing;