Add initial version of lazy attributes in Class::MOP
Scott McWhirter [Thu, 25 Jun 2009 21:54:14 +0000 (22:54 +0100)]
lib/Class/MOP.pm
lib/Class/MOP/Attribute.pm
t/014_attribute_introspection.t
t/025_attribute_lazy.t [new file with mode: 0644]

index 67ade82..7da9660 100644 (file)
@@ -481,6 +481,12 @@ Class::MOP::Attribute->meta->add_attribute(
 );
 
 Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('lazy' => (
+        reader    => { 'is_lazy'     => \&Class::MOP::Attribute::is_lazy     },
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
     Class::MOP::Attribute->new('default' => (
         # default has a custom 'reader' method ...
         predicate => { 'has_default' => \&Class::MOP::Attribute::has_default },
index c58b7d8..d8806d1 100644 (file)
@@ -75,6 +75,7 @@ sub _new {
         'default'            => $options->{default},
         'initializer'        => $options->{initializer},
         'definition_context' => $options->{definition_context},
+        'lazy'               => $options->{lazy},
         # keep a weakened link to the
         # class we are associated with
         'associated_class' => undef,
@@ -101,40 +102,56 @@ sub clone {
     return bless { %{$self}, %options } => ref($self);
 }
 
+sub _call_builder {
+    my ( $self, $instance ) = @_;
+
+    my $builder = $self->builder();
+
+    return $instance->$builder()
+        if $instance->can( $self->builder );
+
+    $self->throw_error(  blessed($instance)
+            . " does not support builder method '"
+            . $self->builder
+            . "' for attribute '"
+            . $self->name
+            . "'",
+            object => $instance,
+     );
+}
+
 sub initialize_instance_slot {
     my ($self, $meta_instance, $instance, $params) = @_;
     my $init_arg = $self->{'init_arg'};
 
+    my ($val, $value_is_set);
     # try to fetch the init arg from the %params ...
 
     # if nothing was in the %params, we can use the
     # attribute's default value (if it has one)
     if(defined $init_arg and exists $params->{$init_arg}){
-        $self->_set_initial_slot_value(
-            $meta_instance, 
-            $instance,
-            $params->{$init_arg},
-        );
-    } 
-    elsif (defined $self->{'default'}) {
-        $self->_set_initial_slot_value(
-            $meta_instance, 
-            $instance,
-            $self->default($instance),
-        );
-    } 
-    elsif (defined( my $builder = $self->{'builder'})) {
-        if ($builder = $instance->can($builder)) {
-            $self->_set_initial_slot_value(
-                $meta_instance, 
-                $instance,
-                $instance->$builder,
-            );
-        } 
-        else {
-            confess(ref($instance)." does not support builder method '". $self->{'builder'} ."' for attribute '" . $self->name . "'");
+        $val = $params->{$init_arg};
+        $value_is_set = 1;
+    } else {
+         return if $self->is_lazy;
+
+        if($self->has_default){
+            $val = $self->default($instance);
+            $value_is_set = 1;
+        } elsif($self->has_builder){
+            $val = $self->_call_builder($instance);
+            $value_is_set = 1;
         }
     }
+
+    return unless $value_is_set;
+    
+    $self->_set_initial_slot_value(
+        $meta_instance,
+        $instance,
+        $val,
+    );
+
 }
 
 sub _set_initial_slot_value {
@@ -184,6 +201,7 @@ sub initializer        { $_[0]->{'initializer'} }
 sub definition_context { $_[0]->{'definition_context'} }
 sub insertion_order    { $_[0]->{'insertion_order'} }
 sub _set_insertion_order { $_[0]->{'insertion_order'} = $_[1] }
+sub is_lazy            { $_[0]->{'lazy'} }
 
 # end bootstrapped away method section.
 # (all methods below here are kept intact)
@@ -319,6 +337,21 @@ sub set_raw_value {
 sub get_raw_value {
     my ($self, $instance) = @_;
 
+    if($self->is_lazy && !$self->has_value($instance)){
+        my $val;
+
+        if($self->has_default){
+            $val = $self->default($instance);
+        } elsif($self->has_builder){
+            $val = $self->_call_builder($instance);
+        }
+       
+        $self->set_initial_value(
+            $instance,
+            $val,
+        );
+    }
+
     Class::MOP::Class->initialize(ref($instance))
                      ->get_meta_instance
                      ->get_slot_value($instance, $self->name);
index 8eb82ce..7bf72a5 100644 (file)
@@ -22,6 +22,7 @@ use Class::MOP;
         initialize_instance_slot
         _set_initial_slot_value
 
+        is_lazy
         name
         has_accessor      accessor
         has_writer        writer
@@ -61,6 +62,7 @@ use Class::MOP;
         install_accessors
         remove_accessors
 
+        _call_builder
         _new
         );
 
diff --git a/t/025_attribute_lazy.t b/t/025_attribute_lazy.t
new file mode 100644 (file)
index 0000000..56c88fa
--- /dev/null
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use Scalar::Util 'reftype', 'blessed';
+
+use Test::More tests => 6;
+use Test::Exception;
+
+use Class::MOP;
+use Class::MOP::Attribute;
+use Class::MOP::Method;
+
+
+{
+    package Foo;
+    use metaclass;
+
+    Foo->meta->add_attribute(
+        bar => (
+            lazy => 1,
+            default => 'haha',
+        )
+    );
+    Foo->meta->add_attribute(
+        baz => (
+            lazy => 1,
+            builder => 'buildit',
+        )
+    );
+
+    sub buildit { 'built' }
+}
+
+{
+    use Devel::Sub::Which qw(:universal);
+
+    my $obj = Foo->meta->new_object();
+    my $attrs = $obj->meta->get_attribute_map();
+
+    my $bar_attr = $attrs->{bar};
+    ok(!$bar_attr->has_value($obj), '... $attr has not had value set');
+    is($bar_attr->get_value($obj), 'haha', '... $attr value is correct');
+    ok($bar_attr->has_value($obj), '... $attr has had value set');
+
+    my $baz_attr = $attrs->{baz};
+    ok(!$baz_attr->has_value($obj), '... $attr has not had value set');
+    is($baz_attr->get_value($obj), 'built', '... $attr value is correct');
+    ok($baz_attr->has_value($obj), '... $attr has had value set');
+}
+
+
+
+