Search code examples
perlmoose

Perl Moose add instance attribute not class attribute


I need to add attribute to Moose class instance. In the code below, when I create instance of the class Child and add attribute "app" to it, I find this attribute also added when I create next instances. What I am doing wrong, again I need the attribute per created instance.

#!C:\perl\bin\perl.exe
#!/usr/bin/perl

use v5.10;
use Moose;
use Data::Dumper;

{
    package Child;

    use Moose;
    use utf8;

    sub name {
        say "My name is Richard";
    }
}

sub add_attribute {
    my ($object, $attr) = @_;

    my $meta = $object->meta;

    if (!$object->can("app")) {
        $meta->add_attribute(app => (is => 'rw', default => sub{$attr}));
        $object->app($attr);
    }
    else {
        #$object->app($attr);
        say "attr $attr already exists: object=". ref($object) . ", attr=".($object->app);
    }
}

my $child = Child->new;
$child->name;
add_attribute($child, "First");
say "Child Attr: " . $child->app;
say "";
say Dumper($child);

my $child1 = Child->new;
$child1->name;
#add_attribute($child1, "Second");
say "Child1 Attr: " . $child1->app;
say Dumper($child1);
#say Dumper($child1->meta);

output:

My name is Richard
Child Attr: First

$VAR1 = bless( {
                 'app' => 'First'
               }, 'Child' );

My name is Richard
Child1 Attr: First
$VAR1 = bless( {
                 'app' => 'First'
               }, 'Child' );

Solution

  • The trick is to create a new subclass of your original class, add the attribute to that, then rebless the instance into the new subclass. Here's an example:

    use v5.14;
    
    package Person {
      use Moose;
      has name => (is => 'ro');
    }
    
    sub add_attribute {
      my ($obj, $name, $value) = @_;
      my $new_class = Moose::Meta::Class->create_anon_class(
        superclasses => [ ref($obj) ],
      );
      $new_class->add_attribute($name, is => 'rw');
      $new_class->rebless_instance($obj, $name => $value);
    }
    
    my $alice  = Person->new(name => 'Alice');
    my $bob    = Person->new(name => 'Bob');
    
    add_attribute($alice, foot_size => 6);
    
    say $alice->foot_size;
    
    say $bob->foot_size;  # dies, no such method