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 ;
Related
In my last question I asked many unrelated things, and can't accept multiple answers what answers only some questions, so here is clearly (i hope) defined question about the (Moo) attributes.
use 5.010;
use strict;
use warnings;
package SomeMoo;
use Moo;
has $_ => ( is => 'rw', predicate => 1) for (qw(a1 a2 nn xx));
package SomeMoose;
use Moose;
has $_ => ( is => 'rw', predicate => "has_".$_) for (qw(a1 a2 nn xx));
package main;
use Data::Dumper;
my $omoo = SomeMoo->new(a1 => "a1val", a2 => "a2val", xx=>"xxval");
say Dumper $omoo;
# $VAR1 = bless( {
# 'a2' => 'a2val',
# 'a1' => 'a1val',
# 'xx' => 'xxval'
# }, 'SomeMoo' );
#for Moose:
my $omoose = SomeMoose->new(a1 => "a1val", a2 => "a2val", xx=>"xxval");
say Dumper $omoose;
#as above, only blessed to package 'SomeMoose'.
#in both cases can do the next, for getting an (partial) list "attributes"
say $_ for keys (%$omoose); #or %$omoo
#anyway, in Moose i can
say "all attributes";
say $_->name for $omoose->meta->get_all_attributes();
#what prints
# all attributes
# nn
# a2
# a1
# xx
So the blessed object refences an object what contains only attributes what are set.
Question:
why the $self references, (so the %$self conatains) only the
attributes what are set, and not all, e.g the nn too from the example
code? (When the bless only associates the reference with an package why the $omoo isn't contains all package variables?) And from where the Moose knows it?)
how to get all_attributes in case of Moo?
Clearly I missing some basic knowledge.. :(
why the $self references, (so the %$self conatains) only the attributes what are set, and not all, e.g the nn too from the example code? (When the bless only associates the reference with an package why the $omoo isn't contains all package variables?) And from where the Moose knows it?)
Each time you create a new object, such as SomeMoo->new, you create a new reference which is made of two parts, the first is the data, which is usually a hashref, that hashref is blessed by the second part, which is the class, SomeMoo. The hash is used to store data associated with this instance of the new object, while SomeMoo is the class which provides the methods associated with with accessing/manipulating/doing stuff with the data.
Inside your class definitions you call a function called has. This function extends the class with new methods. In the case of Moo, you could make a poor man's version, by writing something like:
package SimpleMoo;
no strict 'refs';
sub new {bless {}, $_[0]} # very very simple constructor (oldskool)
*{__PACKAGE__."::$_"} = sub { # fyi: __PACKAGE__ eq "SimpleMoo"
$_[0]->{$_} = $_[1] if exists $_[1];
return $_[0]->{$_};
} for qw(a1 a2 nn xx);
The above add all our methods to the SimpleMoo class, it does so via manipulating the symbol table, and is probably quite close to what Moo does (except Moo/Moose extends a meta class which your class inherits from), an even simpler example would be to define the accessors manually:
package SimpleMoo;
sub new {bless {}, $_[0]} # very very simple constructor (oldskool)
sub a1 {
my ($self) = shift;
$self->{a1} = $_[0] if exists $_[0]; # set value if provided
return $self->{a1}; # return the value
}
sub a2 {
my ($self) = shift;
$self->{a2} = $_[0] if exists $_[0]; # set value if provided
return $self->{a2}; # return the value
}
# ... copy and paste the for nn and xx ...
As you can see from the examples above, I am extending the class with methods that allow you to set and get values from the hash, but not implicitly setting up the hash structure as part of the object's constructor (which should take place inside new if you want to do this) - just like Moo and Moose.
This means that inspecting the object via something like keys %$omoo doesn't show any keys until they've been set (either via the call to ->new or by setting them with $omoo->a1("someValue").
As mentioned by ikegami, adding something like: default => undef to your has statements will cause Moo/Moose to instantiate the value when the object is first constructed.
I hope this explains how $self contains the object data, which contains only keys previously set by methods or constructors.
how to get all_attributes in case of Moo?
Unfortunately the Moo api provides no method to list all defined attributes so I cannot offer a supported solution, but may I offer one of many (non-perfect highly un-recommended) solutions:
package SimpleMoo;
use Moo;
our #attr_list;
sub my_has {push #attr_list, $_[0]; has #_}
sub get_all_attributes {#attr_list}
my_has $_ => ( is => 'rw', predicate => 1) for (qw(a1 a2 nn xx));
package main;
my $omoo = SimpleMoo->new();
say $_ for $omoo->get_all_attributes;
I'm sure someone from Moo, will be able to provide a better way of accessing the meta class used in Moo to get this list. But I'm sure either solution is not recommended.
Ultimately, you are using Moose and Moo, you shouldn't be inspecting the underlying object, both modules use magic (symbol table modification and class inheritance, and all sorts) and you cannot rely on the underlying object nor the class itself to have the attributes and methods you expect them to have. if you have a use case where you want to list every attribute defined on a class, use Moose and inspect the meta class, or take control of you class by defining it yourself by hand.
You should also consider why you need a list of attributes from an object, perhaps you could list them yourself? what if someone inherits from your class? or you decide to mixin a role? would this break whatever code was trying to utilise a list of attributes on your object?
I have a class built with Moose that's essentially a data container for an article list. All the attributes - like name, number, price, quantity - are data. "Well, what else?", I can hear you say. So what else?
An evil conspiration of unfortunate circumstances now forces external functionality into that package: Tax calculation of the data in this class has to be performed by an external component. This external component is tightly coupled to an entire application including database and dependencies that ruin the component's testability, dragging it into the everything-coupled-together stew. (Even thinking about refactoring the tax component out of the stew is completely out of the question.)
So my idea is to have the class accept a coderef wrapping the tax calculation component. The class would then remain independent of the tax calculation implementation (and its possible nightmare of dependencies), and at the same time it would allow integration with the application environment.
has 'tax_calculator', is => 'ro', isa => 'CodeRef';
But then, I'd have added a non-data component to my class. Why is that a problem? Because I'm (ab)using $self->meta->get_attribute_list to assemble a data export for my class:
my %data; # need a plain hash, no objects
my #attrs = $self->meta->get_attribute_list;
$data{ $_ } = $self->$_ for #attrs;
return %data;
Now the coderef is part of the attribute list. I could filter it out, of course. But I'm unsure any of what I'm doing here is a sound way to proceed. So how would you handle this problem, perceived as the need to separate data attributes and behaviour attributes?
A possible half thought out solution: use inheritance. Create your class as you do today but with a calculate_tax method that dies if called (i.e. a virtual function). Then create subclass that overrides that method to call into the external system. You can test the base class and use the child class.
Alternate solution: use a role to add the calculate_tax method. You can create two roles: Calculate::Simple::Tax and Calculate::Real::Tax. When testing you add the simple role, in production you add the real role.
I whipped up this example, but I don't use Moose, so I may be crazy with respect to how to apply the role to the class. There may be some more Moosey way of doing this:
#!/usr/bin/perl
use warnings;
{
package Simple::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
use Moose::Util qw( apply_all_roles );
has price => ( is => "rw", isa => 'Int' ); #price in pennies
sub new_with_simple_tax {
my $class = shift;
my $obj = $class->new(#_);
apply_all_roles( $obj, "Simple::Tax" );
}
}
my $o = A->new_with_simple_tax(price => 100);
print $o->calculate_tax, " cents\n";
It appears as if the right way to do it in Moose is to use two roles. The first is applied to the class and contains the production code. The second is applied to an object you want to use in testing. It subverts the first method using an around method and never calls the original method:
#!/usr/bin/perl
use warnings;
{
package Complex::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
print "complex was called\n";
#pretend this is more complex
return int($self->price * 0.15);
}
}
{
package Simple::Tax;
use Moose::Role;
requires 'price';
around calculate_tax => sub {
my ($orig_method, $self) = #_;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
has price => ( is => "rw", isa => 'Int' ); #price in pennies
with "Complex::Tax";
}
my $prod = A->new(price => 100);
print $prod->calculate_tax, " cents\n";
use Moose::Util qw/ apply_all_roles /;
my $test = A->new(price => 100);
apply_all_roles($test, 'Simple::Tax');
print $test->calculate_tax, " cents\n";
A couple of things come to mind:
Implement the tax calculation logic in a separate TaxCalculation class that has the article list and the tax calculator as attributes.
Use a mock object as the tax calculator when you test. The tax calculator could be stored in an attribute that by default creates the real tax calculator. The test passes in a mock object that has the same interface but doesn't do anything.
Actually that's not really an abuse of get_attribute_list since that's rather exactly how MooseX::Storage works[^1]. IF you are going to continue to use get_attribute_list to build your straight data you'll want to do what MooseX::Storage does and set up an attribute trait for "DoNotSerialize"[^2]:
package MyApp::Meta::Attribute::Trait::DoNotSerialize;
use Moose::Role;
# register this alias ...
package Moose::Meta::Attribute::Custom::Trait::DoNotSerialize;
sub register_implementation { 'MyApp::Meta::Attribute::Trait::DoNotSerialize' }
1;
__END__
You then can use this in your class like so:
has 'tax_calculator' => ( is => 'ro', isa => 'CodeRef', traits => ['DoNotSerialize'] );
and in your serialization code like so:
my %data; # need a plain hash, no objects
my #attrs = grep { !$_->does('MyApp::Meta::Attribute::Trait::DoNotSerialize') } $self->meta->get_all_attributes; # note the change from get_attribute_list
$data{ $_ } = $_->get_value($self) for #attrs; # note the inversion here too
return %data;
Ultimately though you will end up in a solution similar to the Role one that Chas proposes, and I just answered his follow up question regarding it here: How to handle mocking roles in Moose?.
Hope this helps.
[^1]: And since the most basic use-case for MooseX::Storage is doing exactly what you describe, I highly suggest looking at it to do what you're doing by hand here.
[^2]: Or simply re-use the one from MooseX::Storage creates.
I have a class where I want to apply string overloading on its id attribute. However, Moose doesn't allow string overloading on attribute accessors. For example:
package Foo;
use Moose;
use overload '""' => \&id, fallback => 1;
has 'id' => (
is => 'ro',
isa => 'Int',
default => 5,
);
package main;
my $foo = Foo->new;
print "$foo\n";
The above will give an error:
You are overwriting a locally defined method (id) with an accessor at C:/perl/site/lib/Moose/Meta/Attribute.pm line 927
I have tried a couple of options to get around this:
Marking id is => bare, and replacing it with my own accessor: sub id {$_[0]->{id}}. But this is just a hack.
Having the string overloader use another method which just delegates back to id: sub to_string {$_[0]->id}.
I'm just wondering if anyone has a better way of doing this?
use overload '""' => sub {shift->id}, fallback => 1;
Works fine for me.
I believe you are getting an error because \&id creates a placeholder for a sub to be defined later, because Perl will need to know the address that sub will have when it is defined to create a reference to it. Moose has it's own checks to try to avoid overwriting methods you define and reports this to you.
Since I think what you really want to do is call the id method when the object is used as a sting like so:
use overload '""' => 'id', fallback => 1;
From the overload documentation
Values specified as strings are interpreted as method names.
I've been playing around with this code:
package Foo;
use Moose;
package main;
my $PACKAGE = "Foo";
{
no strict 'refs';
my $has = *{"${PACKAGE}::has"}{CODE};
my $with = *{"${PACKAGE}::with"}{CODE};
# Add a instance member to class $PACKAGE
$has->("bar", is => "rw", required => 1);
# Add a role to class $PACKAGE
$with->("some::role");
}
# Create an instance of $PACKAGE:
$PACKAGE->new(); # error: attribute 'bar' is required means we were successful
This allows me to create a Moose class at run-time, i.e. add instance members to a class, add roles, etc.
My question is: how can I import Moose into package $PACKAGE?
I know I can do this with eval: eval "package $PACKAGE; use Moose"; but I'm wondering if there is a solution along the lines of Moose->import(... $PACKAGE ...).
i.e., a way without using eval. Or is there a completely different way of creating and modifying Moose classes at run time?
You probably want to take a look at Moose::Meta::Class and its create method:
my $class = Moose::Meta::Class->create('Foo',
attributes => [attr => Moose::Meta::Attribute->new(is => 'ro'), ...],
roles => [...],
methods => {...},
superclasses => [...],
);
# Edit: Adding an attribute and method modifiers:
$class->add_attribute(otherattr => (is => 'ro'));
$class->add_around_method_modifier(methodname => sub { ... });
Moose::Meta::Class is a subclass of Class::MOP::Class, so you might want to peek into that one as well. With the above, you can specify roles, superclasses, attributes and methods, or you can first create and then add them via the MOP; whatever fits best.
For the attributes you'll want the Moose kind, which means Moose::Meta::Attribute objects. The constructor to that object is basically the same as using has.
You may want to use Class::MOP, see for example https://metacpan.org/module/Moose::Manual::MOP#ALTERING-CLASSES-WITH-THE-MOP
or
https://metacpan.org/module/Class::MOP::Class#SYNOPSIS
Call extends, with, has, before, after, around, override and augment in the Moose package instead of the ones exported by Moose, and pass the meta object of the class you are creating as an additional first argument.
use strict;
use warnings;
use feature qw( say );
use Moose qw( );
{ # Create MyClass on the fly.
my $meta = Moose->init_meta( for_class => 'MyClass' );
# Moose::with( $meta, 'MyRole' );
Moose::has( $meta, foo => (
is => 'ro',
));
}
say MyClass->new( foo => "Foo" )->foo; # Foo
I'm working on a serialization tool using Moose to read and write a file that conforms to a nonstandard format. Right now, I determine how to load the next item based on the default values for the objects in the class, but that has its own drawbacks. Instead, I'd like to be able to use information in the attribute meta-class to generate a new value of the right type. I suspect that there's a way to determine what the 'isa' restriction is and derive a generator from it, but I saw no particular methods in Moose::Meta::Attribute or Class::MOP::Attribute that could help me.
Here's a bit further of an example. Let's say I have the following class:
package Example;
use Moose;
use My::Trait::Order;
use My::Class;
with 'My::Role::Load', 'My::Role::Save';
has 'foo' => (
traits => [ 'Order' ],
isa => 'Num',
is => 'rw',
default => 0,
order => 1,
);
has 'bar' => (
traits => [ 'Order' ],
isa => 'ArrayRef[Str]',
is => 'rw',
default => sub { [ map { "" } 1..8 ] }
order => 2,
);
has 'baz' => (
traits => [ 'Order' ],
isa => 'Custom::Class',
is => 'rw',
default => sub { Custom::Class->new() },
order => 3,
);
__PACKAGE__->meta->make_immutable;
1;
(Further explanation: My::Role::Load and My::Role::Save implement the serialization roles for this file type. They iterate over the attributes of the class they're attached to, and look at the attribute classes for an order to serialize in.)
In the My::Role::Load role, I can iterate over the meta object for the class, looking at all the attributes available to me, and picking only those that have my Order trait:
package My::Role::Load;
use Moose;
...
sub load {
my ($self, $path) = #_;
foreach my $attribute ( $self->meta->get_all_attributes ) {
if (does_role($attribute, 'My::Trait::Order') ) {
$self->load_attribute($attribute) # do the loading
}
}
}
Now, I need to know the isa of the attribute that the meta-attribute represents. Right now, I test that by getting an instance of it, and testing it with something that's kind of like this:
use 5.010_001; # need smartmatch fix.
...
sub load_attribute {
my ($self, $attribute, $fh) = #_;
my $value = $attribute->get_value($self); # <-- ERROR PRONE PROBLEM HERE!
if (ref($value) && ! blessed($value)) { # get the arrayref types.
given (ref($value)) {
when('ARRAY') {
$self->load_array($attribute);
}
when('HASH') {
$self->load_hash($attribute);
}
default {
confess "unable to serialize ref of type '$_'";
}
}
}
else {
when (\&blessed) {
confess "don't know how to load it if it doesn't 'load'."
if ! $_->can('load');
$_->load();
}
default {
$attribute->set_value($self, <$fh>);
}
}
}
But, as you can see at # <-- ERROR PRONE PROBLEM HERE!, this whole process relies on there being a value in the attribute to begin with! If the value is undef, I have no indication as to what to load. I'd like to replace the $attribute->get_value($self) with a way to get information about the type of value that needs to be loaded instead. My problem is that the docs I linked to above for the Class::MOP::Attribute and the Moose::Meta::Attribute don't seem to have any way of getting at the type of object that the attribute is supposed to get.
The type information for an attribute is basically what I'm trying to get at.
(Note to future readers: the answer here got me started, but is not the final solution in of itself. You will have to dig into the Moose::Meta::TypeConstraint classes to actually do what I'm looking for here.)
Not sure I follow what you are after and perhaps Coercions might do what you want?
However to get the attributes isa:
{
package Foo;
use Moose;
has 'bar' => ( isa => 'Str', is => 'rw' );
}
my $foo = Foo->new;
say $foo->meta->get_attribute('bar')->type_constraint; # => 'Str'
/I3az/
Out of curiosity why not use/extend MooseX::Storage? It does Serialization, and has for about two and a half years. At the very least MooseX::Storage will help you by showing how a (well tested and production ready) serialization engine for Moose is written.
I'm not quite sure I understand (perhaps you can include some pseudocode that demonstrates what you are looking for), but it sounds like you could possibly get the behaviour you want by defining a new attribute trait: set up your attribute so that a bunch of methods on the class delegate to the attribute's object (isa => 'MySerializer', handles => [ qw(methods) ]).
You might possibly also need to subclass Moose::Meta::Class (or better, add a role to it) which augments the behaviour of add_attribute().
Edit: If you look at the source for Moose::Meta::Attribute (specifically the _process_options method), you will see that the isa option is processed by Moose::Util::TypeConstraints to return the actual type to be stored in the type_constraint field in the object. This will be a Moose::Meta::TypeConstraint::Class object, which you can make calls like is_a_type_of() against.
This field is available via the type_constraint method in Moose::Meta::Attribute. See Moose::Meta::TypeConstraint for all the interfaces available to you for checking an attributes's type.