Bool-n-CollectionRef
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
index 464aa66..0d154cb 100644 (file)
@@ -4,116 +4,185 @@ package Moose::Meta::Attribute;
 use strict;
 use warnings;
 
-use Scalar::Util 'weaken', 'reftype';
+use Scalar::Util 'blessed', 'weaken', 'reftype';
 use Carp         'confess';
 
-use Moose::Util::TypeConstraints ':no_export';
+our $VERSION = '0.04';
 
-our $VERSION = '0.01';
+use Moose::Util::TypeConstraints '-no-export';
 
 use base 'Class::MOP::Attribute';
 
-Moose::Meta::Attribute->meta->add_attribute(
-    Class::MOP::Attribute->new('weak_ref' => (
-        reader    => 'weak_ref',
-        predicate => {
-                       'has_weak_ref' => sub { $_[0]->weak_ref() ? 1 : 0 }
+__PACKAGE__->meta->add_attribute('required' => (reader => 'is_required'  ));
+__PACKAGE__->meta->add_attribute('lazy'     => (reader => 'is_lazy'      ));
+__PACKAGE__->meta->add_attribute('coerce'   => (reader => 'should_coerce'));
+__PACKAGE__->meta->add_attribute('weak_ref' => (reader => 'is_weak_ref'  ));
+__PACKAGE__->meta->add_attribute('type_constraint' => (
+    reader    => 'type_constraint',
+    predicate => 'has_type_constraint',
+));
+__PACKAGE__->meta->add_attribute('trigger' => (
+    reader    => 'trigger',
+    predicate => 'has_trigger',
+));
+
+sub new {
+       my ($class, $name, %options) = @_;
+       
+       if (exists $options{is}) {
+               if ($options{is} eq 'ro') {
+                       $options{reader} = $name;
+                       (!exists $options{trigger})
+                           || confess "Cannot have a trigger on a read-only attribute";
                }
-    )) 
-);
-
-Moose::Meta::Attribute->meta->add_attribute(
-    Class::MOP::Attribute->new('type_constraint' => (
-        reader    => 'type_constraint',
-        predicate => 'has_type_constraint',
-    )) 
-);
-
-Moose::Meta::Attribute->meta->add_before_method_modifier('new' => sub {
-       my (undef, undef, %options) = @_;
-       (reftype($options{type_constraint}) && reftype($options{type_constraint}) eq 'CODE')
-               || confess "Type cosntraint parameter must be a code-ref, not " . $options{type_constraint}
-                       if exists $options{type_constraint};            
-});
-
-sub generate_accessor_method {
-    my ($self, $attr_name) = @_;
-       if ($self->has_type_constraint) {
-               if ($self->has_weak_ref) {
-                   return sub {
-                               if (scalar(@_) == 2) {
-                                       (defined $self->type_constraint->($_[1]))
-                                               || confess "Attribute ($attr_name) does not pass the type contraint"
-                                                       if defined $_[1];
-                               $_[0]->{$attr_name} = $_[1];
-                                       weaken($_[0]->{$attr_name});
-                               }
-                       $_[0]->{$attr_name};
-                   };                  
+               elsif ($options{is} eq 'rw') {
+                       $options{accessor} = $name;                             
+                       (reftype($options{trigger}) eq 'CODE')
+                           || confess "A trigger must be a CODE reference"
+                               if exists $options{trigger};                    
+               }                       
+       }
+       
+       if (exists $options{isa}) {
+           
+           if (exists $options{does}) {
+               if (eval { $options{isa}->can('does') }) {
+                   ($options{isa}->does($options{does}))                   
+                       || confess "Cannot have an isa option and a does option if the isa does not do the does";
+               }
+           }       
+           
+           # allow for anon-subtypes here ...
+           if (blessed($options{isa}) && $options{isa}->isa('Moose::Meta::TypeConstraint')) {
+                       $options{type_constraint} = $options{isa};
                }
                else {
-                   return sub {
-                               if (scalar(@_) == 2) {
-                                       (defined $self->type_constraint->($_[1]))
-                                               || confess "Attribute ($attr_name) does not pass the type contraint"
-                                                       if defined $_[1];
-                               $_[0]->{$attr_name} = $_[1];
-                               }
-                       $_[0]->{$attr_name};
-                   };  
-               }       
-       }
-       else {
-               if ($self->has_weak_ref) {
-                   return sub {
-                               if (scalar(@_) == 2) {
-                               $_[0]->{$attr_name} = $_[1];
-                                       weaken($_[0]->{$attr_name});
-                               }
-                       $_[0]->{$attr_name};
-                   };                  
+                   # otherwise assume it is a constraint
+                   my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options{isa});     
+                   # if the constraing it not found ....
+                   unless (defined $constraint) {
+                       # assume it is a foreign class, and make 
+                       # an anon constraint for it 
+                       $constraint = Moose::Util::TypeConstraints::subtype(
+                           'Object', 
+                           Moose::Util::TypeConstraints::where { $_->isa($options{isa}) }
+                       );
+                   }                       
+            $options{type_constraint} = $constraint;
                }
-               else {          
-                   sub {
-                           $_[0]->{$attr_name} = $_[1] if scalar(@_) == 2;
-                       $_[0]->{$attr_name};
-                   };          
+       }       
+       elsif (exists $options{does}) {     
+           # allow for anon-subtypes here ...
+           if (blessed($options{does}) && $options{does}->isa('Moose::Meta::TypeConstraint')) {
+                       $options{type_constraint} = $options{isa};
                }
+               else {
+                   # otherwise assume it is a constraint
+                   my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options{does});              
+                   # if the constraing it not found ....
+                   unless (defined $constraint) {                              
+                       # assume it is a foreign class, and make 
+                       # an anon constraint for it 
+                       $constraint = Moose::Util::TypeConstraints::subtype(
+                           'Role', 
+                           Moose::Util::TypeConstraints::where { $_->does($options{does}) }
+                       );
+                   }                       
+            $options{type_constraint} = $constraint;
+               }           
        }
+       
+       if (exists $options{coerce} && $options{coerce}) {
+           (exists $options{type_constraint})
+               || confess "You cannot have coercion without specifying a type constraint";
+        confess "You cannot have a weak reference to a coerced value"
+            if $options{weak_ref};             
+       }       
+       
+       if (exists $options{lazy} && $options{lazy}) {
+           (exists $options{default})
+               || confess "You cannot have lazy attribute without specifying a default value for it";      
+       }
+       
+       $class->SUPER::new($name, %options);    
+}
+
+sub generate_accessor_method {
+    my ($self, $attr_name) = @_;
+    my $value_name = $self->should_coerce ? '$val' : '$_[1]';
+    my $code = 'sub { '
+    . 'if (scalar(@_) == 2) {'
+        . ($self->is_required ? 
+            'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";' 
+            : '')
+        . ($self->should_coerce ? 
+            'my $val = $self->type_constraint->coercion->coerce($_[1]);'
+            : '')
+        . ($self->has_type_constraint ? 
+            ('(defined $self->type_constraint->check(' . $value_name . '))'
+               . '|| confess "Attribute ($attr_name) does not pass the type contraint with \'' . $value_name . '\'"'
+                       . 'if defined ' . $value_name . ';')
+            : '')
+        . '$_[0]->{$attr_name} = ' . $value_name . ';'
+        . ($self->is_weak_ref ?
+            'weaken($_[0]->{$attr_name});'
+            : '')
+        . ($self->has_trigger ?
+            '$self->trigger->($_[0], ' . $value_name . ');'
+            : '')            
+    . ' }'
+    . ($self->is_lazy ? 
+            '$_[0]->{$attr_name} = ($self->has_default ? $self->default($_[0]) : undef)'
+            . 'unless exists $_[0]->{$attr_name};'
+            : '')    
+    . ' $_[0]->{$attr_name};'
+    . ' }';
+    my $sub = eval $code;
+    confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
+    return $sub;    
 }
 
 sub generate_writer_method {
     my ($self, $attr_name) = @_; 
-       if ($self->has_type_constraint) {
-               if ($self->has_weak_ref) {
-                   return sub { 
-                               (defined $self->type_constraint->($_[1]))
-                                       || confess "Attribute ($attr_name) does not pass the type contraint"
-                                               if defined $_[1];
-                               $_[0]->{$attr_name} = $_[1];
-                               weaken($_[0]->{$attr_name});
-                       };
-               }
-               else {
-                   return sub { 
-                               (defined $self->type_constraint->($_[1]))
-                                       || confess "Attribute ($attr_name) does not pass the type contraint"
-                                               if defined $_[1];
-                               $_[0]->{$attr_name} = $_[1];
-                       };                      
-               }
-       }
-       else {
-               if ($self->has_weak_ref) {
-                   return sub { 
-                               $_[0]->{$attr_name} = $_[1];
-                               weaken($_[0]->{$attr_name});
-                       };                      
-               }
-               else {
-                   return sub { $_[0]->{$attr_name} = $_[1] };                 
-               }
-       }
+    my $value_name = $self->should_coerce ? '$val' : '$_[1]';
+    my $code = 'sub { '
+    . ($self->is_required ? 
+        'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";' 
+        : '')
+    . ($self->should_coerce ? 
+        'my $val = $self->type_constraint->coercion->coerce($_[1]);'
+        : '')
+    . ($self->has_type_constraint ? 
+        ('(defined $self->type_constraint->check(' . $value_name . '))'
+               . '|| confess "Attribute ($attr_name) does not pass the type contraint with \'' . $value_name . '\'"'
+                       . 'if defined ' . $value_name . ';')
+        : '')
+    . '$_[0]->{$attr_name} = ' . $value_name . ';'
+    . ($self->is_weak_ref ?
+        'weaken($_[0]->{$attr_name});'
+        : '')
+    . ($self->has_trigger ?
+        '$self->trigger->($_[0], ' . $value_name . ');'
+        : '')        
+    . ' }';
+    my $sub = eval $code;
+    confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
+    return $sub;    
+}
+
+sub generate_reader_method {
+    my ($self, $attr_name) = @_; 
+    my $code = 'sub {'
+    . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
+    . ($self->is_lazy ? 
+            '$_[0]->{$attr_name} = ($self->has_default ? $self->default($_[0]) : undef)'
+            . 'unless exists $_[0]->{$attr_name};'
+            : '')
+    . '$_[0]->{$attr_name};'
+    . '}';
+    my $sub = eval $code;
+    confess "Could not create reader for '$attr_name' because $@ \n code: $code" if $@;
+    return $sub;
 }
 
 1;
@@ -124,14 +193,26 @@ __END__
 
 =head1 NAME
 
-Moose::Meta::Attribute - 
-
-=head1 SYNOPSIS
+Moose::Meta::Attribute - The Moose attribute metaclass
 
 =head1 DESCRIPTION
 
+This is a subclass of L<Class::MOP::Attribute> with Moose specific 
+extensions. 
+
+For the most part, the only time you will ever encounter an 
+instance of this class is if you are doing some serious deep 
+introspection. To really understand this class, you need to refer 
+to the L<Class::MOP::Attribute> documentation.
+
 =head1 METHODS
 
+=head2 Overridden methods
+
+These methods override methods in L<Class::MOP::Attribute> and add 
+Moose specific features. You can safely assume though that they 
+will behave just as L<Class::MOP::Attribute> does.
+
 =over 4
 
 =item B<new>
@@ -140,17 +221,55 @@ Moose::Meta::Attribute -
 
 =item B<generate_writer_method>
 
+=item B<generate_reader_method>
+
 =back
 
+=head2 Additional Moose features
+
+Moose attributes support type-contstraint checking, weak reference 
+creation and type coercion.  
+
 =over 4
 
 =item B<has_type_constraint>
 
+Returns true if this meta-attribute has a type constraint.
+
 =item B<type_constraint>
 
-=item B<has_weak_ref>
+A read-only accessor for this meta-attribute's type constraint. For 
+more information on what you can do with this, see the documentation 
+for L<Moose::Meta::TypeConstraint>.
+
+=item B<is_weak_ref>
+
+Returns true if this meta-attribute produces a weak reference.
+
+=item B<is_required>
+
+Returns true if this meta-attribute is required to have a value.
 
-=item B<weak_ref>
+=item B<is_lazy>
+
+Returns true if this meta-attribute should be initialized lazily.
+
+NOTE: lazy attributes, B<must> have a C<default> field set.
+
+=item B<should_coerce>
+
+Returns true if this meta-attribute should perform type coercion.
+
+=item B<has_trigger>
+
+Returns true if this meta-attribute has a trigger set.
+
+=item B<trigger>
+
+This is a CODE reference which will be executed every time the 
+value of an attribute is assigned. The CODE ref will get two values, 
+the invocant and the new value. This can be used to handle I<basic> 
+bi-directional relations.
 
 =back
 
@@ -160,13 +279,6 @@ All complex software has bugs lurking in it, and this module is no
 exception. If you find a bug please either email me, or add the bug
 to cpan-RT.
 
-=head1 CODE COVERAGE
-
-I use L<Devel::Cover> to test the code coverage of my tests, below is the 
-L<Devel::Cover> report on this module's test suite.
-
-=head1 ACKNOWLEDGEMENTS
-
 =head1 AUTHOR
 
 Stevan Little E<lt>stevan@iinteractive.comE<gt>