Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Re: Extensions via Moose

by stvn (Monsignor)
on Apr 28, 2011 at 21:43 UTC ( [id://901875]=note: print w/replies, xml ) Need Help??


in reply to Extensions via Moose

can Moose add a role ("traits") to an instance after it has been created

Yes, this can be done in a couple different ways. First, there is MooseX::Traits which is probably the best way to do this as it is battle tested code that just does the right thing for you, a simple skim of the POD should give you an idea of its capabilities.

Next, you can use Moose::Util which has apply_all_roles which will not only apply roles to the $applicant, but will handle any type of role parameterization correctly as well. Basically it takes, as a second argument, the same type of list that the with keyword takes.

If you are going to do this often, you might also want to look at Moose::Util::with_traits, which will create the anon-class for you from the list of roles and return the class name so that you can then use it to create new instances, like so:

my $class = Moose::Util::with_traits( $a_class, @bunch_of_roles ); my $instance = $class->new;

Lastly, you can do it the "hard" way, which is to say, do it by hand yourself using the MOP. The simplest way is to use the Role meta-object like so:

My::Role->meta->apply( $some_instance );
This will do the right thing for instances, classes and roles. And if you have multiple roles you want to apply you can do this:
Moose::Meta::Role->combine( @role_meta_objects )->apply( $some_instanc +e );
But really, you want MooseX::Traits, it is simple and pretty much does exactly what you are looking for.

-stvn

Replies are listed 'Best First'.
Re^2: Extensions via Moose
by John M. Dlugosz (Monsignor) on Apr 29, 2011 at 00:16 UTC
    It says, Apply the roles to the class at new time:

    That does look like a good way to do it, in letting the user specify which extensions to use when creating the object. I don't have to imitate Catalyst's syntax or pass them as arguments to the new method.

    But, I'm still composing it before construction. Consider a main class C1 which contains instances of C2 and C3 which it creates and holds on to. If all that's already been created, I can "patch" all three objects. If I derive an anonymous class and then instantiate it, the amendment for C1 has to include instructions to make it change what it creates for C2 and C3.

    Now adding a role can do everything a traditional derived class can do, right? So there is no need to provide a mechanism to say "use this derived class instead of C2" in addition to the mechanism that lists all the roles that are to be applied to C2 at creation time.

    I could have the builder that instantiates C2 consult an attribute that says what roles to add to it, and the original plug-in added to that list. But, I don't like the idea of having a field that doesn't do anything except during object creation. There should be a more transient way to pass the information around.

    Maybe the builder can call something which gathers the trait's names, and the original plug-in can supply 'before' methods to grow that list. (But if I only have one instance anyway, all the extra code is worse than having a defunct slot!)

    Any more suggestions?

      But, I'm still composing it before construction.

      With MooseX::Traits, yes you are, however doing Role->meta->apply( $instance ) will let you do it after construction, as well the Moose::Util tools as well.

      If I derive an anonymous class and then instantiate it, the amendment for C1 has to include instructions to make it change what it creates for C2 and C3.

      I am not sure what you are asking here, are you looking for a trigger to be fired when C1 is reblessed into the anon-class? So that it will then do something with the C2 and C3 instances? If that is the case, no Moose does not provide that, however it would be pretty simple to just do yourself when the role is added to the C1 instance.

      Now adding a role can do everything a traditional derived class can do, right? So there is no need to provide a mechanism to say "use this derived class instead of C2" in addition to the mechanism that lists all the roles that are to be applied to C2 at creation time.

      When a role is applied to an instance, a new anon-class is created whose superclass is the old class (C2) and with the role added in. Then the instance is reblessed into that anon-class (see Class::MOP for all the reblessed MOP API). So, I believe I am answering your first question right when I say "Yes".

      I could have the builder that instantiates C2 consult an attribute that says what roles to add to it, and the original plug-in added to that list. But, I don't like the idea of having a field that doesn't do anything except during object creation. There should be a more transient way to pass the information around.

      I think I am missing a large part of your design, there are a bunch of ways to go about this, all different depending on your needs.

      Maybe the builder can call something which gathers the trait's names, and the original plug-in can supply 'before' methods to grow that list. (But if I only have one instance anyway, all the extra code is worse than having a defunct slot!)

      Well, the question really is, will it *always* just be one instance? As long as you don't get too carried away, it might be worth the extra effort to make it extensible.

      Any more suggestions?

      Like I said, there are many ways to go about this, runtime-roles (roles applied to an instance) are a powerful feature (thanks Perl 6!) and you can (ab)use them in lots of different ways. It all comes down to what is the best fit for your design.

      -stvn
        I am not sure what you are asking here, are you looking for a trigger to be fired when C1 is reblessed into the anon-class?
        I did not think of that. I was supposing that whatever function wrapped the "pull in an extension" would apply a trait to C1, and then look up $instance->c2 and $instance->c3 and apply traits to them too.

        Basically, there are two approaches: (1) Create the extended class before creating the instance, and (2) Extend an existing instance.

        With (2), I can extend all the collaborators at the same time, using only logic within the function that loads and applies the extension.

        With (1), the extension of the top-level class needs to somehow change things so that when the collaborators are created, they are of the extended types too.

        Without a rebless hook, (2) needs to be wrapped by my code so I does all the steps; it can certainly call "lower level" Role->meta->apply as well. My original question was wondering if these had some syntactic sugar or more mature incarnations in another module already done (like Perl 6 "but" feature).

        But, I'm also supposing that I'm not breaking new ground in wanting an extension mechanism for a more complex case (not a single class, but functionality is broken up into smaller classes) and other people may have approached it already.

        As for my aversion to needless instance data, something I saw in MooseX::Traits raises another question. But I'll start a new post for that.

        Thanks for your patience.

        —John

        Hmm, the code in MooseX::Traits states
        # runtime role application is fundamentally broken. if you really # need it, write it yourself, but consider applying the roles before # you create an instance.
        And it is basically a call to:
        $trait->meta->apply($self, rebless_params => $rebless_params || {});
        So why should I be scared of Trait->meta->apply? Why would writing it myself be better than the function that appears here?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://901875]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2024-04-19 20:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found