How can I provide an alternative 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
(for example)
package Test {
use Moose;
has attr => (
is => 'ro',
isa => 'Str',
init_arg => 'attribute'
);
}
which would allow me
Test->new({ attribute => 'foo' });
but not
Test->new({ attr => 'foo' });
in the same time
MooseX :: Aliases does have this behavior, but creating an alias also creates accessors. I am currently trying to understand the code in this module to see if I can figure out how it does it so that I can replicate the specified functionality (as I understand it). If someone can explain how to do this here, with an example that would be great.
update, it looks like MX :: Aliases does it by replacing what is actually passed to the constructor in around initialize_instance_slot
, but I'm still not sure how this is actually called, because in my test code, my environment isn In fact, execution is in progress.
update munging in BUILDARGS
is not really an option, because what I'm trying to do is to customize the accessory via the name of the label that I add to the attribute via Meta Recipe3 . You could tell what i do
has attr => (
is => 'ro',
isa => 'Str',
alt_init_arg => 'attribute'
);
Update
here is what I managed to figure out with what I am 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 block around
never runs and I'm not sure why, maybe I'm wrapping it in the wrong place or something.
source to share
MooseX::Aliases
has several moving parts to do this functionality because the behavior has to be applied to several different places in the MOP. Your code here is very close to the code in the MooseX::Aliases
Trait attribute .
I suspect the reason your code is not being called is because something is wrong when you try to register your trait. MooseX::Aliases
uses Moose::Util::meta_attribute_alias
rather than the old fashioned way of using it here. Try replacing the section Moose::Meta::Attribute::Custom::Trait::OtherName
with a call Moose::Util::meta_attribute_alias 'OtherName';
within your role.
The second code you have here will not work for immutable classes. You will need to add a second trait to handle them because immutability code is handled by the class metaclass, not the attribute metaclass. You will need to add some more traits to handle the attributes in Roles, I think. Then you need to hook up Moose :: Exporter to make sure all traits are applied correctly when everything is compiled.
I have a simple version of this work that cannot be changed. This code is also on github .
Attribute attribute first:
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 is a class property.
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 glue Moose::Exporter
.
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:
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 since there are many moving parts (many of which are purely about maximum efficiency), you bite off a lot of work when you dive.
Good luck.
source to share
I could be wrong, but I think you could accomplish what I think you are trying to do using the BUILDARGS method . This allows you to argue constructor 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";
source to share
So, I hear that:
- At build time, the attribute must be set by its init_arg and any alternate init_args defined in the attribute.
- An attribute should not be controlled by its alternate init_args except when instantiated; that is, in addition to the above, the attribute must behave "normally".
From that it looks like a good match for the MooseX :: MultiInitArg attribute attribute . Yes?:)
source to share