http://www.perlmonks.org?node_id=1043195

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

Hi all,

is there a possibility to enumerate all declared attributes of a Moo object from outside? Like keys of a hash?

Best regards
McA

Replies are listed 'Best First'.
Re: Enumerating all attributes of a Moo object
by tobyink (Canon) on Jul 09, 2013 at 06:47 UTC

    Do you want all the attributes of the object that are set; or all the attributes of the class that have been declared?

    Example:

    package Foo { use Moo; has a => (is => "ro"); has b => (is => "ro"); } my $bar = Foo->new(a => 42);

    Do you want just "a", or do you want "a" and "b"?

    If you want just "a" - that is, the list of attributes that have been set for a particular object - then unless you've been playing around subclassing Method::Generate::Accessor, you ought to be able to use keys(%$bar).

    If you want "a" and "b" - that is, the list of all attributes declared for the class - then you could try:

    keys(%{ 'Moo'->_constructor_maker_for('Foo')->all_attribute_specs })

    ... which IIRC respects inheritance and role composition.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

      Hi tobyink,

      thank you for your answer. The comments above and what you showed me let me think again about what I wanted to do:

      I want to implement a factory class generating Moo-objects. IMHO a factory class IS tightly coupled with the objects it creates. I wanted to avoid to explicitly enumerate every attribute in the code for initialisation of the created object.

      Do you think that the usage of the mentioned "introspection"-method is a good way to achieve that? Or should I use a different design?

      By the way: With 'attributes' I thought of all setters related to the attributes declared via 'has', that means all names used for the initialisation hash for method 'new'. Is there a common wording for that?

      Best regards
      McA

        I'll also point out a technique you could use. Instead of FooFactory introspecting class Foo, you could have class Foo pass FooFactory all the information it needs. One simple way to do that is write a wrapper for has...

        #!/usr/bin/env perl use v5.14; use Data::Dumper; package FooFactory { use Moo; has name => (is => "ro", required => 1); our @known_attributes; sub build_foo { my $self = shift; my %params = @_; my %attrs; for (@known_attributes) { $attrs{$_} = $params{$_} if exists $params{$_}; } 'Foo'->new(%attrs, maker => $self); } } package Foo { use Moo; sub my_has { my ($attr, %spec) = @_; # Inform FooFactory of the new attribute # Note we're ignoring init_arg for this simple example. push @FooFactory::known_attributes, $attr; has($attr, %spec); } my_has foo => (is => "ro"); my_has bar => (is => "ro"); my_has baz => (is => "ro"); my_has maker => (is => "ro"); } my $factory = 'FooFactory'->new(name => "Acme Inc"); my $obj = $factory->build_foo(bar => 42); print Dumper($obj);
        package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

        What you need to bear in mind, is that an attribute could have several different names associated with it. Consider:

        use v5.14; package Foo { use Moo; has foo1 => ( is => "rw", init_arg => "foo2", reader => "foo3", writer => "foo4", ); } # Create an object, setting the attribute... my $obj = Foo->new(foo2 => 40); # Increment the value... $obj->foo4( $obj->foo3 + 1 ); # Or cheat and access the hashref directly... $obj->{foo1}++; # Print the value of the attribute... say $obj->foo3;

        Moo does have all this data buried away, but you may be better off using Moose or Mouse which provide documented, public APIs for all this attribute introspection stuff.

        package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Enumerating all attributes of a Moo object
by frozenwithjoy (Priest) on Jul 08, 2013 at 22:37 UTC

    Moo(se) objects are essentially hashrefs, so you can just do this:

    print "$_\n" for keys %{$moo_object};
      Note, that this will not show attributes that are not set:
      use 5.010; use strict; use warnings; { package Foo; use Moose; has bar => ( is => 'rw' ); has baz => ( is => 'rw' ); } my $foo = Foo->new( bar => 1 ); say join "\n", keys %$foo; __END__ bar

      Thanks for this, it was very educative.

      🌸
Re: Enumerating all attributes of a Moo object
by Preceptor (Deacon) on Jul 08, 2013 at 22:01 UTC

    Data::Dumper may be the tool for the job. But poking at the innards of an object is dirty, and you shouldn't do it.

      Hi,

      declared attributes and their accessors are part of the API of an object, therefor IMHO not part of the innards, isn't it? Probably I asked not precise enough. Thank you for your hint.

      Best regards
      McA