How can I modify a Moose attribute handle? - perl

Following phaylon's answer to "How can I flexibly add data to Moose objects?", suppose I have the following Moose attribute:
has custom_fields => (
traits => [qw( Hash )],
isa => 'HashRef',
builder => '_build_custom_fields',
handles => {
custom_field => 'accessor',
has_custom_field => 'exists',
custom_fields => 'keys',
has_custom_fields => 'count',
delete_custom_field => 'delete',
},
);
sub _build_custom_fields { {} }
Now, suppose I'd like to croak if trying to read (but not write) to a non-existing custom field. I was suggested by phaylon to wrap custom_field with an around modifier. I have experimented with around modifiers following the various examples in Moose docs, but couldn't figure how to modify a handle (rather than just an object method).
Alternatively, is there another way to implement this croak-if-try-to-read-nonexisting-key?

They're still just methods generated by Moose. You can just do:
around 'custom_field' => sub {
my $orig = shift;
my $self = shift;
my $field = shift;
confess "No $field" unless #_ or $self->has_custom_field($field);
$self->$orig($field, #_);
};
(croak is not very useful in method modifiers at the moment. It will just point you at internal Moose code.)
In fact, you don't need to use around for this. Using before is simpler:
before 'custom_field' => sub {
my $self = shift;
my $field = shift;
confess "No $field" unless #_ or $self->has_custom_field($field);
};

Related

Can you call Moose::before in an imported function with local context

I'm writing a client for a REST server using Moose and trying to map the calls into objects. Since many of the calls are simple and will use a boilerplate function to pre-fetch it, I'm trying to use export a function that creates the actual before functions within each class.
package R::A;
use Moose;
use R::Util qw(make_befores);
has 'var' => (is => 'rw', isa => 'Str');
make_befores('/servercall/' => ['var']);
1;
package R::Util;
use Moose; use Moose::Exporter;
sub make_befores {
while (my ($c, $vs) = each(#_)){
before $vs => sub {
# make call and set object values
};
}
}
Moose::Exporter->setup_import_methods(
as_is => [ 'make_befores', ],
);
1;
That's probably incomplete and definitely untested but I think it relays the point. When calling make_befores, it calls it in context of R::Util and breaks since it doesn't call it as R::A with all its variables and such. If make_befores is simply copy-and-pasted into R::A, it works. This will be used in multiple classes, though, so I want it to be an import-able function.
Or am I going about this all wrong?
UPDATED:
Fuji Goro's solution worked great, but was hard to figure out for a Moose newbie like myself, so here's what it ended up looking like:
sub make_befores {
my $meta = shift;
while (my ($c, $vs) = each(#_)){
my $sub = sub { ... };
Moose::Util::add_method_modifier($meta, before => [$vs => $sub]);
}
}
before is just a syntactic sugar to the MOP. See Moose.pm. Use MOP directly, or you can use Moose::Util::add_method_modifier() and with_meta for this case.
use Moose::Util;
use Moose::Exporter;
sub make_before {
my($meta, #methods) = #_;
Moose::Util::add_method_modifier($meta, before => \#methods);
}
Moose::Exporter->setup_import_methods(
with_meta => [qw(make_before)],
);

How can I provide an alternate init arg for an attribute in Moose?

I of course know that I can rename the init arg for an attribute by setting init_arg (e.g)
package Test {
use Moose;
has attr => (
is => 'ro',
isa => 'Str',
init_arg => 'attribute'
);
}
which would allow me to
Test->new({ attribute => 'foo' });
but not
Test->new({ attr => 'foo' });
at the same time
MooseX::Aliases actually has this behavior, but creating an alias also creates accessors. I'm currently trying to understand the code in that module to see if I can't determine how it does it, so that I can replicate said functionality (in a way I understand). If someone could explain how to do it here with an example that'd be great.
update it appears that MX::Aliases is doing this by way of replacing what's actually passed to the constructor in an around initialize_instance_slot but I'm still not sure how that's actually getting called, because in my test code my around isn't actually getting executed.
update munging in BUILDARGS isn't really an option because what I'm trying to do allow setting of the accessor via the name of the label I'm adding to the attribute via Meta Recipe3. You might say I'm doing
has attr => (
is => 'ro',
isa => 'Str',
alt_init_arg => 'attribute'
);
update
here's what I've managed to work out with what I'm trying to do so far.
use 5.014;
use warnings;
package MooseX::Meta::Attribute::Trait::OtherName {
use Moose::Role;
use Carp;
has other_name => (
isa => 'Str',
predicate => 'has_other_name',
required => 1,
is => 'ro',
);
around initialize_instance_slot => sub {
my $orig = shift;
my $self = shift;
my ( $meta_instance, $instance, $params ) = #_;
confess 'actually calling this code';
return $self->$orig(#_)
unless $self->has_other_name && $self->has_init_arg;
if ( $self->has_other_name ) {
$params->{ $self->init_arg }
= delete $params->{ $self->other_name };
}
};
}
package Moose::Meta::Attribute::Custom::Trait::OtherName {
sub register_implementation { 'MooseX::Meta::Attribute::Trait::OtherName' }
}
package Message {
use Moose;
# use MooseX::StrictConstructor;
has attr => (
traits => [ 'OtherName' ],
is => 'ro',
isa => 'Str',
other_name => 'Attr',
);
__PACKAGE__->meta->make_immutable;
}
package Client {
use Moose;
sub serialize {
my ( $self, $message ) = #_;
confess 'no message' unless defined $message;
my %h;
foreach my $attr ( $message->meta->get_all_attributes ) {
if (
$attr->does('MooseX::Meta::Attribute::Trait::OtherName')
&& $attr->has_other_name
) {
$h{$attr->other_name} = $attr->get_value( $message );
}
}
return \%h;
}
__PACKAGE__->meta->make_immutable;
}
my $message = Message->new( Attr => 'foo' );
my $ua = Client->new;
my %h = %{ $ua->serialize( $message )};
use Data::Dumper::Concise;
say Dumper \%h
problem is that my around block is never being run and I'm not sure why, maybe I'm wrapping it in the wrong place or something.
MooseX::Aliases has several moving parts to make this functionality happen, that's because the behavior needs to be applied to several different places in the MOP. Your code here looks very close to the code in MooseX::Aliases's Trait attribute.
I suspect the reason your code isn't being called is due to something going wrong when you try to register your trait. MooseX::Aliases uses Moose::Util::meta_attribute_alias rather than the old fashioned way you're using here. Try replacing your Moose::Meta::Attribute::Custom::Trait::OtherName section with a call to Moose::Util::meta_attribute_alias 'OtherName'; inside your Role.
Second the code you have here won't work for immutable classes. You'll need to add a second trait to handle those because the immutability code is handled by the class's metaclass and not the attribute's metaclass. You'll need to add some more traits to handle attributes in Roles as well I think. Then you'll need to wire up an Moose::Exporter to make sure that all the traits are applied properly when everything is compiled.
I've gotten a simple version of this working up through immutable. This code is also on github.
First the Attribute trait:
package MooseX::AltInitArg::Meta::Trait::Attribute;
use Moose::Role;
use namespace::autoclean;
Moose::Util::meta_attribute_alias 'AltInitArg';
has alt_init_arg => (
is => 'ro',
isa => 'Str',
predicate => 'has_alt_init_arg',
);
around initialize_instance_slot => sub {
my $orig = shift;
my $self = shift;
my ($meta_instance, $instance, $params) = #_;
return $self->$orig(#_)
# don't run if we haven't set any alt_init_args
# don't run if init_arg is explicitly undef
unless $self->has_alt_init_arg && $self->has_init_arg;
if (my #alternates = grep { exists $params->{$_} } ($self->alt_init_arg)) {
if (exists $params->{ $self->init_arg }) {
push #alternates, $self->init_arg;
}
$self->associated_class->throw_error(
'Conflicting init_args: (' . join(', ', #alternates) . ')'
) if #alternates > 1;
$params->{ $self->init_arg } = delete $params->{ $alternates[0] };
}
$self->$orig(#_);
};
1;
__END__
Next the Class trait.
package MooseX::AltInitArg::Meta::Trait::Class;
use Moose::Role;
use namespace::autoclean;
around _inline_slot_initializer => sub {
my $orig = shift;
my $self = shift;
my ($attr, $index) = #_;
my #orig_source = $self->$orig(#_);
return #orig_source
# only run on aliased attributes
unless $attr->meta->can('does_role')
&& $attr->meta->does_role('MooseX::AltInitArg::Meta::Trait::Attribute');
return #orig_source
# don't run if we haven't set any aliases
# don't run if init_arg is explicitly undef
unless $attr->has_alt_init_arg && $attr->has_init_arg;
my $init_arg = $attr->init_arg;
return (
'if (my #aliases = grep { exists $params->{$_} } (qw('
. $attr->alt_init_arg . '))) {',
'if (exists $params->{' . $init_arg . '}) {',
'push #aliases, \'' . $init_arg . '\';',
'}',
'if (#aliases > 1) {',
$self->_inline_throw_error(
'"Conflicting init_args: (" . join(", ", #aliases) . ")"',
) . ';',
'}',
'$params->{' . $init_arg . '} = delete $params->{$aliases[0]};',
'}',
#orig_source,
);
};
1;
__END__
Finally the Moose::Exporter glue.
package MooseX::AltInitArg;
use Moose();
use Moose::Exporter;
use MooseX::AltInitArg::Meta::Trait::Attribute;
Moose::Exporter->setup_import_methods(
class_metaroles => { class => ['MooseX::AltInitArg::Meta::Trait::Class'] }
);
1;
__END__
An example of how this is used then:
package MyApp;
use 5.10.1;
use Moose;
use MooseX::AltInitArg;
has foo => (
is => 'ro',
traits => ['AltInitArg'],
alt_init_arg => 'bar',
);
my $obj = MyApp->new( bar => 'bar' );
say $obj->foo; # prints bar
Meta-Programming in Moose is incredibly powerful, but because there are a lot of moving parts (many of which have solely to do with maximizing performance) you bite off a lot of work when you dive in.
Good luck.
I could be wrong but I think you might be able to accomplish what I think you are trying to do using the BUILDARGS method. This lets you munge the contructor arguments before they are used to create the object.
#!/usr/bin/env perl
use strict;
use warnings;
{
package MyClass;
use Moose;
has attr => (
is => 'ro',
isa => 'Str',
required => 1,
);
around BUILDARGS => sub {
my $orig = shift;
my $self = shift;
my %args = ref $_[0] ? %{shift()} : #_;
if (exists $args{attribute}) {
$args{attr} = delete $args{attribute};
}
$self->$orig(%args);
};
}
my $one = MyClass->new(attribute => "Hi");
my $two = MyClass->new(attr => "Bye");
print $one->attr, "\n";
print $two->attr, "\n";
So what I'm hearing is that:
At construction time, an attribute should be able to be set by its init_arg and any alternate init_args defined on the attribute.
An attribute should not be able to be manipulated by its alternate init_args except at instance construction; that is, aside from the above, the attribute should behave "normally".
Based on that, this seems like a good match for the MooseX::MultiInitArg attribute trait. Yes? :)

Store a Moose object that has a PDL as an attribute

I am new to Moose and doing quite well until I have hit a snag using a PDL as a property. I want to be able to write an object to a file (I have been using use MooseX::Storage; with Storage('io' => 'StorableFile');, and this object has a PDL as a attribute. PDL::IO::Storable provides the necessary methods to use Storable in this way, however I am at a loss as to how to do this in Moose.
Here is an example, it is a little long, I know, but it is as minimal as I can make it:
#!/usr/bin/perl
package LinearPDL;
use Moose;
use PDL::Lite;
use PDL::IO::Storable;
use MooseX::Storage;
with Storage('io' => 'StorableFile');
has 'length' => (is => 'ro', isa => 'Num', required => 1);
has 'divisions' => (is => 'ro', isa => 'Int', required => 1);
has 'linear_pdl' => (is => 'ro', isa => 'PDL', lazy => 1, builder => '_build_pdl');
sub _build_pdl {
my $self = shift;
my $pdl = $self->length() / ( $self->divisions() - 1 ) * PDL::Basic::xvals($self->divisions());
return $pdl;
}
no Moose;
__PACKAGE__->meta->make_immutable;
use strict;
use warnings;
my $linear_pdl = LinearPDL->new('length' => 5, 'divisions' => 10);
print $linear_pdl->linear_pdl;
$linear_pdl->store('file'); # blows up here!
my $loaded_lpdl = load('file');
print $loaded_lpdl->linear_pdl;
I think I may have to make a PDL type or perhaps even wrap PDL into something (using MooseX::NonMoose::InsideOut), but perhaps someone can save me from that (or point me down the right road if it is).
You don't say what actually goes wrong. At a guess you'll need to tell MooseX::Storage how to handle the PDL object using the PDL object's Storable hooks. The documentation for this feature in MooseX::Storage is very poor but MooseX::Storage::Engine has a add_custom_type_handler() method that takes a typename (PDL in your case) and a HashRef of handlers.
MooseX::Storage::Engine->add_custom_type_handler(
'PDL' => (
expand => sub { my ($data) = #_; ... },
collapse => sub { my ($object) = #_; ... },
)
);
Please swing past #moose on irc.perl.org or the Moose mailing list and ask.
[Edit: Update with an example based on the tests.]
The question by Joel and response from perigrin helped me solve a storage problem that had been sitting in the back of my mind for a while. I'm posting a working example here. It doesn't use PDL but it's related and may help someone in the future.
{
package MVR;
use Moose;
use MooseX::Storage;
use Math::Vector::Real;
use Data::Structure::Util qw (unbless);
with Storage('format' => 'JSON', 'io' => 'File');
MooseX::Storage::Engine->add_custom_type_handler(
'Math::Vector::Real' => (
expand => sub {my $v = shift; Math::Vector::Real->new(#{$v})},
collapse => sub {my $mvr = shift; return (unbless($mvr)) },
)
);
has 'mvr' => (is => 'rw', isa => 'Math::Vector::Real');
1;
}
use Math::Vector::Real;
my $p = MVR->new(mvr => V(0,1,3));
print $p->dump;
$p->store('my_point.json');
my $p1 = MVR->load('my_point.json');
print $p1->dump;

Make the Moose constructor ignore undef arguments

A hashtable is the typical initializer for your Perl objects. Now your input is unreliable in that you don't know whether for any given key there'll be a defined value, nor whether the key is there at all. Now you want to feed such unreliable input into Moose objects, and while absent keys are perfectly okay you do want to get rid of the undefined values so you don't end up with an object full of undefined attributes.
You could certainly take great care when instantiating objects and filter out the undefined values. But let's say you want to install that filter in your constructor because then it is in one place. You want the constructor to ignore undefined values, but not to die on encountering them.
For accessor methods, you can use around around to prevent the attribute to be set to undef. But those method modifiers aren't called for the constructor, only for accessors. Is there a similar facility in Moose to achieve the same effect for the c'tor, i.e. to preclude any undef attributes from being accepted?
Note that the Moose Any type will create the hash key in the object if the attribute is undef. I don't want that because I want %$self not to contain any undef values.
Here's some testing I did:
package Gurke;
use Moose;
use Data::Dumper;
has color => is => 'rw', isa => 'Str', default => 'green';
has length => is => 'rw', isa => 'Num';
has appeal => is => 'rw', isa => 'Any';
around color => sub {
# print STDERR Dumper \#_;
my $orig = shift;
my $self = shift;
return $self->$orig unless #_;
return unless defined $_[0];
return $self->$orig( #_ );
};
package main;
use Test::More;
use Test::Exception;
my $gu = Gurke->new;
isa_ok $gu, 'Gurke';
diag explain $gu;
ok ! exists $gu->{length}, 'attribute not passed, so not set';
diag q(attempt to set color to undef - we don't want it to succeed);
ok ! defined $gu->color( undef ), 'returns undef';
is $gu->color, 'green', 'value unchanged';
diag q(passing undef in the constructor will make it die);
dies_ok { Gurke->new( color => undef ) }
'around does not work for the constructor!';
lives_ok { $gu = Gurke->new( appeal => undef ) } 'anything goes';
diag explain $gu;
diag q(... but creates the undef hash key, which is not what I want);
done_testing;
This is exactly what MooseX::UndefTolerant does. If you make your class immutable, it will be much faster than writing your own BUILDARGS method, as the code is inlined into the generated constructor.
Just provide your own BUILDARGS subroutine.
package Gurke;
...
around 'BUILDARGS' => sub{
my($orig,$self,#params) = #_;
my $params;
if( #params == 1 ){
($params) = #params;
}else{
$params = { #params };
}
for my $key ( keys %$params ){
delete $params->{$key} unless defined $params->{$key};
}
$self->$orig($params);
};
I realize that it is somewhat a duplicated effort, but you can hook ctor with BUILDARGS:
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
my %params = ref $_[0] ? %{$_[0]} : #_;
return $class->$orig(
map { $_ => $params{$_} }
grep { defined $params{$_} }
keys %params
);
};
Edit: Edited to support even the reference passed to ctor.
While the example given clarifies that the question is inspired by a desire to handle undef attributes passed to a constructor, the question itself additionally implies the case of passing only undef to the constructor, which is something I've encountered and wanted to solve.
E.g., Class->new(undef).
I like bvr's BUILDARGS answer. It can be extended to handle the case of passing an undef value instead of a hashref as the lone argument to a constructor:
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
my %params = defined $_[0] ? ref $_[0] ? %{$_[0]} : #_ : ();
return $class->$orig(
map { $_ => $params{$_} }
grep { defined $params{$_} }
keys %params
);
};
MooseX::UndefTolerant does not appear to support this case.

Moose: Expiring cached results of calculations when attribute values change?

In our classes we have a pattern where we create an attribute to represent a
calculated value. For obvious reasons we want to cache the calculated value
and then invalidate the cache when one of the underlying values change.
So we currently have this:
package FooBar;
use Moose;
has 'foo' => (
accessor => {
'foo' => sub {
my $self = shift;
if (#_ > 0) {
# writer
$self->{foo} = $_[0];
# reset fields that are dependant on me
$self->{bar} = undef;
}
# reader part;
return $self->{foo};
}
}
);
has 'bar' => (
accessor => {
'bar' => sub {
my $self = shift;
if (#_ > 0) {
# writer
$self->{bar} = $_[0];
}
# reader part;
$self->{bar} = calculate_bar($self->foo, $self->baz)
if (not defined($self->{bar}));
return $self->{bar};
}
}
);
sub calculate_bar { ... }
This long hand method is getting very tedious and error prone when calculated values
depend on other calculated values.
Is there a smarter/simpler way for 'bar' to monitor the attributes it depends on
vs having 'foo' know who is dependent on it? Also how can I avoid setting bar via hash
member access?
If I understand you correctly, you can use triggers to clear attributes when one is set. Here's an example:
has 'foo' => (
is => 'rw',
trigger => sub{
my ($self) = #_;
$self->clear_bar;
}
);
has 'bar' => (
is => 'rw',
clearer => 'clear_bar',
lazy => 1,
default => sub{
my ($self) = #_;
return calculate_bar( ... );
}
);
So, any writes to foo via $obj->foo($newvalue) will cause bar to be cleared, and recreated on next access.
I think it is quite possible that you're making this harder on yourself by using an Attributes implicit memoization with lazy, when you could just make the memoization explicit making your whole program more transparent
has [qw/foo bar baz/] => ( isa => 'Value', is => 'rw' );
use Memoize;
memoize('_memoize_this');
sub old_lazy_attr {
my $self = shift;
_memoize_this( $self->attr1, $self->attr2, $self->attr3 );
}
sub _memoize_this {
my #args = #_;
# complex stuff
return $result
}
See cpan's Memoize for information and control of the internal cache, also remember that a Memoized function can not be dependent on the state of the object. So the arguments must be passed in explicitly.
Would this work?
#!/usr/bin/perl
package Test;
use Modern::Perl;
use Moose;
has a => (is => 'rw', isa => 'Str', trigger => \&change_a);
has b => (is => 'rw', isa => 'Str', trigger => \&change_b);
has c => (is => 'rw', isa => 'Str');
sub change_a
{
my $self = shift;
say 'update b';
$self->b($self->a . ', bar');
}
sub change_b
{
my $self = shift;
say 'update c';
}
package main;
my $test = Test->new->a('Foo');
Output:
$ perl test.pl
update b
update c
I haven't done any poking around in Moose internals and the meta object protocol, but I think this is a good time to do it.
You want to patch the code generation so that when you specify an attribute as
has 'foo' => ();
has 'bar' => (
depends_on => [qw( foo )],
lazy => \&calculate_bar,
);
the code generation phase creates code for the foo and bar attributes as you specified above.
How to do this is an exercise left to the reader. If I had a clue, I'd try to give you a start. Unfortunately, all I can advise you with is "This is a job for the MOP".