in reply to Re: Moose and default values
in thread Moose and default values
Good idea. I don't know why I didn't think of initializers. The Moose cabal aren't especially big fans of initializers, and consider them to be a bit of a misfeature, but this is the kind of niche area they seem to come in handy.
Let's take your initializer technique and add some metaprogramming.
First some test cases...
use v5.14; use Test::More; package Foo { use Moose; has foo => ( traits => ['MooseX::IgnoreFalse::Trait::Attribute'], is => 'ro', default => 'whatever', ); } is(Foo->new(foo => 'xyz')->foo, 'xyz'); is(Foo->new(foo => '0')->foo, 'whatever'); is(Foo->new(foo => '')->foo, 'whatever'); is(Foo->new(foo => undef)->foo, 'whatever'); done_testing;
When those tests all pass, then we're done.
Now, a first stab at the metatrait. For attribute metatraits, after _process_options can be a very handy place to hook onto. It effectively allows you to rewrite a has declaration before the attribute gets created. If you're not doing anything to deeply modify how Moose internals work, then that's often sufficient.
Here we'll just hook after _process_options to add an initializer that does the following:
- If the value passed into the constructor was true, then set it.
- Otherwise, if there is a default, set the attribute value to the default value.
- Otherwise, if there is a builder, set the attribute value to the built value.
- Otherwise, don't set the attribute at all.
How does that look?
use v5.14; package MooseX::IgnoreFalse::Trait::Attribute { use Moose::Role; after _process_options => sub { my ($class, $name, $options) = @_; $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $setter->( $value ) : $meta->has_default ? $setter->( $meta->default($instance) +) : $meta->has_builder ? $setter->( $meta->_call_builder($inst +ance) ) : (); } }; }
This is sufficient to pass our test case; yay! However, the keen eyed may have noticed that if the attribute already had an initializer, that will be replaced with our new initializer. So let's make our hook a little smarter to supplement any existing initializer...
use v5.14; package MooseX::IgnoreFalse::Trait::Attribute { use Moose::Role; after _process_options => sub { my ($class, $name, $options) = @_; if (my $orig = $options->{initializer}) { $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $orig->( $instance, $value, $sett +er ) : $meta->has_default ? $orig->( $instance, $meta->defaul +t($instance), $setter ) : $meta->has_builder ? $orig->( $instance, $meta->_call_ +builder($instance), $setter ) : (); } } else { $options->{initializer} = sub { my ($instance, $value, $setter) = @_; my $meta = $instance->meta->get_attribute($name); $value ? $setter->( $value ) : $meta->has_default ? $setter->( $meta->default($instan +ce) ) : $meta->has_builder ? $setter->( $meta->_call_builder($ +instance) ) : (); } } }; }
That's a fairly gentle introduction to attribute metaprogramming.
For completeness, here's everything in one file.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re^3: Moose and default values
by 7stud (Deacon) on Feb 22, 2013 at 00:06 UTC | |
by tobyink (Canon) on Feb 22, 2013 at 11:41 UTC |