Trait::Attribute::Derived - trait for lazy-built Moose attributes that are derived from another attribute
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;
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.
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.
Split
processor
$_
%_
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.
"Robert Redford"
(segment => 0)
first_name
(segment => -1)
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.
@_
$self
The fields hashref defines which fields will be available in %_ plus a type constraint for each.
fields
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.
full_name
segment
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:
source
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.
postprocessor
has first_three_vowels_only => ( traits => [ FindReplace ], source => 'plain', find => qr{[^AEIOU]}i, replace => '', postprocessor => sub { substr($_, 0, 3) }, );
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;
Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=Trait-Attribute-Derived.
Moose::Cookbook::Meta::WhyMeta, Moose::Cookbook::Meta::Labeled_AttributeTrait, Moose::Meta::Attribute.
Toby Inkster <tobyink@cpan.org>.
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.
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.
To install Trait::Attribute::Derived, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Trait::Attribute::Derived
CPAN shell
perl -MCPAN -e shell install Trait::Attribute::Derived
For more information on module installation, please visit the detailed CPAN module installation guide.