Toby Inkster > Trait-Attribute-Derived-0.003 > Trait::Attribute::Derived

Download:
Trait-Attribute-Derived-0.003.tar.gz

Dependencies

Annotate this POD

Website

CPAN RT

Open  0
View/Report Bugs
Module Version: 0.003   Source   Latest Release: Trait-Attribute-Derived-0.005

NAME ^

Trait::Attribute::Derived - trait for lazy-built Moose attributes that are derived from another attribute

SYNOPSIS ^

   use strict;
   use warnings;
   use Test::More;
   
   {
      package Person;   
      use Moose;
      
      use Trait::Attribute::Derived Split => {
         fields    => { segment => 'Num' },
         processor => sub { (split)[$_{segment}] },
      };
      
      has full_name => (
         is            => 'ro',
         isa           => 'Str',
         required      => 1,
      );
      has first_name => (
         traits        => [ Split ],
         source        => 'full_name',
         segment       => 0,
      );
      has last_name => (
         traits       => [ Split ],
         source        => 'full_name',
         segment      => -1,
      );
      has initial => (
         traits        => [ Split ],
         source        => 'full_name',
         segment       => 0,
         postprocessor => sub { substr $_, 0, 1 },
      );
   }
   
   my $bob = Person->new(full_name => 'Robert Redford');
   is($bob->first_name, 'Robert');
   is($bob->initial, 'R');
   is($bob->last_name, 'Redford');
   done_testing;

DESCRIPTION ^

It is quite common in Moose to have one attribute derived from another via lazy builders. Often you will have several which are very similar:

   has first_name => (
      is           => 'ro',
      lazy         => 1,
      builder      => '_build_first_name',
   );
   
   sub _build_first_name {
      my $self = shift;
      (split /\s/, $self->full_name)[0];
   }
   
   has last_name => (
      is           => 'ro',
      lazy         => 1,
      builder      => '_build_last_name',
   );
   
   sub _build_last_name {
      my $self = shift;
      (split /\s/, $self->full_name)[-1];
   }

Other examples might be an attribute holding an XML DOM tree where several attributes are lazily built using XPath queries; or an attribute holding a DBI database handle where several attribues are lazily built by querying the database; or where one attribute holds the binary contents of a file, and others are fields extracted using unpack.

Trait::Attribute::Derived allows you to automate some of this, reducing duplicated code.

Trait::Attribute::Derived is a trait for Moose attributes; it a parameterized role. The first step when using it is to create a variant of the role with the parameters filled in.

   use Trait::Attribute::Derived Split => {
      fields    => { segment => 'Num' },
      processor => sub { (split)[$_{segment}] },
   };

This defines a variant called Split. The processor coderef is the template for deriving a lazily built attribute from a source attribute. Within this coderef, the special global $_ is set to the value of the source attribute, and the special global %_ hash contains a set of other fields useful in deriving the lazily built attributes.

Using our example from the SYNOPSIS, $_ will be the string "Robert Redford" and %_ will be a hash (segment => 0) when building the first_name or (segment => -1) when building the last_name.

If you'd rather not use magic global variables, the coderef is also passed as arguments (@_): $self, the source attribute value, and a refernce to that hash.

The fields hashref defines which fields will be available in %_ plus a type constraint for each.

Then when we define the attribute itself:

   has first_name => (
      traits        => [ Split ],
      source        => 'full_name',
      segment       => 0,
   );

First of all we reference the Split trait variant; secondly we tell it what source attribute to derive the first name from (full_name); lastly we tell it what segment of the name we want. This corresponds to the segment field we defined when creating the trait variant.

Here's another example:

   {
      package Text;
      use Moose;
      
      use Trait::Attribute::Derived FindReplace => {
         fields => {
            find    => 'RegexpRef',
            replace => 'Str',
         },
         processor => sub {
            my ($self, $value, $fields) = @_;
            $value =~ s/$fields->{find}/$fields->{replace}/g;
            return $value;
         },
      };
      
      has plain => (
         is       => 'ro',
         isa      => 'Str',
      );
      has vowels_only => (
         traits   => [ FindReplace ],
         source   => 'plain',
         find     => qr{[^AEIOU]}i,
         replace  => '',
      );
      has no_vowels  => (
         traits   => [ FindReplace ],
         source   => 'plain',
         find     => qr{[AEIOU]}i,
         replace  => '',
      );
   }

An alternative to setting source on each derived attribute is to set it once when creating the trait variant:

   use Trait::Attribute::Derived FindReplace => {
      source    => 'plain',
      fields    => { ... },
      processor => sub { ... },
   };

One last detail from the SYNOPSIS is postprocessing. An attribute can define a postprocessor coderef that executes after the processor coderef. This takes the same parameters as the processor coderef (and has access to $_ and %_) but rather than operating on the source attribute, operates on the output of the processor.

   has first_three_vowels_only => (
      traits   => [ FindReplace ],
      source   => 'plain',
      find     => qr{[^AEIOU]}i,
      replace  => '',
      postprocessor => sub { substr($_, 0, 3) },
   );

Introspection

   use 5.010;
   
   # say "full_name"
   say Person->meta->get_attribute('first_name')->derived_from;
   
   # say "0"
   say Person->meta->get_attribute('first_name')->segment;
   
   # say "1"
   say Person->meta->get_attribute('initial')->has_postprocessor;

BUGS ^

Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=Trait-Attribute-Derived.

SEE ALSO ^

Moose::Cookbook::Meta::WhyMeta, Moose::Cookbook::Meta::Labeled_AttributeTrait, Moose::Meta::Attribute.

AUTHOR ^

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE ^

This software is copyright (c) 2013 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

DISCLAIMER OF WARRANTIES ^

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

syntax highlighting: