Neighbour has asked for the wisdom of the Perl Monks concerning the following question:

Consider the following minimal demonstration classes:
package Parent; use Moose; has 'knibbel' => ( is => 'rw', isa => 'HashRef[Maybe[Value]]', lazy_build => 1, ); has 'knabbel' => ( is => 'rw', isa => 'ArrayRef[Maybe[Value]]', lazy_build => 1, ); sub BUILD { my $self = shift; my $meta = $self->meta; print("BUILD called for " . __PACKAGE__ . "\n"); no strict; foreach my $attribute ($meta->get_attribute_list) { print("Creating builder for attribute [$attribute]\n"); *{__PACKAGE__ . '::_build_' . $attribute} = sub { my $self = shift; my $meta = $self->meta; my $fropsel = $meta->get_attribute($attribute); if ($fropsel->type_constraint->name =~ /^ArrayRef/) { retu +rn []; } if ($fropsel->type_constraint->name =~ /^HashRef/) { retur +n {}; } }; } use strict; } 1;
package Child; use Moose; extends 'Parent'; has 'knuisje' => ( is => 'rw', isa => 'Str', lazy_build => 1, ); sub BUILD { my $self = shift; my $meta = $self->meta; print("BUILD called for " . __PACKAGE__ . "\n"); no strict; foreach my $attribute ($meta->get_attribute_list) { print("Creating builder for attribute [$attribute]\n"); *{__PACKAGE__ . '::_build_' . $attribute} = sub { my $self = shift; my $meta = $self->meta; my $fropsel = $meta->get_attribute($attribute); if ($fropsel->type_constraint->name =~ /^Str/) { return "" +; } }; } use strict; } 1;
And this simple script to use it:
#!/usr/bin/perl -w use strict; use Parent; use Child; my $child = Child->new();
Which gives the following output:
$ ./testchild.pl BUILD called for Parent Creating builder for attribute [knuisje] BUILD called for Child Creating builder for attribute [knuisje]
Which is not quite what I expected, nor wanted :)
How can I avoid this issue, and access the attributes of the parent in the parent's BUILD-sub?

Replies are listed 'Best First'.
Re: Problem generating builder functions with Moose for inherited objects
by tospo (Hermit) on Sep 02, 2010 at 14:56 UTC

    I just had another look at the documentation and it turns out there is a meta method that you can use to do what you want:

    for my $attr ( $meta->get_all_attributes ) { print $attr->name, "\n"; }
    (from the MOP documentation). Prints out all attributes including inherited ones. To use it, I guess you should only define a BUILD method in the Parent class, where you replace your loop through the attributes by the above.

      That seems to work, thanks! :) That puts the BUILD method in the Parent class at this:
      sub BUILD { my $self = shift; my $meta = $self->meta; print("BUILD called for " . __PACKAGE__ . "\n"); no strict; foreach my $build_attribute ($meta->get_all_attributes) { my $build_attname = $build_attribute->name; print("Creating builder for attribute [" . $build_attname . "] +\n"); if (!defined *{__PACKAGE__ . '::_build_' . $build_attname}) { *{__PACKAGE__ . '::_build_' . $build_attname} = sub { my $self = shift; my $meta = $self->meta; my $attribute = $meta->find_attribute_by_name($build_a +ttname); if (!defined $attribute) { Carp::confess("Error: Can't + find attribute [$build_attname]\n"); } my $type_name = $attribute->type_constraint->name; #if ($attribute->type_constraint->is_a_type_of("ArrayR +ef[Any]")) { return []; } if ($type_name =~ /^ArrayRef/) { return []; } #elsif ($attribute->type_constraint->is_a_type_of("Has +hRef[Any]")) { return {}; } elsif ($type_name =~ /^HashRef/) { return []; } elsif ($attribute->type_constraint->equals("Str")) { r +eturn ""; } elsif ($attribute->type_constraint->is_a_type_of("Num" +)) { return 0; } else { return undef }; }; } } use strict; }
      Unfortunately, the 2 commented lines didn't work out, so I had to replace them with a ->name regex match.
        You have the Moose::Meta::Attribute object already in $build_attribute in the outer loop, so you can do your test simply on that one:
        $result = $build_attribute->type_constraint->is_a_type_of('ArrayRef') +? 'it's an ArrayRef' :'not an ArrayRef';
        I just tried this in one of my Moose classes and it works for me that way.
Re: Problem generating builder functions with Moose for inherited objects
by stvn (Monsignor) on Sep 14, 2010 at 18:26 UTC

    In short, don't create methods inside BUILD. You are making a common mistake of mixing up "object" and "class".

    BUILD is called every time an instance is created, so every time you create an instance of Child or Parent, you will be overwriting all the builder methods, which no doubt is not what you want because it makes no sense.

    All the code you have within BUILD could simply be put in the package declaration itself like so:

    package Child; use Moose; extends 'Parent'; has 'knuisje' => ( is => 'rw', isa => 'Str', lazy_build => 1, ); { my $meta = __PACKAGE__->meta; foreach my $attribute ($meta->get_attribute_list) { $meta->add_method( '_build_' . $attribute, sub { my $self = shift; my $fropsel = $meta->get_attribute($attribute); if ($fropsel->type_constraint->name =~ /^Str/) { retur +n ""; } } ); } } 1;
    This way the code would be generated only once upon class creation, rather then instance creation.

    -stvn
      You are totally right. Thanks for pointing this silly mistake out :)
Re: Problem generating builder functions with Moose for inherited objects
by tospo (Hermit) on Sep 01, 2010 at 13:45 UTC

    I'm not entirely sure I understand what it is you want to do but is there any reason why you are not using builders for your attributes (as in: "has something=>(builder => '_build_something)"?
    They are just method names and therefore inheritance works as expected for any other method.

      The classes that I actually have to work with have too many attributes to manually define builders for. Because of that, I've made this contraption that generates builder functions.
      Unfortunately, while executing the parent's BUILD-function, it seems that $self->meta->get_attribute_list returns the attributes of the child, not the parent (as I was expecting, since the parent's BUILD gets called before the child's BUILD).

        Honestly, please feel free to tell me off for using SmartAss mode but I can't resist to make two comments :->

        1. Using Moose for a class that has attribute's you can't declare the Moose way sort of defeats the purpose of using Moose in the first place, doesn't it? Maybe you are better off doing this the "classical" way.
        2. Is it possible that these classes need to be re-designed? If you have more atributes than you can manage/declare then there might be something wrong with it. I don't claim to be a master OO designer at all but others here certainly are and it might be worth posting your design here to get some advice from other monks.
          My question would be, if this class has hundreds of attributes (or more?) then what does the API look like? How would you document it?

        As for the attribute list: I think the behaviour that you are seeing makes sense. Yes, the parent's BUILD method is called but $self isa "Child", so $self->meta->get_attribute_list should return the Child classes attribute list as far as I can see.