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 . SEE ALSO Moose::Cookbook::Meta::WhyMeta, Moose::Cookbook::Meta::Labeled_AttributeTrait, Moose::Meta::Attribute. AUTHOR Toby Inkster . 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.