I'm writing a module for a moose object. I would like to allow a user using this object (or myself...) add some fields on the fly as he/she desires. I can't define these fields a priori since I simply don't know what they will be.
I currently simply added a single field called extra of type hashref which is is set to rw
, so users can simply put stuff in that hash:
# $obj is a ref to my Moose object
$obj->extra()->{new_thingie}="abc123"; # adds some arbitrary stuff to the object
say $obj->extra()->{new_thingie};
This works. But... is this a common practice? Any other (possibly more elegant) ideas?
Note I do not wish to create another module the extends this one, this really just for on-the-fly stuff I'd like to add.
I would probably do this via native traits:
has custom_fields => (
traits => [qw( Hash )],
isa => 'HashRef',
builder => '_build_custom_fields',
handles => {
custom_field => 'accessor',
has_custom_field => 'exists',
custom_fields => 'keys',
has_custom_fields => 'count',
delete_custom_field => 'delete',
},
);
sub _build_custom_fields { {} }
On an object you'd use this like the following:
my $val = $obj->custom_field('foo'); # get field value
$obj->custom_field('foo', 23); # set field to value
$obj->has_custom_field('foo'); # does a specific field exist?
$obj->has_custom_fields; # are there any fields?
my @names = $obj->custom_fields; # what fields are there?
my $value = $obj->delete_custom_field('foo'); # remove field value
A common use-case for stuff like this is adding optional introspectable data to exception and message classes.
If you haven't made the class immutable (there is a performance penalty for not doing that, in addition to my concerns about changing class definitions on the fly), you should be able to do that by getting the meta class for the object (using $meta = $object->meta
) and using the add_attribute
method in Class::MOP::Class.
#!/usr/bin/perl
package My::Class;
use Moose;
use namespace::autoclean;
package main;
my $x = My::Class->new;
my $meta = $x->meta;
$meta->add_attribute(
foo => (
accessor => 'foo',
)
);
$x->foo(42);
print $x->foo, "\n";
my $y = My::Class->new({ foo => 5 });
print $y->foo, "\n";
Output:
42
5
Just in case you want to add a method to an object and not to the whole class then have a look at something like MooseX::SingletonMethod
.
E.g.
use 5.012;
use warnings;
{
package Foo;
use MooseX::SingletonMethod;
sub bar { 'bar' } # method available to all objects
}
my $foo = Foo->new;
$foo->add_singleton_method( baz => sub { 'baz!' } );
$foo->baz; # => baz!
So in above the method baz
is only added to the object $foo
and not to class Foo
.
Hmmm... I wonder if I could implement a MooseX::SingletonAttribute?
Some previous SO answer using MooseX::SingletonMethod
:
- How do you replace a method of a Moose object at runtime?
- How do I make a new Moose class and instantiate an object of that class at runtime?
And also this blog post maybe of use and/or interest: Easy Anonymous Objects
/I3az/
Even if it's not a good pratice to modify a class at runtime, you can simply make the meta-class mutable, add the attribute(s) and make class immutable again:
$ref->meta->make_mutable ;
$ref->meta->add_attribute($attr_name,%cfg) ;
$ref->meta->make_immmutable ;