0

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' );
daliaessam
  • 1,636
  • 2
  • 21
  • 43
  • How do you intend to create methods on an instance? What would that mean in the context of a class-based object system? – Richard Huxton Oct 12 '14 at 13:02
  • as far as I understand, Moose attributes are stored in a hash in the object instance, so the question if I add an attribute in the first instance, why this attribute also exist in the next created instances? Is my question clear now? – daliaessam Oct 12 '14 at 13:09

1 Answers1

6

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
tobyink
  • 13,478
  • 1
  • 23
  • 35
  • The question is why the attributes added to all instances if Moose stores the attributes in a hash inside the object, are these class wide attributes or instance attributes? – daliaessam Oct 12 '14 at 18:23
  • 1
    Moose stores the attribute **values** inside the object's hash, but the **methods** to access those values are added to the class, so can be called on any instance of the class. For example, the "6" for Alice's foot size is stored in Alice's hash. But the getter/setter method has been added to Alice's class, so any other objects of the same class can also have a foot size (which might be smaller or larger than "6"). That's why we needed to pull Alice into a new class to stop Bob from getting the `foot_size` method too. – tobyink Oct 12 '14 at 18:33
  • Yes I understand now, you said it very clear. But why this technique, is not it bad, it should be added to the requested instance only as we call the meta on the object. – daliaessam Oct 12 '14 at 18:59
  • 1
    That's just the way method dispatch works in Perl. (And indeed most OO languages!) When you call a method, the language's runtime looks first to the object's class to supply a definition of the method, then any parent classes in order. – tobyink Oct 12 '14 at 19:07
  • 1
    Compare "Self" to "Smalltalk" to see the difference in prototype vs class-based sytems -http://en.wikipedia.org/wiki/Self_programming_language – Richard Huxton Oct 13 '14 at 07:40