Subject: | Role application to instance with init_arg'd attributes |
The attached .patch covers a (perhaps over the) edge case where a role is applied at
runtime to an instance which has attributes which in turn have user-defined init_arg
names. With Moose, this works as I expect it to; the role is applied cleanly and all
attributes are in their correct slots. With Mouse, after the instance is reblessed
in Mouse::Meta::Role::apply(), its contents are used (as a simple hashref) to re-
initialize the object. For attributes with init_arg names different from their
actual name, the keys in this hashref are incorrect for initialization.
...an example is probably a lot easier to understand than that was, so here's one
where the attribute is also required so the problem is slightly more evident:
package User; use Mouse;
has 'name' => (isa => 'Str', is => 'ro', required => 1, init_arg => 'Name');
package Admin; use Mouse::Role;
package main;
my $tim = User->new(Name => 'Tim');
Admin->meta->apply($tim);
Be aware that it's allergy season so this 'fix' may not be the best solution (seeing
as I had to patch both the XS and PurePerl versions of _initialize_object(), it's
most likely the wrong place for such changes) and should be triple checked before
being accepted.
--
Sanko Robinson
sanko@cpan.org
Subject: | patch1.patch |
diff --git a/lib/Mouse/PurePerl.pm b/lib/Mouse/PurePerl.pm
index d1b2c27..92532c6 100644
--- a/lib/Mouse/PurePerl.pm
+++ b/lib/Mouse/PurePerl.pm
@@ -288,6 +288,10 @@ sub _initialize_object{
my $init_arg = $attribute->init_arg;
my $slot = $attribute->name;
+ if (defined($init_arg) && exists($args->{$slot})
+ && ! exists($args->{$init_arg})) {
+ $args->{$init_arg} = delete $args->{$slot};
+ }
if (defined($init_arg) && exists($args->{$init_arg})) {
$object->{$slot} = $attribute->_coerce_and_verify($args->{$init_arg}, $object);
diff --git a/t/100_bugs/028_runtime_roles_with_require_and_init_arg_attr.t b/t/100_bugs/028_runtime_roles_with_require_and_init_arg_attr.t
new file mode 100644
index 0000000..4946bd7
--- /dev/null
+++ b/t/100_bugs/028_runtime_roles_with_require_and_init_arg_attr.t
@@ -0,0 +1,23 @@
+use Test::More tests => 2;
+
+{
+ package Admin;
+ use Mouse::Role;
+ sub shutdown {1}
+}
+{
+ package User;
+ use Mouse;
+ has 'name' =>
+ (isa => 'Str', is => 'ro', init_arg => 'Name', required => 1);
+}
+
+package main;
+my $tim = User->new(Name => 'Tim');
+
+Admin->meta->apply($tim);
+
+ok($tim->can('shutdown'),
+ 'The role was successfully composed at the object level');
+is($tim->name, 'Tim',
+ '... attribute with init_arg was re-initialized correctly');
diff --git a/xs-src/Mouse.xs b/xs-src/Mouse.xs
index 5c7e623..fc43932 100644
--- a/xs-src/Mouse.xs
+++ b/xs-src/Mouse.xs
@@ -317,6 +317,15 @@ mouse_class_initialize_object(pTHX_ SV* const meta, SV* const object, HV* const
SV* const init_arg = MOUSE_xa_init_arg(xa);
HE* he;
+ if(SvOK(init_arg) && hv_exists_ent(args, slot, 0U)
+ && !hv_exists_ent(args, init_arg, 0U)) {
+ SV * _current = hv_delete_ent(args, slot, 0, 0U);
+ he = hv_fetch_ent(args, init_arg, TRUE, 0U);
+ SV * value = HeVAL(he);
+ sv_setsv(value, _current);
+ SvSETMAGIC(value);
+ }
+
if(SvOK(init_arg) && ( he = hv_fetch_ent(args, init_arg, FALSE, 0U) ) ){
SV* value = HeVAL(he);
if(flags & MOUSEf_ATTR_HAS_TC){