Showing posts with label Moose::Role. Show all posts
Showing posts with label Moose::Role. Show all posts

Tuesday, 16 February 2010

New release of MooseX::AttributeCloner

I released a new version of MooseX:AttributeCloner yesterday to CPAN.

Here are the changes:

1) BugFix - CPANTs put in a bug report that MooseX::Getopt was not in the dependencies list in the Build.PL module

2) You can now do

  my $NewObject = new::object->new_with_cloned_attributes($CurrentObject);

instead of only

  my $NewObject = $CurrentObject->new_with_cloned_attributes(q{new::object);

However, to do this, both objects need to use the MooseX:AttributeCloner role. This is on my TODO list that $CurrentObject would only need to be a Moose object, and not have to utilise the MooseX:AttributeCloner role.

It's out there now. Any feedback appreciated.

Andy

Tuesday, 17 November 2009

Refactoring into Roles

So, the pipeline moves along, and then the bossman says:

'I want to be able to run it outside of the normal pipeline locations - how do I do it?'

After a discussion as to why you want to do this (since the normal person responsible for testing out new Illumina pipelines often does it in the usual place, moving softlinks as necessary), it is that he wanted to run some stuff to dump the output into his home directory.

OK, if that is what you want.

'Oh yes, also, can we make it more generic. A lot of things we have done imply the setup we have here (most of which is generated by the Illumina pipeline) and I would like it to move away from that'.

This is a major refactor then. Separate Business logic from Generic logic, and allow everything to be user defined, should the user want to.

Looking at it, it seems an obvious thing really, but at the time I have to say, I panicked a bit. The pipeline has been written to be pluggable, so new 'components' can be switched in or out quickly, but I hadn't really planned it to be used outside of the current setup. The principle is easy enough to apply elsewhere, and should not take long to set up, but the components were specialised to apply to what they represent.

So, the first thing I did - go and get a drink. I don't drink Tea or Coffee, but popping off for a break seemed like the right thing to do. This break lasted a while, as I thought about strategies to take. Should I go for all new objects, passing them around. How about pushing them from the command line? MooseX::Getopt seemed to give an option to put any attributes onto the command line, but what about subsequent component objects from the pipeline?

In the end, I wondered about using Moose Roles. The Manual seemed to suggest that this could be a good way forward. A 'class' that doesn't need to be instantiated or extended, but instead is consumed to become part of the class that you want. So, everything could have all the same attributes, in exactly the same way.

Now, I have to say, I could see an immediate danger here. If everything can be given to everything, then what makes anything different to be a component, or the pipeline 'flag waver'. So, I needed to be sensible, in that the pipelines need to match the components that are going to be launched from them, but not others.

First job: Sort out the pipelines. This was quite simple, and needed doing anyway, since the flag waver had a component launcher for every possible component, but most components are only used for 1 of 3 pipelines, so separate out the 3 pipeline components into subclasses. This had the added advantage of naming the 3 pipeline flag wavers as well.

OK, so what's the next thing.

Go through all components looking for common attributes and methods, and labelling them as generic, or specific. Once I had done this, I created some roles which I refactored these into. Sometimes leaving them in the class if they were unnecessary to be available in multiple classes, otherwise putting them in a role which described if they were generic/business logic and which described the type of feature they give.

After that, I just needed to apply roles to classes which meant that the flag waver had the same attributes as any component classes it would launch. This enables me to have the attribute value in the flag waver, and pass it to anything launched from it.

This left a headache in that new instantiation would need me to loop through all attributes and pass them through. So, I put together another role to do this (see http://vampiresoftware.blogspot.com/2009/11/moosexattributecloner.html for details on this).

So, now I'm left with the final problem, how to allow the user to run a pipeline with the options they want. A quick solution to this was already being used by another of my team, MooseX::Getopt. This is a great Role to apply to a class, which then enables any attribute to instantly become a command line option. Since the the Roles created above give attributes to a class, then they become command line options. Hurrah, problem solved.

So, after I initially thought that Roles would not really be worth it much, considering how much subclassing I have normally done and have been used to, now I'm convinced that this is a very useful technology.

Result of all this refactoring - over 900 lines of code lost. The value according to sloccount if $35,000 less. The code is now more maintainable, and more useful for users who want to define their own parameters.

Sorry Class::Std, you have just had another nail forcefully bashed into your coffin.

Sunday, 8 November 2009

MooseX::AttributeCloner

I have just released a MooseX::AttributeCloner to the CPAN. The purpose of this role is to go pass all built attribute values from one class object to a newly instantiated object.

The reason for the creation of this role is for our pluggable pipeline system, where we want to be able to pass any parameters from the command line to any of the objects which are responsible for submitting jobs.

The Role inspects all the attributes the class has via meta, and checks that the attribute is defined. If it is, it passes the value into a hash, which it then uses in the object instantiation of the new object.

my $object_with_attributes = $self->new_with_cloned_attributes('Package::Name');

This will take $self's built attributes, and uses them in a hash_ref to create a new object instance of 'Package::Name'.

It does check the init_arg of the attribute in $self, and uses that as the key for the value. If you have a Ref in the attribute, then it will be the same Ref in the new object. (So, in this case, it isn't strictly cloned, please don't be pedantic).

If you wish to pass through additional information, or use a different value for an attribute that you already have a value for in the current object, then a hash_ref can also be provided with key/value pairs so that these can be used/added as as well

my $object_with_attributes = $self->new_with_cloned_attributes('Package::Name',{
attr1 => q{val1},...
});

There are also two additional methods provided by this role, 'attributes_as_json' and attributes_as_escaped_json'. With these methods, you can get a JSON string with the values of all built attributes, which you could then use elsewhere, such as just building a data hash or pushing into a commandline. More info can be found in the Documentation, but it is worth noting, objects are not stringified, throughout the whole data structure. In the case where this would be in an array, a null will be found instead.

I hope that this will be found to be useful. Please give me any feedback for this that you wish.

Friday, 6 November 2009

Extending a Role attribute

Not so much a blog post, more a blog question.

When you are 'extend'ing a Moose base class, Attributes can be extended

package Super::Hero;
use Moose;

has q{super_abilitity} => (isa => q{Str}, is => q{rw});

...

package My::Hero;
use Moose;
extends qw{Super::Hero};

has q{+super_abilitity} => (required => 1);

...


However, in this case, I would like Super::Hero to be a Role to be consumed by some class.

package Super::Hero;
use Moose::Role;

has q{super_abilitity} => (isa => q{Str}, is => q{rw});

...

However, the following doesn't work:

package My::Hero;
use Moose;

with qw{Super::Hero};

has q{+super_abilitity} => (required => 1);

...

The required 'extension' is just ignored. I have to actually declare the whole attribute again.

package My::Hero;
use Moose;

with qw{Super::Hero};

has q{super_abilitity} => (isa => q{Str}, is => q{ro}, required => 1);

...

I can't find anything in CPAN Documentation to confirm or deny that this the deliberate design. Does anyone have any suggestions as to any solutions to this?

Any help appreciated.

Saturday, 3 October 2009

Role reversal

So, in our further investigations into using Moose, I have started to look at using Roles to produce reusable code instead of base classes.

You can find out in the Moose::Manual pages on CPAN about setting them up, but what we had found initially confusing was how much we should make a role do. So our initial investigations just led us to produce ordinary Moose objects to do the functions, but not import them into the consuming objects.

So, we move further on, and find ourselves repeating code as we don't want to import some objects which mostly do other things, so what is the solution.

I'm now taking another look at Roles, to see if that might be the solution, but with another key factor. Keep the role doing as specific a thing as possible.

An example of this is that we often need an object to have methods to expose a run id, a short run name and a run_folder. We do have an object which can provide this, along with other functions, but it is not really reusable in some situations. So I have just done a very small role run::short_info, which can then be used to import just these methods in.

The result, a small amount of (what should be) very reusable code over most, if not all, of our frameworks.

The next one I am doing is run::path_info, which will hopefully give just the next level of features that many need, but not all.

This feels very much the right way to go now, and I hope that we will have a good amount of reusable code which will increase flexibility, and keep the code low maintenance. (Well, let's keep our fingers crossed).

Just now to hope that someone fixes the requires attribute/method in Moose:Role soon.