The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MooX::Attributes::Shadow - shadow attributes of contained objects

SYNOPSIS

  # shadow Foo's attributes in Bar
  package Bar;

  use Moo;
  use Foo;

  use MooX::Attributes::Shadow ':all';

  # create attributes shadowing class Foo's a and b attributes, with a
  # prefix to avoid collisions.
  shadow_attrs( Foo =>
                attrs => [ qw( a b ) ],
                fmt => sub { 'pfx_' . shift },
              );

  # create an attribute which holds the contained oject, and
  # delegate the shadowed accessors to it.
  has foo   => ( is => 'ro',
                 lazy => 1,
                 default => sub { Foo->new( xtract_attrs( Foo => shift ) ) },
                 handles => shadowed_attrs( Foo ),
               );

DESCRIPTION

Classes which contain other objects at times need to reflect the contained objects' attributes in their own attributes.

In most cases, simple method delegation will suffice:

  package ContainsFoo;

  has foo => ( is => 'ro',
               isa => sub { die unless eval { shift->isa('Foo') } },
               handles => [ 'a' ],
             );

However, method delegation does not kick in when attributes are specified during instantiation of the container class. For example, in

  ContainsFoo->new( a => 1 );

the delegated method for a is not called, and a is simply dropped.

One way of dealing with this is to establish proxy attributes which shadow Foo's attributes, and delay passing them on until after the container object has been instantiated:

  has _a => ( is => 'ro', init_arg => 'a' );

  sub BUILD {

     my $self = shift;

     $self->foo->a( $self->_a );

  }

This requires that Foo's a attribute be of type rw. If the foo attribute can be constructed on the fly,

  has foo => ( is => 'ro',
               handles => [ 'a' ],
               lazy => 1,
               sub default { my $self = shift,
                             Foo->new( a => $self->_a ) }
             )

Then Foo's attribute can be of type ro.

This is tedious when more than one attribute is propagated. If the container has its own a attribute, then one must do more work to avoid name space collisions.

MooX::Attributes::Shadow provides a means of registering the attributes to be shadowed, automatically creating proxy attributes in the container class, and easily extracting the shadowed attributes and values from the container class for use in the contained class's constructor.

A contained class can use MooX::Attributes::Shadow::Role to simplify things even further, so that container classes using it need not know the names of the attributes to shadow.

INTERFACE

shadow_attrs
   shadow_attrs( $contained_class, attrs => \@attrs, %options );

Create read-only attributes for the attributes in @attrs and associate them with $contained_class. There is no means of specifying additional attribute options.

It takes the following options:

fmt

This is a reference to a subroutine which should return a modified attribute name (e.g. to prevent attribute collisions). It is passed the attribute name as its first parameter.

instance

In the case where more than one instance of an object is contained, this (string) is used to identify an individual instance.

private

If true, the actual attribute name is mangled; the attribute initialization name is left untouched (see the init_arg option to the Moo has subroutine). This defaults to true.

shadowed_attrs
  $attrs = shadowed_attrs( $contained, [ $container,] \%options );

Return a hash of attributes shadowed from $contained into $container. $contained and $container may either be a class name or an object. If $container is not specified, the package name of the calling routine is used.

It takes the following options:

instance

In the case where more than one instance of an object is contained, this (string) is used to identify an individual instance.

The keys in the returned hash are the attribute initialization names (not the mangled ones) in the container class; the hash values are the attribute names in the contained class. This makes it easy to delegate accessors to the contained class:

  has foo   => ( is => 'ro',
                 lazy => 1,
                 default => sub { Foo->new( xtract_attrs( Foo => shift ) ) },
                 handles => shadowed_attrs( 'Foo' ),
               );
xtract_attrs
  %attrs = xtract_attrs( $contained, $container_obj, \%options );

After the container class is instantiated, xtract_attrs is used to extract attributes for the contained object from the container object. $contained may be either a class name or an object in the contained class.

It takes the following options:

instance

In the case where more than one instance of an object is contained, this (string) is used to identify an individual instance.

COPYRIGHT & LICENSE

Copyright 2012 Smithsonian Astrophysical Observatory

This software is released under the GNU General Public License. You may find a copy at

   http://www.fsf.org/copyleft/gpl.html

AUTHOR

Diab Jerius <djerius@cfa.harvard.edu>